这题太经典了,做法有很多,可以归并排序,可以树状数组,可以权值线段树,这里只说一下权值线段树的做法。权值线段树的作用是维护值域中每个数在序列中出现了多少次,所以其占用空间与值域很有关系。如果值域过大,我们需要离散化一下(就是排序一下,然后用二分查每个数的排名)。我们知道线段树之类的东西需要有询问或者修改操作才行,那么这个问题有什么询问和修改操作呢?可以这样想,我们每次往树中插入一个数,然后看看这个数和它前面的数能组成多少逆序对,那么修改就是插入数,查询就是看比这个数小的数现在出现了多少个,即每个数出现次数的前缀和,然后用目前已经插入的数的个数减去不大于它的数的个数,就是前面已经插入的比它大的数的个数,对于插入数,其实就是某个数出现次数+1,查询前缀和就不用说了。如果没接触过权值线段树的话,可能还是比较难想到这个做法的。在这里我贴上权值线段树的AC代码:
PS:我这个题卡了好久,离散化和二分,甚至单点修改都静态debug了好久,不过还好一发提交就AC了。
#include <cstdio>
#include <cstdlib>
#include <algorithm>
#define ll long long
using namespace std;
const int N=5e5+9;
ll a[N],q[N];
ll f[4*N],n;
void pushup(ll k);
int cmp(const void *a,const void *b);
void modify(ll left,ll right,ll pos,ll k);
ll query(ll left,ll right,ll ql,ll qr,ll k);
int main(){
ll l,r,mid,pos,ans=0;
scanf("%lld",&n);
for(int i=1;i<=n;i++){
scanf("%lld",&a[i]);
q[i]=a[i];
}
qsort(q+1,n,sizeof(ll),cmp);
for(int i=1;i<=n;i++){
l=1;
r=n;
while(l<=r){
mid=(l+r)/2;
if(q[mid]<a[i]){ //找到离散化之后的数据
l=mid+1;
} else {
pos=mid;
r=mid-1;
}
}
modify(1,n,pos,1); //pos是a[i]离散化之后的数据
ans=ans+i-query(1,n,1,pos,1); //把前i个数里面小于等于pos的数据统计起来,i-cnt就是大于的个数
}
printf("%lld\n",ans);
return 0;
}
void pushup(ll k){
f[k]=f[2*k]+f[2*k+1];
}
ll query(ll left,ll right,ll ql,ll qr,ll k){
if(ql<=left && right<=qr){
return f[k];
}
ll mid=(left+right)/2;
ll ansleft=0,ansright=0;
if(ql<=mid){
ansleft=query(left,mid,ql,qr,2*k);
}
if(qr>mid){
ansright=query(mid+1,right,ql,qr,2*k+1);
}
return ansleft+ansright;
}
void modify(ll left,ll right,ll pos,ll k){ //我发现我现在不会写单点修改了!!!
if(pos<left || pos>right){
return;
}
if(left==right && left==pos){
f[k]++;
return;
}
ll mid=(left+right)/2;
if(pos<=mid){
modify(left,mid,pos,2*k);
} else {
modify(mid+1,right,pos,2*k+1);
}
pushup(k);
}
int cmp(const void *a,const void *b){
ll *p1=(ll*)a;
ll *p2=(ll*)b;
return (*p1)-(*p2);
}
题目大意见原题。
这个题目我是从线段树题单里找到的,所以这个题目肯定用线段树能做,但是怎么做呢?我们连序列都没有啊!没关系,我们可以想办法构造序列,然后建立线段树。注意到操作1是乘法,我们不难想到,如果是第i次操作是1,乘了m,那么我们可以把序列的第i个位置改成m,这样求前缀积,再取模,就是当前的模数了。又看到操作2,相当于是对乘法的撤销,但是带着取模咋撤销呢?可以这样想,撤销相当于不乘那个数了,也就是变成乘1了,所以我们只需要把对应位置改为1,然后刷新区间积的模数就好了。这个题目总体来说建模不是很难,关键在于构造出能建立线段树的序列。事实上,在没有显式序列时,我们经常会对一个全是0或者全是1或者其他数的序列建立线段树,然后通过题目叙述的修改和查询来把序列中的数改为有意义的数据。
代码就不放出来了,因为和线段树的模板大同小异,只要能建模出来,就只需要把模板改一下能AC了。
题目大意见原题。
印象深刻,教训深刻的一道题目。对于一个出现数学公式的题目,我们一定不能放任着他不管,除非它根本不可化简或者计算,否则一定要做一些必要的化简工作,即便是重新组合一下顺序,把形式相同的项放在一起也很有用。比如之前做过一道题,就是如果把式子展开并且按照形式归类之后,发现可以用前缀和减少重复计算。本题虽然不是用前缀和简化计算,但是不化简的话,很难做出来本题。把待求式化简之后,我们发现:\(\sum a_i^{2}\)和\(\sum b_i^2\)都是固定的,不管怎么调换顺序都是不变的,唯一的变量是\(\sum a_ib_i\) ,为了让待求式最小,我们需要让这个和最大。如果学过排序不等式的话,肯定知道顺序和>=乱序和>=反序和。所以为了让这个和最大,需要进行一些排序的工作,让这两个序列大小排名相同的火柴放在一起。那么如何看需要移动多少次才能实现这一点呢?可以这样想:我们先拷贝一份数组a到c中,然后对数组c排序,然后构造数组q,q[i]存储的是a中第i个元素的排名(您可能看出来了这里是在做离散化),接着,我们可以对b数组进行相同操作,即拷贝后排序,构造编号数组r[]。这时候,不难发现q和r都是1-n的一个排列,我们想做的事情就是通过对其中一个排列进行操作,得到另一个排列。假设q是1 3 2 4 5,r是1 5 4 2 3,不妨把q变成a b c d e,这样r就得变成a e d c b(rxz大佬将LCS转化为LIS问题时的思想)。如果按照字典序的话,q已经有序了,我们只需要看看需要多少次交换才能让r变得有序就好了,这就成了一个求逆序对有多少个的问题。逆序对可以权值线段树搞定,但是这里显然用mergesort更简单。为了便于理解,我可能进行了多次不必要的拷贝和映射,如果脑子比较好用,大可一步到位,一次映射搞定问题。
代码如下,可能省去了很多不必要的拷贝和映射,不过思想是一样的:
#include <bits/stdc++.h>
#define ll long long
using namespace std;
const int N=1e5+9;
const int mod=1e8-3;
typedef struct{
ll value,pos;
}NB;
NB a[N],b[N],tmp[N],q[N];
ll n,ans;
void Mergesort(ll start,ll end,NB m[]);
void Merge(ll start,ll mid,ll end,NB m[]);
int main(){
scanf("%lld",&n);
for(int i=1;i<=n;i++){
scanf("%lld",&a[i].value);
a[i].pos=i;
}
for(int i=1;i<=n;i++){
scanf("%lld",&b[i].value);
b[i].pos=i;
}
Mergesort(1,n,a);
Mergesort(1,n,b);//离散化
for(ll i=1;i<=n;i++){
q[a[i].pos].value=b[i].pos;
}
ans=0;
Mergesort(1,n,q);
printf("%lld\n",ans);
return 0;
}
void Mergesort(ll start,ll end,NB m[]){
if(start<end){
ll mid=(start+end)/2;
Mergesort(start,mid,m);
Mergesort(mid+1,end,m);
Merge(start,mid,end,m);
}
}
void Merge(ll start,ll mid,ll end,NB m[]){
ll i=start,j=mid+1,k=start;
for(;i<=mid && j<=end;k++){
if(m[i].value<=m[j].value)
tmp[k]=m[i++];
else{
tmp[k]=m[j++];
ans=(ans+mid-i+1)%mod;
}
}
for(;i<=mid;i++,k++)
tmp[k]=m[i];
for(;j<=end;j++,k++)
tmp[k]=m[j];
for(i=start;i<=end;i++)
m[i]=tmp[i];
return;
}
未完待续
题目大意:有一棵树,你最开始在某个结点上,树上每个结点有点权,每次删除操作,你都可以把离你所在的结点距离不超过\(k\)的结点的点权都修改为0,然后你可以选择将自己移动一步或者不移动,其他节点的点权必须沿着向你的方向的简单路径转移,求需要多少次删除操作才能把树上的点的点权全部变成0?
这道题看起来是一道数据结构题。这道题的规则花里胡哨的,我们一个一个看一下,想想怎么搞。首先,把所有离某个结点距离不超过某个数\(k\)的结点的点权全部变成0,不是传统的子树修改或者区间修改,直接暴力修改的话就是迭代加深,如果是把初始点记为根节点的话,其实就是层次遍历,如果想比较快的修改的话,可能考虑树剖,那样的话就得想怎么把对应的区间的编号算出来,由于刚才的层次遍历的启发,我想到一种定义\(bfs\) 序,然后建立线段树维护的方法(结果查了一下天哪真的有\(bfs\)序这种东西)。这样的话,删除的区间在\(bfs\)序上就比较容易求出来,一次修改复杂度是\(O(log^2n)\) 。
其次,对于换根操作(有了刚才的思考,我觉得把这个操作叫做换根并不为过),动手画了几个图之后,没有想出来怎么表示今后的修改区间。
最后,对于移动操作,如果根不换的话,就是把儿子的权值加到父亲上,然后儿子的权值清零。
上面的思路有很多操作并不能高效实现,可能废了。
下面是L_C_A大佬给出的思路:我们杀死所有小怪所用的时间的最小值,取决于离我们最远的小怪有多远,想到这一点,可能会有二分的冲动。假设最远距离是\(maxdist\),则在\([1,maxdist]\)中二分答案。怎样写check呢?看了看数据范围,应该需要在\(O(n)\)的时间内完成check。考虑到我们check的时候一般都是贪心的,所以这里我们也贪心地去验证\(key\)回合能否杀死所有小怪。显然,如果当前的\(maxdist<=k\),则可以1回合杀完,否则的话,我们先杀光范围内的小怪,然后考虑移动:如果自己不移动的话,就是所有其他的小怪离自己的距离-1,如果自己移动的话,肯定是往最远的小怪的方向移动,并且最远的小怪也会向自己移动,这样就相当于最远的小怪离自己的距离-2了,那其他小怪呢?显然,自己移动的方向上的所有小怪的距离都是-2,除此之外的小怪,离自己的距离不变。如何判断哪些结点是在最大值方向的结点呢?看起来好像和子树有关系。自己走到了一个新的节点,那么那个节点的子树上的所有小怪离自己的距离就会-2。
线段树单点修改板子:
void modify(ll left,ll right,ll pos,ll val,ll k){
if(pos<left || pos>right ) return ; //必须要有,否则会无限递归
if(left==pos && pos==right){
f[k]=val;
return;
}
ll mid=(left+right)/2;
if(pos<=mid){ //一定按照线段树的规则
modify(left,mid,pos,val,2*k);
} else {
modify(mid+1,right,pos,val,2*k+1);
}
pushup(k);
}
题目大意:有一个数列,长度为n,允许你用两个不相交的长度为k的区间去覆盖数列,求覆盖的部分的数的和的最大值。
这道题目我的想法是枚举左面那个区间,然后用\(O(logn)\)的时间求出来它对应的右边区间的最大值。如何实现对数查询呢?我开始想的是倍增,但是不太会写。后来看了题解,发现可以线段树。如果对区间长度为k的区间的和的数组建立线段树,那么查询最大值就是单点查询了,十分简单。
当然,这道题还有一个\(dp\)做法:枚举点,对于一个点,求它左边的长度为k的区间的最大值和右边长度为k的区间的最大值,动态更新答案。
这道题的经验是:
题目大意:有一个数列,你需要快速将某个区间内所有的数变成1或者0或者取反或者查询1的个数或者查询连续的1的个数。
考虑前两个操作:都是覆盖性的操作,性质相同,所以如果只维护这两个的话是完全没有问题的。
考虑第三个操作:如果只有取反的话,也好说,但是取反操作和前两种操作的性质明显不一样,所以需要再开一个标记来做。模拟一下可以发现,在对一个极大区间标记取反之前,我们要把前两种覆盖性的操作的标记下放然后清空,不然的话之后就不知道是先取反还是先覆盖的了。然后根据老师的提示,注意到如果有一个区间有取反标记,然后又被染色的话,就不用取反了。
考虑第四个操作:其实还好,我们可以让线段树的结点的值就存储该结点表示的区间的1的个数,然后根据那些标记进行修改就好了。
考虑第五个操作:最多多少个连续的1,显然单个点很好说,如果是区间的话,我们要知道包含区间左端点的最多连续的1的个数是多少(记为\(cnt_1\)),包含右端点的最多连续的1的个数是多少(记为\(cnt_2\)),除此之外最多连续的1的个数是多少(记为\(cnt_3\)),然后在合并的时候假设左边的区间叫x,右边的区间叫y,则区间合并之后的结果就是\(max(xcnt_1,xcnt_3,xcnt_2+ycnt_1,ycnt_2,ycnt_3)\) 。注意,在查询的时候,应该是左边区间的查询结果、右边区间的查询结果以及左区间右连续+右区间左连续的结果的最大值。还有一点是,左区间右连续的结果和右区间左连续的结果要保证在\([ql,qr]\)的范围内,也就是说得取一个min,详见代码注释。
这道题目的经验是:对于覆盖性操作标记和其他类型标记,在打覆盖性标记时可能会让其他标记失效,类似地,在已经有覆盖性标记时又打其他标记,覆盖性标记也会发生变化。
代码如下(很长):
#include <bits/stdc++.h>
#define ll long long
using namespace std;
const int N=1e5+9;
const int M=4e5+9;
typedef struct{
ll cnt; //这个区间有多少个数
ll sum;
ll concrete_one,concrete_zero;
ll left_zero_cnt,right_zero_cnt,mid_zero_cnt;
ll left_one_cnt,right_one_cnt,mid_one_cnt;
ll color,reverse; //color初始化为-1
}SMT;
SMT f[4*N];
ll n,m,a[N];
void pushup(ll k);
void build(ll left,ll right,ll k);
void pushdown(ll left,ll right,ll k);
void modify0(ll left,ll right,ll ql,ll qr,ll k);
void modify1(ll left,ll right,ll ql,ll qr,ll k);
void modify2(ll left,ll right,ll ql,ll qr,ll k);
ll query3(ll left,ll right,ll ql,ll qr,ll k);
ll query4(ll left,ll right,ll ql,ll qr,ll k);
int main(){
ll op,l,r;
scanf("%lld %lld",&n,&m);
for(int i=1;i<=n;i++){
scanf("%lld",&a[i]);
}
for(ll i=1;i<=M;i++){
f[i].color=-1;
}
build(1,n,1);
while(m--){
scanf("%lld %lld %lld",&op,&l,&r);
if(op==0){
modify0(1,n,l+1,r+1,1);
} else if(op==1){
modify1(1,n,l+1,r+1,1);
} else if(op==2){
modify2(1,n,l+1,r+1,1);
} else if(op==3){
printf("%lld\n",query3(1,n,l+1,r+1,1));
} else {
printf("%lld\n",query4(1,n,l+1,r+1,1));
}
}
return 0;
}
void modify1(ll left,ll right,ll ql,ll qr,ll k){
if(ql<=left && right<=qr){
f[k].color=1;
f[k].concrete_one=f[k].cnt;
f[k].left_one_cnt=f[k].cnt;
f[k].mid_one_cnt=f[k].cnt;
f[k].right_one_cnt=f[k].cnt;
f[k].left_zero_cnt=0;
f[k].mid_zero_cnt=0;
f[k].right_zero_cnt=0;
f[k].concrete_zero=0;
f[k].reverse=0;
f[k].sum=f[k].cnt;
return;
}
pushdown(left,right,k);
ll mid=(left+right)/2;
if(ql<=mid){
modify1(left,mid,ql,qr,2*k);
}
if(qr>mid){
modify1(mid+1,right,ql,qr,2*k+1);
}
pushup(k);
}
void modify2(ll left,ll right,ll ql,ll qr,ll k){
if(ql<=left && right<=qr){
if(f[k].color!=-1){ //如果这个区间已经有了染色标记,则把染色标记翻转
f[k].color^=1;
} else { //如果没有染色标记,则翻转标记加一
f[k].reverse^=1;
}
swap(f[k].concrete_one,f[k].concrete_zero);
swap(f[k].left_one_cnt,f[k].left_zero_cnt);
swap(f[k].mid_one_cnt,f[k].mid_zero_cnt);
swap(f[k].right_one_cnt,f[k].right_zero_cnt);
f[k].sum=f[k].cnt-f[k].sum;
return;
}
pushdown(left,right,k);
ll mid=(left+right)/2;
if(ql<=mid){
modify2(left,mid,ql,qr,2*k);
}
if(qr>mid){
modify2(mid+1,right,ql,qr,2*k+1);
}
pushup(k);
}
void modify0(ll left,ll right,ll ql,ll qr,ll k){
if(ql<=left && right<=qr){
f[k].color=0;
f[k].concrete_one=0;
f[k].left_one_cnt=0;
f[k].mid_one_cnt=0;
f[k].right_one_cnt=0;
f[k].left_zero_cnt=f[k].cnt;
f[k].mid_zero_cnt=f[k].cnt;
f[k].right_zero_cnt=f[k].cnt;
f[k].concrete_zero=f[k].cnt;
f[k].reverse=0;
f[k].sum=0;
return;
}
pushdown(left,right,k);
ll mid=(left+right)/2;
if(ql<=mid){
modify0(left,mid,ql,qr,2*k);
}
if(qr>mid){
modify0(mid+1,right,ql,qr,2*k+1);
}
pushup(k);
}
ll query4(ll left,ll right,ll ql,ll qr,ll k){
if(ql<=left && right<=qr){
return f[k].concrete_one;
}
pushdown(left,right,k);
ll mid=(left+right)/2;
ll ans=0;
if(ql<=mid){
ans=query4(left,mid,ql,qr,2*k);
}
if(qr>mid){
ans=max(ans,query4(mid+1,right,ql,qr,2*k+1));
}
if(ql<=mid && qr>mid)
ans=max(ans,min(f[2*k].right_one_cnt,mid-ql+1)+min(f[2*k+1].left_one_cnt,qr-mid)); //很关键,防止越界
return ans;
}
ll query3(ll left,ll right,ll ql,ll qr,ll k){
if(ql<=left && right<=qr){
return f[k].sum;
}
pushdown(left,right,k);
ll mid=(left+right)/2;
ll ret=0;
if(ql<=mid){
ret+=query3(left,mid,ql,qr,2*k);
}
if(qr>mid){
ret+=query3(mid+1,right,ql,qr,2*k+1);
}
return ret;
}
void pushdown(ll left,ll right,ll k){
if(f[k].color!=-1){
f[2*k].color=f[k].color;
f[2*k+1].color=f[k].color;
f[2*k].reverse=0; //取反失效
f[2*k+1].reverse=0;
if(f[k].color==0){
f[2*k].sum=0;
f[2*k].left_one_cnt=0;
f[2*k].mid_one_cnt=0;
f[2*k].right_one_cnt=0;
f[2*k].left_zero_cnt=f[2*k].cnt;
f[2*k].mid_zero_cnt=f[2*k].cnt;
f[2*k].right_zero_cnt=f[2*k].cnt;
f[2*k].concrete_one=0;
f[2*k].concrete_zero=f[2*k].cnt;
f[2*k+1].sum=0;
f[2*k+1].left_one_cnt=0;
f[2*k+1].mid_one_cnt=0;
f[2*k+1].right_one_cnt=0;
f[2*k+1].left_zero_cnt=f[2*k+1].cnt;
f[2*k+1].mid_zero_cnt=f[2*k+1].cnt;
f[2*k+1].right_zero_cnt=f[2*k+1].cnt;
f[2*k+1].concrete_one=0;
f[2*k+1].concrete_zero=f[2*k+1].cnt;
} else {
f[2*k].sum=f[2*k].cnt;
f[2*k].left_one_cnt=f[2*k].cnt;
f[2*k].mid_one_cnt=f[2*k].cnt;
f[2*k].right_one_cnt=f[2*k].cnt;
f[2*k].left_zero_cnt=0;
f[2*k].mid_zero_cnt=0;
f[2*k].right_zero_cnt=0;
f[2*k].concrete_one=f[2*k].cnt;
f[2*k].concrete_zero=0;
f[2*k+1].sum=f[2*k+1].cnt;
f[2*k+1].left_one_cnt=f[2*k+1].cnt;
f[2*k+1].mid_one_cnt=f[2*k+1].cnt;
f[2*k+1].right_one_cnt=f[2*k+1].cnt;
f[2*k+1].left_zero_cnt=0;
f[2*k+1].mid_zero_cnt=0;
f[2*k+1].right_zero_cnt=0;
f[2*k+1].concrete_one=f[2*k+1].cnt;
f[2*k+1].concrete_zero=0;
}
f[k].color=-1;
}
if(f[k].reverse!=0){
f[2*k].sum=f[2*k].cnt-f[2*k].sum;
swap(f[2*k].left_one_cnt,f[2*k].left_zero_cnt);
swap(f[2*k].mid_one_cnt,f[2*k].mid_zero_cnt);
swap(f[2*k].right_one_cnt,f[2*k].right_zero_cnt);
swap(f[2*k].concrete_one,f[2*k].concrete_zero);
f[2*k+1].sum=f[2*k+1].cnt-f[2*k+1].sum;
swap(f[2*k+1].left_one_cnt,f[2*k+1].left_zero_cnt);
swap(f[2*k+1].mid_one_cnt,f[2*k+1].mid_zero_cnt);
swap(f[2*k+1].right_one_cnt,f[2*k+1].right_zero_cnt);
swap(f[2*k+1].concrete_one,f[2*k+1].concrete_zero);
f[2*k].reverse^=f[k].reverse;
f[2*k+1].reverse^=f[k].reverse;
f[k].reverse=0;
}
//下面应该是pushup的内容
// f[k].concrete_zero=max(f[2*k].left_zero_cnt,max(f[2*k].mid_zero_cnt,max(f[2*k].right_zero_cnt+f[2*k+1].left_zero_cnt,max(f[2*k+1].mid_zero_cnt,f[2*k+1].right_zero_cnt))));
// f[k].concrete_one=max(f[2*k].left_one_cnt,max(f[2*k].mid_one_cnt,max(f[2*k].right_one_cnt+f[2*k+1].left_one_cnt,max(f[2*k+1].mid_one_cnt,f[2*k+1].right_one_cnt))));
}
inline void pushup(ll k){
if(f[2*k].left_one_cnt==f[2*k].cnt){
f[k].left_one_cnt=f[2*k].left_one_cnt+f[2*k+1].left_one_cnt; //左区间内全是1
} else {
f[k].left_one_cnt=f[2*k].left_one_cnt;
}
if(f[2*k].left_zero_cnt==f[2*k].cnt){
f[k].left_zero_cnt=f[2*k].left_zero_cnt+f[2*k+1].left_zero_cnt;
} else {
f[k].left_zero_cnt=f[2*k].left_zero_cnt;
}
f[k].mid_one_cnt=max(f[2*k].right_one_cnt+f[2*k+1].left_one_cnt,max(f[2*k].mid_one_cnt,f[2*k+1].mid_one_cnt)); //可能有点问题
f[k].mid_zero_cnt=max(f[2*k].right_zero_cnt+f[2*k+1].left_zero_cnt,max(f[2*k].mid_zero_cnt,f[2*k+1].mid_zero_cnt));
if(f[2*k+1].right_one_cnt==f[2*k+1].cnt){
f[k].right_one_cnt=f[2*k+1].right_one_cnt+f[2*k].right_one_cnt;
} else {
f[k].right_one_cnt=f[2*k+1].right_one_cnt;
}
if(f[2*k+1].right_zero_cnt==f[2*k+1].cnt){
f[k].right_zero_cnt=f[2*k+1].right_zero_cnt+f[2*k].right_zero_cnt;
} else {
f[k].right_zero_cnt=f[2*k+1].right_zero_cnt;
}
f[k].sum=f[2*k].sum+f[2*k+1].sum; //总共的1的个数
f[k].concrete_zero=max(f[2*k].left_zero_cnt,max(f[2*k].mid_zero_cnt,max(f[2*k].right_zero_cnt+f[2*k+1].left_zero_cnt,max(f[2*k+1].mid_zero_cnt,f[2*k+1].right_zero_cnt))));
f[k].concrete_one=max(f[2*k].left_one_cnt,max(f[2*k].mid_one_cnt,max(f[2*k].right_one_cnt+f[2*k+1].left_one_cnt,max(f[2*k+1].mid_one_cnt,f[2*k+1].right_one_cnt))));
f[k].cnt=f[2*k].cnt+f[2*k+1].cnt;
}
void build(ll left,ll right,ll k){
if(left==right){
f[k].cnt=1;
f[k].sum=a[left];
f[k].concrete_one=a[left];
f[k].concrete_zero=1-a[left];
f[k].left_one_cnt=a[left];
f[k].left_zero_cnt=1-a[left];
f[k].mid_one_cnt=a[left];
f[k].mid_zero_cnt=1-a[left];
f[k].right_one_cnt=a[left];
f[k].right_zero_cnt=1-a[left];
return;
}
ll mid=(left+right)/2;
build(left,mid,2*k);
build(mid+1,right,2*k+1);
pushup(k);
}
题目大意:有一棵树,每个点都有点权,查询树上一条路径上的点的权值的k次方和,其中1<=k<=50
这个题我是从树剖的题单找过去的,所以我自然往树剖上想的。如果是查询某确定次方和的话,就是树剖裸题,注意到本题k范围比较小,所以我们可以预处理出每个点的点权的1-50次方,然后查询的时候直接用树剖转换为区间问题,用树状数组查相应的前缀和,作差就是结果了。对于本题来说,有一个坑点:由于要取模,所以最后作差可能出现负数,所以我们应该作差之后+mod,然后再%mod,这样才保证了结果的正确性,不这么做会爆零。另外,这个题目数据是3e5,我们的算法复杂度有两个log,所以需要读写优化+内联进行卡常才能通过。
代码不放了,基本上树剖的代码长得都差不多,主要是题目建模(但这是个裸题)。
另外,这个题正解并不是树剖,以后有机会会补上正解。
做了一些题之后,明白了,这种有结合律并且还不带修改的信息,完全可以倍增维护,复杂度降低一个\(log\)。
这是一道模板题,让用树剖维护树上的边权信息。
这道题做法是把边权转化为点权,一种转化方法是把边权转化为它较深的端点的点权。路径修改边权时,即修改点权,但对于路径来说,lca的点权不能修改,因为显然lca的点权代表的边权不在路径上。在查询边权时,即查询边的较深的端点的点权就好了。捎带说一句,之前我是没用树剖求过lca的,但是这里我知道了,树剖两个点最后跳到同一条重链的时候,深度小的那个就是原来那两个点的lca。
代码不放了,几乎就是树剖的模板,只改了上述几个地方。
题目大意:一棵树,你需要修改某条路径上的点权为同一个数,可以指定某个点为根,可以查询以某个点为根的子树中的点权的最小值。
换根我们现在是第二次碰到了,思考方法主要是考虑使用新根和使用老根之间的变与不变。
首先,修改操作不管换不换根都是不变的,因为他是路径修改而不是子树修改。
换根之后,一种想法是,把信息都修改了,但是太慢了。
其实不用改它们的信息,我们最开始默认按照1号为根进行树剖预处理,然后在查询的时候,根据查询的点和新的根的关系,确定查询的区间就好了。
根据要查询的结点和\(newroot\)的逻辑关系来分类:
第一种情况,更简洁的表述是\(i\)不是\(newroot\)的祖先,这个是容易判断的,可以利用树剖\(lca\),看\(i和newroot\)的最近公共祖先。注意先判断第四种情况,因为那时候\(lca\)和这种情况是一样的。
\(i\)是\(newroot\)本尊,则直接按照1号根查询全局最小值就好了。
\(i\)在以\(newroot\) 为根的子树中,则换根前后查询结果不变。
\(i\)在\(newroot\)和\(oldroot\)这条链上,则可以画图看一下,按照以\(newroot\)为根,适当把边进行一下旋转,能够发现,\(i\)的查询范围,就是整个树-\(i\)的包含\(newroot\) 那棵子树。记那棵子树的根为\(y\),则\(y\)是换根之前\(i\)的儿子,且\(y\)的所有后代里面有\(newroot\) 。现在的问题是如何快速查询这个补集的信息呢?思考一下发现,\(y\)及其子树的dfs序的区间是\([v[y].id,v[y].id+v[y].size-1]\) ,所以我们只需要查询\([1,v[y].id-1]\) 和\([v[y].id+v[y].size,n]\) 就可以了。
本题主要在于如何合理利用树剖,代码的主要部分还是树剖的模板,所以就不放了。关于代码细节,这道题我的\(pushdown\)函数写错了,很致命,要知道线段树里面最核心的部分就是\(pushdown\)了,所以一定要谨慎思考;另外树剖预处理部分的dfs也写错了,不过静态debug发现了;然后我写的树上倍增居然也错了,好像是移位数太多了。不得不说这个题数据很水,犯了三个致命错误,居然可以得到90分。
题目大意:有一棵树,最开始树上的点都没有标记,我们可以对树进行两种操作:对一个结点打标记;查询离某个结点最近的打标记的祖先结点。
我们先考虑链上的做法:一个显然的暴力是每次询问的时候就直接往前找,但会超时。我们考虑维护区间的信息来加速,即在标记了一个结点之后,储存某个区间内的一些额外信息,比如某个区间\([l,r]\)内的深度最深的打标记的点。考虑一个经典的长度为8的链的例子,建立线段树,假设对5号点进行标记,那么\([1,8],[5,8],[5,6],[5,5]\)这四个区间维护的区间内打标记最深点的深度都有可能被刷新,这个可以在进行单点修改之后回溯时用\(pushup\)函数来实现。查询的话,比如查询7号点,那么可以直接查区间\([1,7]\)的那个信息,只要在查询时对各个极大区间取\(max\)就好了。这样的话,树上的也很明了了:修改一个点时就用\(dfs\)序来刷新信息,查询一个点时,即查询该点到根这条路径上的最深的打标记的点。问题解决!
未完待续
题目大意:有一棵树,你最开始在某个结点上,树上每个结点有点权,每次删除操作,你都可以把离你所在的结点距离不超过\(k\)的结点的点权都修改为0,然后你可以选择将自己移动一步或者不移动,其他节点的点权必须沿着向你的方向的简单路径转移,求需要多少次删除操作才能把树上的点的点权全部变成0?
这道题看起来是一道数据结构题。这道题的规则花里胡哨的,我们一个一个看一下,想想怎么搞。首先,把所有离某个结点距离不超过某个数\(k\)的结点的点权全部变成0,不是传统的子树修改或者区间修改,直接暴力修改的话就是迭代加深,如果是把初始点记为根节点的话,其实就是层次遍历,如果想比较快的修改的话,可能考虑树剖,那样的话就得想怎么把对应的区间的编号算出来,由于刚才的层次遍历的启发,我想到一种定义\(bfs\) 序,然后建立线段树维护的方法(结果查了一下天哪真的有\(bfs\)序这种东西)。这样的话,删除的区间在\(bfs\)序上就比较容易求出来,一次修改复杂度是\(O(log^2n)\) 。
其次,对于换根操作(有了刚才的思考,我觉得把这个操作叫做换根并不为过),动手画了几个图之后,没有想出来怎么表示今后的修改区间。
最后,对于移动操作,如果根不换的话,就是把儿子的权值加到父亲上,然后儿子的权值清零。
题目大意:给定一个数列,给定查询区间[l,r],求这段区间内有多少不一样的数。
虽然这个题正解不是莫队,离线也很容易被卡,但是本人感觉这个题可以作为一个莫队算法的入门题目。莫队算法步骤大概是:分块,询问排序,按新顺序动态调整区间与答案进行回答,最后按照原序输出询问结果。关于莫队算法究竟如何操作,我已经写在笔记本上了,下面就只贴上代码了。
//TLE 58pts
#include <cstdio>
#include <cstdlib>
#include <algorithm>
#include <cmath>
#define ll long long
using namespace std;
typedef struct{
ll l,r,order,blocknum;
}Query;
const int N=1e6+9;
Query q[N];
ll n,m,a[N],blocksize,ans[N],cnt[N],now;
void add(ll p);
void sub(ll p);
int cmp(const void *a,const void *b);
int main(){
ll left=1,right=0;
scanf("%lld",&n);
for(int i=1;i<=n;i++){
scanf("%lld",&a[i]);
}
scanf("%lld",&m);
blocksize=sqrt(n);
for(int i=1;i<=m;i++){
scanf("%lld %lld",&q[i].l,&q[i].r);
q[i].order=i;
q[i].blocknum=q[i].l/blocksize;
}
qsort(q+1,m,sizeof(Query),cmp);
// for(int i=1;i<=m;i++){
// printf("%lld %lld\n",q[i].l,q[i].r);
// }
for(int i=1;i<=m;i++){
while(left<q[i].l){
sub(left);
left++;
}
while(left>q[i].l){
left--;
add(left);
}
while(right<q[i].r){
right++;
add(right);
}
while(right>q[i].r){
sub(right);
right--;
}
ans[q[i].order]=now;
}
for(int i=1;i<=m;i++){
printf("%lld\n",ans[i]);
}
return 0;
}
void add(ll p){
if(cnt[a[p]]==0){
now++;
}
cnt[a[p]]++;
}
void sub(ll p){
cnt[a[p]]--;
if(cnt[a[p]]==0){
now--;
}
}
int cmp(const void *a,const void *b){
Query *p1=(Query*)a;
Query *p2=(Query*)b;
if(p1->blocknum==p2->blocknum){
return p1->r-p2->r;
}
return p1->blocknum-p2->blocknum;
}
dbp我现在只会写AVL树,并且只写过了普通平衡树。等到我学会了splay,写过了文艺和二逼之后,再回来补上。
题意:在树中找一条路径,使得这条路径的边权的异或和最大。
预处理每个节点到根的异或和是套路。然后,考虑枚举两个点,\(O(1)\)查询其异或和,这样的话总的复杂度是\(O(n^2)\)。为了提高效率,我们要利用数据结构的力量。根据经验,解决最大异或值的问题的时候一般是从高位往下找,且一般会使用trie树来实现这个贪心。所以我们考虑用trie做这个题。
为了保证结果正确,我们建trie树的时候,要让所有的数的二进制表达长度一样。然后就是把trie建出来,然后贪心就好了。
代码如下:
#include <bits/stdc++.h>
#define ll long long
#define INF 999999999999
using namespace std;
const int N=1e5+9;
typedef struct{
ll to,nxt,weight;
}Edge;
typedef struct ss{
ll value;
struct ss *child[3];
bool isend;
ll seq;
}Node;
typedef Node* Nodeptr;
Edge edge[2*N];
ll head[N],cnt,n,val[N],component[N];
void add(ll x,ll y,ll z);
ll query(ll v,Nodeptr root);
void dfs(ll now,ll fa,ll sum);
Nodeptr insert(ll v,Nodeptr root,ll seq);
int main(){
ll x,y,z,now,ans=0;
Nodeptr root=NULL;
scanf("%lld",&n);
for(int i=0;i<=n;i++){
head[i]=-1;
}
for(int i=1;i<n;i++){
scanf("%lld %lld %lld",&x,&y,&z);
add(x,y,z);
add(y,x,z);
}
dfs(1,0,0);
for(int i=1;i<=n;i++){
root=insert(val[i],root,i);
}
for(int i=1;i<=n;i++){
now=query(val[i],root);
ans=max(ans,now);
}
printf("%lld\n",ans);
return 0;
}
ll query(ll v,Nodeptr root){
ll tmp=v,cnt=0,ret=0;
Nodeptr p;
p=root;
for(int i=0;i<=33;i++){
component[i]=0;
}
while(tmp){
if(tmp&1){
component[cnt++]=1;
} else {
component[cnt++]=0;
}
tmp=tmp>>1;
}
for(int i=33;i>=0;i--){
if(p->child[component[i]^1]!=NULL){
p=p->child[component[i]^1];
} else {
p=p->child[component[i]];
}
}
if(p->isend){
ret=v^(val[p->seq]);
}
return ret;
}
Nodeptr insert(ll v,Nodeptr root,ll seq){
ll tmp=v,cnt=0;
Nodeptr p;
if(root==NULL){
root=(Nodeptr)malloc(sizeof(Node));
root->value=0;
root->isend=false;
for(int i=0;i<3;i++){
root->child[i]=NULL;
}
}
p=root;
for(int i=0;i<=33;i++){
component[i]=0;
}
while(tmp){
if(tmp&1){
component[cnt++]=1;
} else {
component[cnt++]=0;
}
tmp=tmp>>1;
}
for(int i=33;i>=0;i--){ //从高位到低位开始插入,长度对齐
if(p->child[component[i]]!=NULL){
p=p->child[component[i]];
} else {
p->child[component[i]]=(Nodeptr)malloc(sizeof(Node));
p=p->child[component[i]];
p->value=component[i];
p->isend=false;
for(int j=0;j<3;j++){
p->child[j]=NULL;
}
}
}
p->isend=true;
p->seq=seq;
return root;
}
void dfs(ll now,ll fa,ll sum){
val[now]=sum;
for(ll i=head[now];i>=0;i=edge[i].nxt){
if(edge[i].to!=fa){
dfs(edge[i].to,now,sum^edge[i].weight);
}
}
}
void add(ll x,ll y,ll z){
edge[cnt].to=y;
edge[cnt].weight=z;
edge[cnt].nxt=head[x];
head[x]=cnt++;
}
题目大意:有一些变量相等或者不等的限制条件,现在判定这些条件是否能同时满足。
相等是一种等价关系,所以我们可以考虑用并查集来表示这种二元关系。不等,意味着两个元素不应该在一个集合中,我们只需要调用together方法进行判定就好了。注意,我们需要先把所有的相等关系都弄好之后,才能处理不等关系,否则有可能出现最开始两个元素不在一个集合,但是后来又被合并的情况。
本题多组数据,注意清零。为了无bug,建议完全清零。
#include <bits/stdc++.h>
#define ll long long
using namespace std;
const int N=1e6+9;
unordered_map<int,int> table; //假的hash表
int f[N],t,n,cnt;
typedef struct{
int i,j,e;
}Node;
Node a[N];
int find(int x);
void un(int x,int y);
bool together(int x,int y);
int main(){
scanf("%d",&t);
while(t--){
bool flag=true;
table.erase(table.begin(),table.end()); //先清空map
cnt=0;
scanf("%d",&n);
for(int k=1;k<=n;k++){
f[k]=k;
}
for(int k=1;k<=n;k++){
scanf("%d %d %d",&a[k].i,&a[k].j,&a[k].e);
}
sort(a+1,a+n+1); //1放在前面,0放在后面
for(int k=1;k<=n;k++){
if(table.find(a[k].i)==table.end()){
table[a[k].i]=++cnt;
}
if(table.find(a[k].j)==table.end()){
table[a[k].j]=++cnt;
}
if(a[k].e==1){
un(table[a[k].i],table[a[k].j]);
} else {
if(together(table[a[k].i],table[a[k].j])){
flag=false;
}
}
}
if(flag){
printf("YES\n");
} else {
printf("NO\n");
}
}
return 0;
}
inline bool operator <(const Node &a,const Node &b){
if(a.e>b.e){
return true;
} else {
return false;
}
}
inline bool together(int x,int y){
return find(x)==find(y);
}
int find(int x){
if(x==f[x]) return x;
return f[x]=find(f[x]);
}
void un(int x,int y){
x=find(x);
y=find(y);
f[x]=y;
}
原文:https://www.cnblogs.com/BUAA-Wander/p/13311305.html