【题解】2020 年电子科技大学 ACM-ICPC 暑假前集训 - 数据结构

比赛地址

A - 红魔族首屈一指の恶魔使


在最左端和最右端添加括号,保证中途的括号深度不小于零且左右括号数量相等
\(O(n)\)

#include <cstdio>
int T,n,dep,ans;
int main()
{
	scanf("%d",&T);
	while(T--)
	{
		scanf("%d",&n);
		dep=0;
		ans=0;
		while(n--){
			char ch;
			do{ch=getchar();}while(ch!='('&&ch!=')');
			if (ch=='('){
				dep++;
			}else{
				dep--;
			}
			if (dep<0){
				ans++;
				dep=0;
			}
		}
		ans+=dep;
		printf("%d\n",ans);
	}
}

B - 雪菜的新家

并查集,基环外向树
可以证明打破基环上的罐子可以使得整个连通块的罐子都被打破
\(O(n)\)

#include <cstdio>
#include <cstring>
int ans=0,n,a,b,fa[1000001],use[1000001];
int find(int x){
    if (x==fa[x]) return x;
    return fa[x]=find(fa[x]);
}
int main(){
	memset(use,0,sizeof(use));
	scanf("%d",&n);
	for (int i=1;i<=n;i++) fa[i]=i;
	for (int i=1;i<=n;i++){
		scanf("%d",&a);
		b=find(a);
		if (i!=b)
			fa[i]=a;
	}
	for (int i=1;i<=n;i++){
		b=find(i);
		if (!use[b]){
			use[b]=1;
			ans++;
		}
	}
	printf("%d",ans); 
}

C - 我,不是说了能力要平均值么

逆序对
\(O(nlogn)\)

#include <cstdio>
#define ll long long 
ll n,k,ans,a[200001],s[200001],p[200001],tot,tmp[200001];
ll merge(ll l,ll r){
	if (l>=r) return 0;
	ll mid=(l+r)/2;
	ll ans=0;
	ans+=merge(l,mid);
	ans+=merge(mid+1,r);
	ll pl=l,pr=mid+1,ptmp=0;
	while(pl<=mid&&pr<=r){
		if (p[pl]>=p[pr]){
			tmp[ptmp++]=p[pr++];
			ans+=mid-pl+1;
		}else{
			tmp[ptmp++]=p[pl++];
		}
	}
	while(pl<=mid) tmp[ptmp++]=p[pl++];
	while(pr<=r) tmp[ptmp++]=p[pr++];
	for (ll i=0;i<ptmp;i++) {
		p[l+i]=tmp[i];
	}
	return ans;
}
ll gcd(ll x,ll y){
	return y?gcd(y,x%y):x;
}
void out(ll x,ll y){
	if (x==0){
		printf("0/1\n");
		return;
	}
	ll g=gcd(x,y);
	x/=g,y/=g;
	printf("%lld/%lld",x,y);
}
int main()
{
	scanf("%lld%lld",&n,&k);
	s[0]=0;tot=0;ans=0;
	for (ll i=1;i<=n;i++){
		scanf("%lld",&a[i]);
		s[i]=a[i]+s[i-1];
		p[i]=s[i]-k*i;
		tot+=i;
	}
	ans=merge(0,n);
	out(ans,tot);
}

D - 利姆露的魔法序列


将整个栈的合法性问题分解为左右两个部分,判断中途的括号深度不小于零且左右括号数量相等
\(O(n)\)

#include <cstdio>
#include <iostream>
using namespace std;
const int N=1000010;
char _g[2*N+1];
int _ldpc[2*N+1],_rdpc[2*N+1];
int n,ldp=0,rdp=0,pt=0,lmax=0,rmax=0,lmin=0,rmin=0;
int main()
{
	char *g=_g+N;
	int *ldpc=_ldpc+N,*rdpc=_rdpc+N;
	ldpc[0]=rdpc[0]=1;
	scanf("%d",&n);
	while(n--){
		char op;
		do{op=getchar();}while(!(op=='R'||op=='L'||op=='('||op==')'||(op>='a'&&op<='z')));
		switch(op){
			case 'R':
				if (g[pt]=='('){ldp++;ldpc[ldp]++;}
				if (g[pt]==')'){ldp--;ldpc[ldp]++;}
				if (g[pt+1]==')'){rdpc[rdp]--;rdp--;}
				if (g[pt+1]=='('){rdpc[rdp]--;rdp++;}
				pt++;
				break;
			case 'L':
				if (g[pt-1]=='('){ldpc[ldp]--;ldp--;}
				if (g[pt-1]==')'){ldpc[ldp]--;ldp++;}
				if (g[pt]==')'){rdp++;rdpc[rdp]++;}
				if (g[pt]=='('){rdp--;rdpc[rdp]++;}
				pt--;
				break;
			default:
				g[pt]=op;
				break;
		}
		if (ldpc[lmax+1]>0) lmax++;
		if (ldpc[lmax]==0) lmax--;
		if (ldpc[lmin-1]>0) lmin--;
		if (ldpc[lmin]==0) lmin++;
		if (rdpc[rmax+1]>0) rmax++;
		if (rdpc[rmax]==0) rmax--;
		if (rdpc[rmin-1]>0) rmin--;
		if (rdpc[rmin]==0) rmin++;
		if (ldp+(g[pt]=='(')-(g[pt]==')')-rdp==0&&lmin==0&&rmin==0){
			printf("%d ",max(rmax,lmax));
		}else{
			printf("-1 ");
		}
	}
	
}

E - 哪个男孩不想挖最多的矿呢

最大公约数,ST表
维护正方形区域的最大公约数,\(4\)个边长为\(sz\)的区域可以合并为\(1\)个边长为\(2*sz-1\)的区域,然后就可以使用倍增法进行统计了
\(O(n^2logn)\)

#include <cstdio>
#include <iostream>
#include <cstdlib>
using namespace std;
int x[501][501],maxsz,n,m,src[501][501],dst[501][501];
int gcd(int a,int b){
    return b?gcd(b,a%b):a;
}
int main(){
    if (1){
        scanf("%d%d",&n,&m);
        for (int i=1;i<=n;i++)
            for (int j=1;j<=m;j++)
                scanf("%d",&x[i][j]);
    }else{
        n=m=10;
        for (int i=1;i<=n;i++)
            for (int j=1;j<=m;j++)
                x[i][j]=rand()+1;
    }
    maxsz=min(n,m);
    for (int i=2;i<=n-1;i++)
        for (int j=2;j<=m-1;j++)
            for (int a=-1;a<=1;a++)
                for (int b=-1;b<=1;b++)
                    src[i][j]=gcd(src[i][j],x[i+a][j+b]);
    int ans=1,cnt=0,anscnt=n*m;
    for (int i=2;i<=n-1;i++)
        for (int j=2;j<=m-1;j++)
            if (src[i][j]%x[i][j]==0)
                cnt++;
    if (cnt>0) ans=3,anscnt=cnt;
    for (int sz=5;cnt>0&&sz<=maxsz;sz=sz*2-1){
        cnt=0;
        int d=sz/4;
        for (int i=1+sz/2;i<=n-sz/2;i++)
            for (int j=1+sz/2;j<=m-sz/2;j++){
                dst[i][j]=src[i-d][j-d];
                dst[i][j]=gcd(dst[i][j],src[i-d][j+d]);
                dst[i][j]=gcd(dst[i][j],src[i+d][j-d]);
                dst[i][j]=gcd(dst[i][j],src[i+d][j+d]);
                if (dst[i][j]%x[i][j]==0) {
                    cnt++;
                }
            }
        if (cnt>0){
            for (int i=1+sz/2;i<=n-sz/2;i++)
            {
                for (int j=1+sz/2;j<=m-sz/2;j++)
                {
                    src[i][j]=dst[i][j];
                }
            }
            ans=sz;
            anscnt=cnt;
        }
    }
    if (ans>=5){
        int preans=ans;
        for (int t=ans/2;t>=2;t/=2){
            if (ans+t<=maxsz){
                cnt=0;
                int sz=ans+t;
                int d=(sz-preans)/2;
                for (int i=1+sz/2;i<=n-sz/2;i++)
                    for (int j=1+sz/2;j<=m-sz/2;j++){
                        dst[i][j]=src[i-d][j-d];
                        dst[i][j]=gcd(dst[i][j],src[i-d][j+d]);
                        dst[i][j]=gcd(dst[i][j],src[i+d][j-d]);
                        dst[i][j]=gcd(dst[i][j],src[i+d][j+d]);
                        if (dst[i][j]%x[i][j]==0) cnt++;
                    }
                if (cnt>0){
                    ans=ans+t;
                    anscnt=cnt;
                }
            }
        }
    }
    printf("%d\n%d\n",ans*ans,anscnt);
}

F - 赏金

树状数组,set
可以证明整个环可以从一个位置拆开而不影响最后结果,拆开后就变成强制在线的田忌赛马问题了
需要实现的功能有:
1 插入一个数
2 删除一个数
3 查询大于\(k\)的第一个数
使用树状数组或set实现即可
\(O(nlogn)\)

#include <cstdio>
#include <queue>
#include <cstdlib>
#include <algorithm>
#include <set>
using namespace std;
const int N=500010;
queue<int> q[N];
set<int> pset;
int m[N*2],msz=0,ra;
int reg[N],n,w[N],pc[N],pw[N],ans=0;
int c[N*2+1],nn;
int lowbit(int x){
	return x&(-x);
}
void add(int x,int v){
	while(x<=nn){
		c[x]+=v;
		x+=lowbit(x);
	}
}
int sum(int x){
	int ans=0;
	while(x){
		ans+=c[x];
		x-=lowbit(x);
	}
	return ans;
}
int find_kk(int x){
	int ans=0,cnt=0;
	for(int i=20;i>=0;i--){
		ans+=(1<<i);
		if(ans>nn||cnt+c[ans]>=x){
			ans-=(1<<i);
		}else cnt+=c[ans];
	}
	return ans+1;
}
int main(){
  if (0){
    n=500000;
    for (int i=1;i<=n;i++) pc[i]=rand()%n+1;
    for (int i=1;i<=n;i++) {
      do{ra=rand();}while(pset.count(ra));w[i]=ra;}
    for (int i=1;i<=n;i++) {
      do{ra=rand();}while(pset.count(ra));pw[i]=ra;}
  }else{
    scanf("%d",&n);
    for (int i=1;i<=n;i++) scanf("%d",&pc[i]);
    for (int i=1;i<=n;i++) scanf("%d",&w[i]);
    for (int i=1;i<=n;i++) scanf("%d",&pw[i]);
  }
  for (int i=1;i<=n;i++){
    m[msz++]=w[i];
    m[msz++]=pw[i];
  }
  sort(m,m+msz);
  for (int i=1;i<=n;i++){
    w[i]=lower_bound(m,m+msz,w[i])-m+1;
    pw[i]=lower_bound(m,m+msz,pw[i])-m+1;
  }
	for (int i=1;i<=n;i++) q[pc[i]].push(pw[i]);
	int pcnt=0,ptr=1,ptot=0;
	while(pcnt<n){
		if (!reg[ptr]){
			ptot+=q[ptr].size();
		}
		if (!reg[ptr]&&ptot>0){
			ptot--;
			pcnt++;
			reg[ptr]=1;
		}
		ptr=(ptr)%n+1;
	}
  nn=2*n+1;
  add(nn,1);
	for (int i=0;i<n;i++){
		while(!q[ptr].empty()){
			add(q[ptr].front(),1);
			q[ptr].pop();
		}
    int x=w[ptr];
		int p=find_kk(sum(x)+1);
		if (p!=nn){
			ans++;
		}else{
			p=find_kk(sum(0)+1);
		}
		add(p,-1);
		ptr=(ptr)%n+1;
	}
	printf("%d\n",ans);
}

G - 红魔族首屈一指の鞋店老板之子

树上差分,可并堆
可以离线用树上差分做,也可以在线用可并堆做
\(O(nlogn)\)

#include <cstdio>
#define ll long long
const ll N=200010;
struct E{
    ll next,to,len;
}e[2*N];
ll sz=0,head[N],w[N],dep[N],fa[N][20],a,b,n,l,ans[N],tag[N];
void insert(ll a,ll b,ll w){
    sz++;
    e[sz].next=head[a];
    head[a]=sz;
    e[sz].to=b;
    e[sz].len=w;
}
ll unreach(ll x){
    ll y=x;
    for (ll i=19;i>=0;i--)
        if (dep[x]-dep[fa[y][i]]<=l)
            y=fa[y][i];
    return fa[y][0];
}
void dfs(ll x,ll f){
    for (ll i=1;i<20;i++) fa[x][i]=fa[fa[x][i-1]][i-1];
    tag[x]+=1;
    tag[unreach(x)]-=1;
    for (ll i=head[x];i;i=e[i].next)
        if (e[i].to!=f){
            dep[e[i].to]=dep[x]+e[i].len;
            fa[e[i].to][0]=x;
            dfs(e[i].to,x);
        }
}
ll solve(ll x,ll f){
    ll sum=0;
    for (ll i=head[x];i;i=e[i].next)
        if (e[i].to!=f){
            sum+=solve(e[i].to,x);
        }
    sum+=tag[x];
    ans[x]=sum;
    return sum;
}
int main(){
    scanf("%lld%lld",&n,&l);
    for (ll i=2;i<=n;i++){
        scanf("%lld%lld",&a,&b);
        insert(i,a,b);insert(a,i,b);
    }
    dfs(1,1);
    solve(1,1);
    for (ll i=1;i<=n;i++){
        printf("%lld\n",ans[i]);
    }
}

H - 綾波的游戏I

分块
对每一块维护一个桶,记录块内每个数字出现的次数即可
\(O(n\sqrt{n})\)

#include <cstdio>
#include <cmath>
const int N=100001,K=318;
int f[K][N],l[K],r[K],tag[K],h,n,m,k,w[N],pos[N],a,b,c,d;
void add(int a,int b,int c){
    f[a][w[b]]--;
    w[b]=(w[b]+c)%k;
    f[a][w[b]]++;
}
void pushdown(int x){
    for (int i=l[x];i<=r[x];i++) add(x,i,tag[x]);
    tag[x]=0;
}
int solve(int a,int b,int c,int d){
    int p=pos[a],q=pos[b],ans=0;
    if (p==q){
        pushdown(p);
        for (int i=a;i<=b;i++){
            if ((w[i]-c)%k==0) ans++;
            add(p,i,d);
        }
    }else{
        pushdown(p);
        pushdown(q);
        for (int i=p+1;i<=q-1;i++){
            ans+=f[i][(c-tag[i]+k)%k];
            tag[i]=(tag[i]+d)%k;
        }
        for (int i=a;i<=r[p];i++){
            if ((w[i]-c)%k==0) ans++;
            add(p,i,d);
        }
        for (int i=l[q];i<=b;i++){
            if ((w[i]-c)%k==0) ans++;
            add(q,i,d);
        }
    }
    return ans;
}
int main(){
    scanf("%d%d",&n,&k);
    for (int i=1;i<=n;i++){
        scanf("%d",&w[i]);
        w[i]=(w[i]%k+k)%k;
    }
    scanf("%d",&m);
    h=sqrt(n);
    for (int i=1;i<=h;i++)
        l[i]=(i-1)*h+1,r[i]=i*h;
    if (r[h]<n)
        h++,l[h]=r[h-1]+1,r[h]=n;
    for (int i=1;i<=h;i++)
        for (int j=l[i];j<=r[i];j++){
            pos[j]=i;
            f[i][w[j]]++;
        }
    for (int i=1;i<=m;i++){
        scanf("%d%d%d%d",&a,&b,&c,&d);
        c=(c%k+k)%k,d=(d%k+k)%k;
        int t=solve(a,b,c,d);
        printf("%d\n",t);
    }
}

I - 利姆露与魔法元素

带权并查集
维护并查集的同时维护元素和根的相对关系即可
\(O(n)\)

#include <cstdio>
const int K=1000001,N=100001;
int n,k,m,f[K][3],op[K][3],del[K],a,b,ans[K],anscnt=0;;
char buf[4];
int pre[N],rela[N];
int find(int x){
    if (x==pre[x]) return x;
    int t=pre[x];
    pre[x]=find(pre[x]);
    rela[x]=(rela[x]+rela[t])%3;
    return pre[x];
}
int query(int x,int y){
    if (find(x)==find(y))
        return (rela[x]-rela[y]+3)%3;
    else
        return 3;
}
void insert(int r,int x,int y){
    int fx=find(x),fy=find(y);
    if (fx!=fy){
        pre[fx]=fy;
        rela[fx]=(rela[y]-rela[x]+r+3)%3;
    }
}
int main(){
    scanf("%d%d%d",&n,&k,&m);
    for (int i=1;i<=n;i++) pre[i]=i;
    for(int i=1;i<=k;i++){
        scanf("%d%d%d",&f[i][0],&f[i][1],&f[i][2]);
        f[i][0]--;
    }
    for(int i=1;i<=m;i++){
        scanf("%s",buf);
        if (buf[0]=='q'){
            scanf("%d%d",&a,&b);
            op[i][0]=0,op[i][1]=a,op[i][2]=b;
        }else{
            scanf("%d",&a);
            op[i][0]=1,op[i][1]=a,del[a]=1;
        }
    }
    for (int i=1;i<=k;i++)
        if (!del[i]){
            insert(f[i][0],f[i][1],f[i][2]);
        }
    for (int i=m;i>=1;i--){
        if (op[i][0]==0){
            int t=query(op[i][1],op[i][2]);
            ans[++anscnt]=t;
        }else{
            int t=op[i][1];
            insert(f[t][0],f[t][1],f[t][2]);
        }
    }
    for (int i=anscnt;i>=1;i--){
        printf("%d\n",ans[i]);
    }
}

J - 百分之 99 的人竟然不知道两个数字之间还能这么算

可持久化trie树,点分治
边权树上路径的统计问题比较简单,使用前缀和就能解决
点权树上路径的统计问题还是有点麻烦的,需要用到点分治
对于每个分治节点,统计各个分支向下走可以得到的异或和,并将其存到trie树中,再将分治节点与其两个不同分支向下走的结果组合起来就是一条通过根节点的路径了
为了避免合并两个相同分支的情况,对于每个分支需要通过查询trie树的上一个版本来与之前的所有分支进行组合
\(O(nlogn)\)

#include<bits/stdc++.h>
#define N 100010
using namespace std;

struct edge{int u,next;}e[N*2]; int head[N],Use=0;
void ins(int x,int y){Use++;e[Use].u=y;e[Use].next=head[x];head[x]=Use;}

int w[N],vis[N],siz[N],minn,minid,l,r,n,dbg=0;
long long ans=0;
int bit(int a,int b){
    return (a>>b)&1;
}
void dfssiz(int x,int fa){
    siz[x]=1;
    for(int i=head[x];i;i=e[i].next)
    if(e[i].u!=fa&&vis[e[i].u]==0){
        dfssiz(e[i].u,x);
        siz[x]+=siz[e[i].u];
    }
}
void dfsmax(int x,int fa,int fasiz){
    int maxn=fasiz-siz[x];
    for(int i=head[x];i;i=e[i].next)
    if(e[i].u!=fa&&vis[e[i].u]==0){
        dfsmax(e[i].u,x,fasiz);
        maxn=max(maxn,siz[e[i].u]);
    }
    if(maxn<minn) minn=maxn,minid=x;
}
int makeroot(int x){
    dfssiz(x,0);
    minn=n; minid=0;
    dfsmax(x,0,siz[x]);
    return minid;
}
struct trie{
    int a[2];
    int sz[2];
}a[N*32];int root[N],use=0;
void tadd(int &x,int zhi,int wei){
    if(wei<0) return;
    a[++use]=a[x]; x=use; int hh=0;
    if((1<<wei)&zhi) hh=1;
    a[x].sz[hh]++;
    tadd(a[x].a[hh],zhi,wei-1);
}
int tquery(int x,int zhi,int wei,int lim){
    if(wei<0||x==0) return 0;
    int t=tquery(a[x].a[bit(zhi^lim,wei)],zhi,wei-1,lim);
    if (bit(lim,wei)) t+=a[x].sz[bit(zhi,wei)];
    return t;
}
void dfsdis(int x,int fa,int hh,int cnt){
    hh^=w[x];
    tadd(root[cnt],hh,31);
    for(int i=head[x];i;i=e[i].next)
    if(e[i].u!=fa&&vis[e[i].u]==0){
        dfsdis(e[i].u,x,hh,cnt);
    }
}
void query(int x,int fa,int hh,int cnt){
    hh^=w[x];
    int now=tquery(root[cnt-1],hh,31,r+1)-tquery(root[cnt-1],hh,31,l);
    ans+=now;
    if (l<=hh&&hh<=r) ans++;
    for(int i=head[x];i;i=e[i].next)
    if(e[i].u!=fa&&vis[e[i].u]==0){
        query(e[i].u,x,hh,cnt);
    }
}
void calc(int x){
    int cnt=0;
    for(int i=head[x];i;i=e[i].next)
    if(vis[e[i].u]==0){
        cnt++; root[cnt]=root[cnt-1];
        dfsdis(e[i].u,x,0,cnt);
    }
    cnt=0;
    for(int i=head[x];i;i=e[i].next)
    if(vis[e[i].u]==0){
        cnt++;
        query(e[i].u,x,w[x],cnt);
    }
    use=0;
    for (int i=0;i<=cnt;i++) root[i]=0;
}
void dfs(int x){
    x=makeroot(x); vis[x]=1;
    calc(x);
    for(int i=head[x];i;i=e[i].next)
    if(vis[e[i].u]==0) dfs(e[i].u);
}
int main(){
    scanf("%d%d%d",&n,&l,&r);
    for(int i=1;i<=n;i++) scanf("%d",&w[i]);
    for(int i=1;i<n;i++){
        int x,y; scanf("%d%d",&x,&y);
        ins(x,y); ins(y,x);
    }
    dfs(1);
    printf("%lld",ans);
}

K - 简单题

优先队列
维护每张牌出现的最晚时间即可
\(O(nlogn)\)

#include <cstdio>
#include <queue>
#include <cstring>
using namespace std;
typedef pair<int,int> P;
int n,k,a[1000001],reg[1000001],l,r;
priority_queue<P,vector<P>,greater<P> > que,aque;
void updateque(){
	while(!que.empty()&&reg[que.top().second]){
		que.pop();
	}
}
int main()
{
	memset(reg,0,sizeof(reg));
	scanf("%d%d",&n,&k);
	for (int i=1;i<=n;i++){
		scanf("%d",&a[i]);
	}
	for (int i=n;i>=1;i--){
		if (!reg[a[i]]){
			que.push(P(i,a[i]));
			reg[a[i]]=1;
		}
	}
	int yes=1;
	for (int i=1;i<k;i++){
		if (!reg[i])
			yes=0;
	}
	if (!yes){
		printf("Kanade\n");
		return 0;
	}
	memset(reg,0,sizeof(reg));
	l=1;r=que.top().first;
	for (int i=l;i<=r;i++){
		aque.push(P(a[i],i));
	}
	for (int i=1;i<=k;i++){
		while(reg[aque.top().first]||aque.top().second<l){aque.pop();}
		int mina=aque.top().first;
		int minapos=aque.top().second;
		printf("%d ",mina);
		reg[mina]=1;
		l=minapos+1;
		updateque();
		if (!que.empty()){
			int newr=que.top().first;
			for (int j=r+1;j<=newr;j++){
				aque.push(P(a[j],j));
			}
			r=newr;
		}
	}
}

L - 红魔族首屈一指の大魔法师

LCT,动态树
维护动态树的连通性,使用LCT+Splay进行解决
\(O(nlogn)\)

#include <algorithm>
#include <cstdio>
using namespace std;
const int N=10001;
int n,m,fa[N],tag[N],ch[N][2],a,b;
char op[2];
int getch(int x){
    return ch[fa[x]][1]==x;
}
int isroot(int x){
    return ch[fa[x]][0]!=x&&ch[fa[x]][1]!=x;
}
void pushdown(int x){
    if(tag[x]){
        if(ch[x][0]) tag[ch[x][0]]^=1,swap(ch[ch[x][0]][0],ch[ch[x][0]][1]);
        if(ch[x][1]) tag[ch[x][1]]^=1,swap(ch[ch[x][1]][0],ch[ch[x][1]][1]);
        tag[x]=0;
    }
}
void update(int x){
    if (!isroot(x)) update(fa[x]);
    pushdown(x);
}
void rotate(int x){
    int y=fa[x],z=fa[y],chx=getch(x),chy=getch(y);
    fa[x]=z;
    if (!isroot(y)) ch[z][chy]=x;
    ch[y][chx]=ch[x][chx^1];
    fa[ch[x][chx^1]]=y;
    ch[x][chx^1]=y;
    fa[y]=x;
}
void splay(int x){
    update(x);
    for (int f=fa[x];f=fa[x],!isroot(x);rotate(x))
        if (!isroot(f)) rotate(getch(x)==getch(f)?f:x);
}
void access(int x){
    for (int f=0;x;f=x,x=fa[x])
        splay(x),ch[x][1]=f;
}
void makeroot(int x){
    access(x);splay(x);
    tag[x]^=1;
    swap(ch[x][0],ch[x][1]);
}
int find(int x){
    access(x);splay(x);
    while(ch[x][0]) x=ch[x][0];
    splay(x);
    return x;
}
void link(int x,int y){
    makeroot(x);
    fa[x]=y;
}
void cut(int x,int y){
    makeroot(x);access(y),splay(y);
    ch[y][0]=fa[x]=0;
}
int main(){
    scanf("%d%d",&n,&m);
    for (int i=1;i<=m;i++){
        scanf("%s%d%d",op,&a,&b);
        if (op[0]=='?'){
            if (find(a)==find(b))
                printf("Yes\n");
            else
                printf("No\n");
        }
        if (op[0]=='+') link(a,b);
        if (op[0]=='-') cut(a,b);
    }
}

M - 这个群里是就我不会机器学习了吗

KDTree
用KDTree查询最近点即可
\(O(nlogn)\)

#include <cstdio>
#include <algorithm>
#define ll long long
using namespace std;
const ll N=200001,M=8000000000000000001LL;
ll n,m,a,b,c,F=0,rt,to,mnd,ans,f[N];
struct P{
    ll d[2],mx[2],mn[2],id,l,r;
    ll& operator[](ll x){
        return d[x];
    }
    friend bool operator<(P a,P b){
        return a[F]<b[F];
    }
}p[N];
ll calc(ll x)
{
    ll ty=min(abs(p[x].mn[1]-p[to][1]),
        abs(p[x].mx[1]-p[to][1])),
        tx=min(abs(p[x].mn[0]-p[to][0]),
        abs(p[x].mx[0]-p[to][0])),tot=0;
    if (p[x].mn[0]<=p[to][0]&&p[to][0]<=p[x].mx[0]&&
    p[x].mn[1]<=p[to][1]&&p[to][1]<=p[x].mx[1]){
        tot=0;
    }else if (p[x].mn[0]<=p[to][0]&&p[to][0]<=p[x].mx[0]){
        tot=ty*ty;
    }else if (p[x].mn[1]<=p[to][1]&&p[to][1]<=p[x].mx[1]){
        tot=tx*tx;
    }else{
        tot=tx*tx+ty*ty;
    }
    return tot;
}
ll dis(ll x){
    ll ty=abs(p[x][1]-p[to][1]),
        tx=abs(p[x][0]-p[to][0]);
    return tx*tx+ty*ty;
}
struct KD{
    void update(ll x)
    {
        ll l=p[x].l,r=p[x].r;
        for (ll i=0;i<2;i++)
        {
            p[x].mn[i]=p[x].mx[i]=p[x][i];
            if (l) p[x].mn[i]=min(p[x].mn[i],p[l].mn[i]),p[x].mx[i]=max(p[x].mx[i],p[l].mx[i]);
            if (r) p[x].mn[i]=min(p[x].mn[i],p[r].mn[i]),p[x].mx[i]=max(p[x].mx[i],p[r].mx[i]);
        }
    }
    ll build(ll l,ll r)
    {
        ll mid=(l+r)/2;
        nth_element(p+l,p+mid,p+r+1);
        F=!F;
        if (l<mid) p[mid].l=build(l,mid-1);
        if (r>mid) p[mid].r=build(mid+1,r);
        update(mid);
        return mid; 
    }
    void query(ll x)
    {
        if (calc(x)>mnd) return;
        ll td=dis(x);
        if (td==mnd){
            if (p[x][0]>p[ans][0]||
            p[x][0]==p[ans][0]&&
            p[x][1]>p[ans][1]){
                ans=x;
            }
        }else if (0<td&&td<mnd){
            mnd=td;
            ans=x;
        }
        ll l=p[x].l,r=p[x].r,cx,cy;
        if (l&&r){
            if (calc(l)<calc(r)){
                query(l);query(r);
            }else{
                query(r);query(l);
            }
        }else{
            if (l) query(l);
            if (r) query(r);
        }
    }
}kd;
int find(ll x){
    return f[x]==x?x:f[x]=find(f[x]);
}
int main()
{
    scanf("%lld%lld",&n,&m);
    for (ll i=1;i<=n;i++) f[i]=i;
    for (ll i=1;i<=n;i++){
        scanf("%lld%lld",&p[i][0],&p[i][1]);
        p[i].id=i;
    }
    rt=kd.build(1,n);
    for (ll i=1;i<=n;i++){
        to=i;mnd=M;
        kd.query(rt);
        f[find(p[i].id)]=find(p[ans].id);
    }
    for (ll i=1;i<=m;i++){
        scanf("%lld%lld",&a,&b);
        if (find(a)==find(b)){
            printf("Kanade\n");
        }else{
            printf("Yuzuru\n");
        }
    }
}

N - 利姆露与魔法阵

树链剖分
用线段树维护重链上的点权和
\(O(nlogn)\)

#include <iostream>
#include <cstdio>
#define ll long long
using namespace std;
const ll N=100001;
struct edge{
    ll next,to;
}e[N*2];
struct node{
    ll l,r,v,tag;
}s[N*4];
ll n,m,r,cnt=0,mod=170001,op,a,b,c,head[N],sz=0;
ll siz[N],w[N],d[N],f[N],id[N],rk[N],son[N],top[N];
void insert(ll x,ll y){
    e[++sz].next=head[x];
    e[sz].to=y;
    head[x]=sz;
}
void dfs1(ll x){
    siz[x]=1,d[x]=d[f[x]]+1;
    ll t;
    for (ll v,i=head[x];i;i=e[i].next)
        if((v=e[i].to)!=f[x]){
            f[v]=x;dfs1(v);siz[x]+=siz[v];
            if (siz[son[x]]<siz[v]) son[x]=v;
        }
}
void dfs2(ll x,ll tp){
    top[x]=tp,id[x]=++cnt,rk[cnt]=x;
    if (son[x]) dfs2(son[x],tp);
    for (ll v,i=head[x];i;i=e[i].next)
        if((v=e[i].to)!=f[x]&&v!=son[x])
            dfs2(v,v);
}
ll len(ll x){
    return s[x].r-s[x].l+1;
}
void pushup(ll x){
    s[x].v=(s[x*2].v+s[x*2+1].v)%mod;
}
void pushdown(ll x){
    ll ls=x*2,rs=x*2+1,t=s[x].tag;
    s[ls].v=(s[ls].v+t*len(ls))%mod;
    s[rs].v=(s[rs].v+t*len(rs))%mod;
    s[ls].tag=(s[ls].tag+t)%mod;
    s[rs].tag=(s[rs].tag+t)%mod;
    s[x].tag=0;
}
void build(ll x,ll l,ll r){
    s[x].l=l,s[x].r=r;
    if (l==r){
        s[x].v=w[rk[l]];
        return;
    }
    ll mid=(l+r)/2,ls=x*2,rs=x*2+1;
    build(ls,l,mid);build(rs,mid+1,r);
    pushup(x);
}
ll tquery(ll x,ll l,ll r){
    if (l<=s[x].l&&s[x].r<=r){
        return s[x].v;
    }
    pushdown(x);
    ll ls=x*2,rs=x*2+1,mid=(s[x].l+s[x].r)/2,tot=0;
    if (l<=mid) tot+=tquery(ls,l,r);
    if (mid+1<=r) tot+=tquery(rs,l,r);
    return tot%mod;
}
void tadd(ll x,ll l,ll r,ll v){
    if (l<=s[x].l&&s[x].r<=r){
        s[x].v=(s[x].v+v*len(x))%mod;
        s[x].tag=(s[x].tag+v)%mod;
        return;
    }
    pushdown(x);
    ll ls=x*2,rs=x*2+1,mid=(s[x].l+s[x].r)/2,tot=0;
    if (l<=mid) tadd(ls,l,r,v);
    if (mid+1<=r) tadd(rs,l,r,v);
    pushup(x);
}
ll query(ll x,ll y){
    ll tot=0;
    while(top[x]!=top[y]){
        if (d[top[x]]<d[top[y]]) swap(x,y);
        tot=(tot+tquery(1,id[top[x]],id[x]))%mod;
        x=f[top[x]];
    }
    if (id[x]>id[y]) swap(x,y);
    return (tot+tquery(1,id[x],id[y]))%mod;
}
void add(ll x,ll y,ll v){
    while(top[x]!=top[y]){
        if (d[top[x]]<d[top[y]]) swap(x,y);
        tadd(1,id[top[x]],id[x],v);
        x=f[top[x]];
    }
    if (id[x]>id[y]) swap(x,y);
    tadd(1,id[x],id[y],v);
}
int main(){
    scanf("%lld%lld",&n,&m);
    for (ll i=1;i<=n;i++){
        scanf("%lld",&w[i]);
        w[i]%=mod;
    }
    for (ll i=1;i<n;i++){
        scanf("%lld%lld",&a,&b);
        insert(a,b);insert(b,a);
    }
    dfs1(1);dfs2(1,1);
    build(1,1,n);
    for (ll i=1;i<=m;i++){
        scanf("%lld",&op);
        if (op==1){
            scanf("%lld%lld%lld",&a,&b,&c);
            c%=mod;
            add(a,b,c);
        }else{
            scanf("%lld%lld",&a,&b);
            ll t=query(a,b);
            printf("%lld\n",t);
        }
    }
}

O - 尤斯蒂娅的麦穗

线段树
用线段树维护区间最大值,查询的时候就可以使用二分查找来找到区间大于\(k\)的第一个数
\(O(nlogn)\)

#include <cstdio>
#include <iostream>
using namespace std;
const int N=2000000;
int n,h[N],m,a,b;
struct MaxSTree{
    struct P{
        int l,r,v,p,tag;
    }t[4*N];
    int build(int x,int l,int r){
        t[x].l=l,t[x].r=r,t[x].tag=0;
        if (l==r){
            t[x].p=l;
            return t[x].v=h[l];
        }
        int mid=(l+r)/2;
        return t[x].v=max(build(x*2,l,mid),build(x*2+1,mid+1,r));
    }
    void pushdown(int x){
        if (!t[x].tag) return;
        t[x*2].v+=t[x].tag;
        t[x*2+1].v+=t[x].tag;
        t[x*2].tag+=t[x].tag;
        t[x*2+1].tag+=t[x].tag;
        t[x].tag=0;
    }
    int add(int x,int l,int r,int v){
        if (l==t[x].l&&r==t[x].r){t[x].tag+=v;return t[x].v+=v;}
        pushdown(x);
        int mid=(t[x].l+t[x].r)/2;
        if (r<=mid) return t[x].v=max(t[x*2+1].v,add(x*2,l,r,v));
        else if (l>=mid+1) return t[x].v=max(t[x*2].v,add(x*2+1,l,r,v));
        else return t[x].v=max(add(x*2,l,mid,v),add(x*2+1,mid+1,r,v));
    }
    int query(int x,int l,int r,int v){
        if (t[x].v<v) return -1;
        if (t[x].l==t[x].r) return t[x].p;
        pushdown(x);
        int mid=(t[x].l+t[x].r)/2;
        if (r<=mid) return query(x*2,l,r,v);
        else if (l>=mid+1) return query(x*2+1,l,r,v);
        else{
            int t=query(x*2,l,mid,v);
            if (t==-1) t=query(x*2+1,mid+1,r,v);
            return t;
        }
    }
}st;
int main(){
    scanf("%d",&n);
    for (int i=0;i<n;i++) scanf("%d",&h[i]);
    st.build(1,0,n-1);
    scanf("%d",&m);
    for (int i=0;i<m;i++){
        scanf("%d%d",&a,&b);
        int t=st.query(1,a,n-1,b);
        if (t==-1) t=st.query(1,0,a-1,b);
        if (t==-1){
            printf("-1\n");
        }else{
            printf("%d\n",(t+n-a)%n);
            st.add(1,t,t,-h[t]-m);
        }
        st.add(1,0,n-1,1);
    }
}

P - 我,不是说了能力要平均值么 · 改

线段树
欧拉定理
\(a^{\varphi(P)}\equiv{1}\space(mod\space P)\)
\(a^{k\varphi(P)+r}\equiv{a^r}\space(mod\space P)\)
\(a^b\equiv{a^{b\space mod\space \varphi(P)}}\space(mod\space P)\)
因为\(P\)是质数,所以
\(\varphi{(P)}=P-1\)
又因为
\(5^{27}\equiv{1}\space(mod\space P-1)\)
所以有
\(a^{5^{27}}\equiv{a^{5^{27}mod\space(p-1)}}\space (mod\space P)\)
由欧拉定理可知对一个数重复计算\(5\)次方,在模\(73156157\)意义下进行\(27\)次就会循环
线段树维护\(27\)种区间\(5\)次方即可
\(O(27*nlogn)\)

#include <cstdio>
#define ll long long
const ll N=200001,M=27;
ll n,m,l,r,op,a[N],mod=73156157,f[M];
struct node{
    ll l,r,tag,f[M];
}p[N*4];
void pushdown(ll x){
    ll ls=x*2,rs=x*2+1,t=p[x].tag;
    for (ll i=0;i<M;i++) f[i]=p[ls].f[i];
    for (ll i=0;i<M;i++) p[ls].f[i]=f[(i+t)%M];
    for (ll i=0;i<M;i++) f[i]=p[rs].f[i];
    for (ll i=0;i<M;i++) p[rs].f[i]=f[(i+t)%M];
    p[ls].tag+=t;p[rs].tag+=t;p[x].tag=0;
}
void pushup(ll x){
    ll ls=x*2,rs=x*2+1;
    for (ll i=0;i<M;i++) p[x].f[i]=(p[ls].f[i]+p[rs].f[i])%mod;
}
ll query(ll x,ll l,ll r){
    if (l<=p[x].l&&p[x].r<=r){
        return p[x].f[0];
    }
    pushdown(x);
    ll mid=(p[x].l+p[x].r)/2,ls=x*2,rs=x*2+1,tot=0;
    if (l<=mid) tot=(tot+query(ls,l,r))%mod;
    if (r>=mid+1) tot=(tot+query(rs,l,r))%mod;
    pushup(x);
    return tot;
}
ll add(ll x,ll l,ll r,ll v){
    if (l<=p[x].l&&p[x].r<=r){
        for (ll i=0;i<M;i++) f[i]=p[x].f[i];
        for (ll i=0;i<M;i++) p[x].f[i]=f[(i+v)%M];
        p[x].tag+=v;
        return p[x].f[0];
    }
    pushdown(x);
    ll mid=(p[x].l+p[x].r)/2,ls=x*2,rs=x*2+1,tot=0;
    if (l<=mid) add(ls,l,r,v);
    if (r>=mid+1) add(rs,l,r,v);
    pushup(x);
}
void build(ll x,ll l,ll r){
    p[x].l=l,p[x].r=r;
    if (l==r){
        p[x].f[0]=a[l];
        for (ll i=1;i<M;i++){
            ll t=1;
            for (ll j=0;j<5;j++)
                t=(t*p[x].f[i-1])%mod;
            p[x].f[i]=t;
        }
        return;
    }
    ll mid=(l+r)/2,ls=x*2,rs=x*2+1;
    build(ls,l,mid);
    build(rs,mid+1,r);
    pushup(x);
}
ll power(ll a,ll b){
    ll tot=1;
    while(b){
        if (b&1) tot=(tot*a)%mod;
        b/=2;
        a=(a*a)%mod;
    }
    return tot;
}
int main(){
    scanf("%lld%lld",&n,&m);
    for (ll i=1;i<=n;i++) scanf("%lld",&a[i]);
    build(1,1,n);
    for (ll i=1;i<=m;i++){
        scanf("%lld%lld%lld",&op,&l,&r);
        if (op==1){
            add(1,l,r,1);
        }else{
            ll t=(query(1,l,r)*power(r-l+1,mod-2))%mod;
            printf("%lld\n",t);
        }
    }
}

Q - 自动 Treap 机

笛卡尔树,吉司机线段树,线段树套树状数组,Segment Tree Beats
出自Codeforces 1290E
\(maxr[i]\)代表区间\([i,k]\)中的节点能够构成一棵子树的条件下\(k\)的最大值
同理定义\(maxl[i]\)代表区间\([k,i]\)中的节点能够构成一棵子树的条件下\(k\)的最小值
最后一步的答案为
\(\sum\limits_{i=1}^n{[maxr(i)-i+1]+[i-maxl(i)+1]}-n\)
\(n\)是因为整棵树\(maxr(1)=n\)\(maxl(n)=1\)所以被统计了两次
对于\([i-maxl(i)+1]\)我们只需要反转一下区间,就和统计\([maxr(i)-i+1]\)的方法一样了
为了插入每个点\(x\)的时候能快速统计对答案的贡献,我们可以使用一个数组来维护所有区间的标记\(que\)
\(i\)的位置打上\(1\),并在\(maxr[i]\)的位置打上\(-1\),这样每插入一个点对答案的贡献即为数组\(que\)的前缀和
每插入一个点\(x\)不仅要计算其对答案的贡献,还要对\(maxr\)进行区间操作
对于\(0<i<x\)左侧的第一个已经插入的点,需要更新\(maxr(i)=min(maxr(i),x左侧的第一个已经插入的点)\)
吉司机线段树解决\(maxr\)的区间最值操作和单点更新操作,再将线段树产生的修改和区间标记数组\(que\)进行同步
\(O(nlognlogn)\)

#include <cstdio>
#include <set>
#include <iostream>
#include <algorithm>
#include <cstring>
using namespace std;
typedef pair<int,int> P;
typedef long long ll;
const ll N=100001;
ll n,a,p[N],psz=0,ch[N],df[N],sz,ans[N];
P tp[N];
struct ftree{
	ll t[N];
	void add(ll x,ll v){
		for (ll i=x;i&&i<=n;i+=i&-i) t[i]+=v;
	}
	ll query(ll x){
		ll tot=0;
		for (ll i=x;i;i-=i&-i) tot+=t[i];
		return tot;
	}
	void clear(){
		memset(t,0,sizeof(t));
	}
}reg,que;
struct stree{
	struct node{
		ll l,r,mx,se,cnt;
	}t[N*4];
	void pushdown(ll x){
		ll ls=x*2,rs=x*2+1;
		t[ls].mx=min(t[ls].mx,t[x].mx);
		t[rs].mx=min(t[rs].mx,t[x].mx);
	}
	void pushup(ll x){
		ll ls=x*2,rs=x*2+1;
		if (t[ls].mx==t[rs].mx){
			t[x].cnt=t[ls].cnt+t[rs].cnt;
			t[x].se=max(t[ls].se,t[rs].se);
			t[x].mx=t[ls].mx;
		}else if (t[ls].mx>t[rs].mx){
			t[x].cnt=t[ls].cnt;
			t[x].se=max(t[ls].se,t[rs].mx);
			t[x].mx=t[ls].mx;
		}else{
			t[x].cnt=t[rs].cnt;
			t[x].se=max(t[rs].se,t[ls].mx);
			t[x].mx=t[rs].mx;
		}
	}
	void build(ll x,ll l,ll r){
		t[x].l=l;t[x].r=r;
		if (l==r){
			t[x].se=-1e9;
			t[x].mx=0;
			t[x].cnt=1;
			return;
		}
		ll mid=(l+r)/2,ls=x*2,rs=x*2+1;
		build(ls,l,mid);
		build(rs,mid+1,r);
		pushup(x);
	}
	void updateseg(ll x,ll l,ll r,ll v){
		if (t[x].r<l||r<t[x].l||t[x].mx<=v) return;
		if (l<=t[x].l&&t[x].r<=r&&t[x].se<v){
			ch[++sz]=t[x].mx;df[sz]=-t[x].cnt;
			t[x].mx=v;
			ch[++sz]=t[x].mx;df[sz]=t[x].cnt;
			return;
		}
		ll ls=x*2,rs=x*2+1;
		pushdown(x);
		updateseg(ls,l,r,v);
		updateseg(rs,l,r,v);
		pushup(x);
	}
	void updatepos(ll x,ll p,ll v){
		if (t[x].r<p||p<t[x].l) return;
		if (t[x].l==p&&t[x].r==p){
			ch[++sz]=t[x].mx;df[sz]=-t[x].cnt;
			t[x].mx=v;
			ch[++sz]=t[x].mx;df[sz]=t[x].cnt;
			return;
		}
		ll ls=x*2,rs=x*2+1;
		pushdown(x);
		updatepos(ls,p,v);
		updatepos(rs,p,v);
		pushup(x);
	}
}w;
set<int> arr,isr;
void solve(){
	arr.clear();//维护已有元素位置
	que.clear();//维护区间标记
	reg.clear();//维护已有元素标记
	isr.clear();//维护maxr是否为null
	w.build(1,1,n);//维护maxr并更新que
	arr.insert(p[1]);
	isr.insert(p[1]);
	reg.add(p[1],1);
	que.add(p[1],1);
	que.add(p[1]-1,-1);
	w.updatepos(1,p[1],p[1]);
	ll tot=1,mx=p[1],mn=p[1];
	ans[1]+=tot;
	for (ll i=2;i<=n;i++){
		sz=0;
		reg.add(p[i],1);
		tot+=que.query(n)-que.query(p[i]-1);
		if (p[i]<mn){
			w.updatepos(1,p[i],mx);
			que.add(p[i]-1,-1);
			isr.insert(p[i]);
			mn=p[i];
		}else if (p[i]>mx){
			w.updatepos(1,mn,p[i]);
			mx=p[i];
		}else{
			set<int>::iterator it=arr.lower_bound(p[i]);
			ll rmn=*it,lmx=*(--it);
			w.updatepos(1,rmn,mx);
			w.updateseg(1,1,lmx,lmx);
			if (isr.count(rmn)==0){
				que.add(rmn-1,-1);
				tot-=reg.query(rmn-1);
				isr.insert(rmn);
			}
			w.updatepos(1,mn,mx);
		}
		for (ll j=1;j<=sz;j++){
			tot+=reg.query(ch[j])*df[j];
			que.add(ch[j],df[j]);
		}
		arr.insert(p[i]);
		ans[i]+=tot;
	}
}
int main(){
	scanf("%lld",&n);
	for (ll i=1;i<=n;i++){
		scanf("%lld",&a);
		tp[++psz]=P(a,i);
	}
	sort(tp+1,tp+1+psz);
	for (ll i=1;i<=psz;i++) p[tp[i].second]=i;
	solve();
	for (ll i=1;i<=n;i++) p[i]=n+1-p[i];
	solve();
	for (ll i=1;i<=n;i++){
		ans[i]-=i;
		printf("%lld\n",ans[i]);
	}
}

R - 利姆露的魔法咒语库

DFS
版本之间的关系可以抽象为一颗树
离线DFS一遍就算出来每个版本的状态了
\(O(n)\)

#include <cstdio>
const int N=1000001;
struct edge{
    int next,to;
}e[2*N],qe[2*N];
int head[N],qhead[N],ans[N],sz=0,qsz=0,n,m,a[N],b,c,q[N],cnt=0,qcnt=0,op,v,vx[N],vy[N];
void insert(int x,int y){
    sz++;
    e[sz].next=head[x];
    head[x]=sz;
    e[sz].to=y;
}
void qinsert(int x,int y){
    qsz++;
    qe[qsz].next=qhead[x];
    qhead[x]=qsz;
    qe[sz].to=y;
}
void dfs(int x){
    int t=a[vx[x]];
    a[vx[x]]=vy[x];
    for (int v,i=qhead[x];i;i=qe[i].next){
        ans[i]=a[q[i]];
    }
    for (int v,i=head[x];i;i=e[i].next){
        dfs(i);
    }
    a[vx[x]]=t;
}
int main(){
    scanf("%d%d",&n,&m);
    for (int i=1;i<=n;i++) scanf("%d",&a[i]);
    for (int i=1;i<=m;i++){
        scanf("%d",&op);
        if (op==1){
            cnt++;
            scanf("%d%d%d",&v,&vx[cnt],&vy[cnt]);
            insert(v,cnt);
        }else{
            qcnt++;
            scanf("%d%d",&b,&q[qcnt]);
            qinsert(b,qcnt);
        }
    }
    dfs(0);
    for (int i=1;i<=qcnt;i++) printf("%d\n",ans[i]);
}

S - 鸽子与 808

Splay
两点交换操作可以等价为两次区间反转操作,用Splay维护保证时间复杂度
\(O(nlogn)\)

#include <cstdio>
#include <iostream>
#define ll long long
using namespace std;
const ll N=100100;
ll a,b,c;
ll n,m,tot,rt,siz[N],fa[N],rev[N],pls[N],ch[N][2],sum[N],val[N],p[N];
char op[2];
void update(ll x){
    ll ls=ch[x][0],rs=ch[x][1];
    siz[x]=siz[ls]+siz[rs]+1;
    sum[x]=sum[ls]+sum[rs]+val[x];
}
void pushdown(ll x){
    ll ls=ch[x][0],rs=ch[x][1],t=pls[x];
    if(rev[x]){
        rev[ls]^=1;
        rev[rs]^=1;
        swap(ch[x][0],ch[x][1]);
        rev[x]=0;
    }
    if (t){
        pls[ls]+=t,sum[ls]+=siz[ls]*t,val[ls]+=t;
        pls[rs]+=t,sum[rs]+=siz[rs]*t,val[rs]+=t;
        pls[x]=0;
    }
}
ll getson(ll x){
    return ch[fa[x]][1]==x;
}
void rotate(ll x){
    ll y=fa[x],z=fa[y],b=getson(x),c=getson(y),a=ch[x][!b];
    if(z)ch[z][c]=x;else rt=x;fa[x]=z;
    if(a)fa[a]=y;ch[y][b]=a;
    ch[x][!b]=y;fa[y]=x;
    update(y);update(x);
}
void splay(ll x,ll i){
    while(fa[x]!=i){
        ll y=fa[x],z=fa[y];
        if(z==i)rotate(x);
        else {
            if(getson(x)==getson(y)){rotate(y);rotate(x);}
            else {rotate(x);rotate(x);}
        }
    }
}
ll find(ll x){
    ll now=rt;
    while(now){
        pushdown(now);
        if(ch[now][0]&&x<=siz[ch[now][0]])
            now=ch[now][0];
        else {
            ll t=(ch[now][0]?siz[ch[now][0]]:0)+1;
            if(x<=t)return now;
            x-=t;
            now=ch[now][1];
        }
    }
}
ll solve(ll x){
    ll now=rt,ans=0;
    while(now){
        pushdown(now);
        if(ch[now][0]&&x<=sum[ch[now][0]])
            now=ch[now][0];
        else {
            ll t=(ch[now][0]?sum[ch[now][0]]:0)+val[now];
            ans+=(ch[now][0]?siz[ch[now][0]]:0)+1;
            if(x<=t)return ans;
            x-=t;
            now=ch[now][1];
        }
    }
}
void reverse(ll l,ll r){
    ll x=find(l-1),y=find(r+1);
    splay(x,0);splay(y,x);
    ll t=ch[ch[rt][1]][0];
    rev[t]^=1;
}
void add(ll l,ll r,ll v){
    ll x=find(l-1),y=find(r+1);
    splay(x,0);splay(y,x);
    ll t=ch[ch[rt][1]][0];
    val[t]+=v,pls[t]+=v,sum[t]+=siz[t]*v;
}
ll build(ll l,ll r,ll rt)
{
    ll now=l+r>>1;
    fa[now]=rt;
    if(l<now)ch[now][0]=build(l,now-1,now);
    if(r>now)ch[now][1]=build(now+1,r,now);
    update(now);
    return now;
}
int main(){
    scanf("%lld%lld",&n,&m);
    for (ll i=2;i<=n+1;i++){
        scanf("%lld",&val[i]);
        tot+=val[i];
    }
    for (ll i=2;i<=n+1;i++)
        scanf("%lld",&p[i]);
    rt=build(1,n+2,0);
    ll t=solve((tot+1)/2);
    printf("%lld\n",p[t]);
    for (ll i=1;i<=m;i++){
        scanf("%s",op);
        if (op[0]=='A'){
            scanf("%lld%lld%lld",&a,&b,&c);
            a++;b++;
            tot+=(b-a+1)*c;
            add(a,b,c);
        }else{
            scanf("%lld%lld",&a,&b);
            a++;b++;
            if (a<b){
                reverse(a,b);
                reverse(a,b-1);
            }else{
                reverse(b,a);
                reverse(b+1,a);
            }
        }
        ll t=solve((tot+1)/2);
        printf("%lld\n",p[t]);
    }
}

T - 红魔族首屈一指の族长之女

树上带修莫队
按照DFS序对进行整颗树进行分块,对所有的查询按照起始点所在块、终止点所在块、版本号进行排序,再依次暴力处理每个查询即可
\(O(nlogn+n^{\frac{5}{3}})\)

#include <cstdio>
#include <iostream>
#include <algorithm>
#include <cmath>
using namespace std;
typedef long long ll;
const ll N=100001,M=17;
ll n,m,q,tot=0,now=0,head[N],d[N],k[N],f[N],c[N],p2[N],p[N],ans[N],a,b,op,fa[N][M],sta[N],B,esz=0,ssz=0,cnt=0,bl[N],vis[N],t1=0,t2=0;
struct E{
    ll next,to;
}e[N*2];
struct Q1{
    ll a,b,t,id;
    bool operator < (const Q1 &r) const {
        return  bl[a]==bl[r.a]?
                    bl[b]==bl[r.b]?
                        t<r.t:
                    bl[b]<bl[r.b]:
                bl[a]<bl[r.a];
    }
}q1[N];
struct Q2{
    ll x,c,ls;
}q2[N];
void insert(ll a,ll b){
    esz++;
    e[esz].next=head[a];
    head[a]=esz;
    e[esz].to=b;
}
void dfs(ll x){
    sta[++ssz]=x;
    if (ssz==B){
        cnt++;
        while(ssz) bl[sta[ssz--]]=cnt;
    }
    for (ll i=1;i<M;i++)
        fa[x][i]=fa[fa[x][i-1]][i-1];
    for (ll i=head[x];i;i=e[i].next){
        ll v=e[i].to;
        if (!d[v]){
            fa[v][0]=x;
            d[v]=d[x]+1;
            dfs(v);
        }
    }
}
void revpos(ll x){
    vis[x]^=1;
    if (vis[x])
        tot+=f[p[x]]*c[++k[p[x]]];
    else
        tot-=f[p[x]]*c[k[p[x]]--];
}
void change(ll x,ll v){
    if (vis[x]){
        revpos(x);p[x]=v;revpos(x);
    }else{
        p[x]=v;
    }
}
void revseg(ll a,ll b){
    while(a!=b){
        if (d[a]<d[b]) swap(a,b);
        revpos(a);
        a=fa[a][0];
    }
}
ll lca(ll a,ll b){
    if (d[a]<d[b]) swap(a,b);
    for (ll i=M-1;i>=0;i--)
        if (d[fa[a][i]]>=d[b])
            a=fa[a][i];
    if (a==b) return a;
    for (ll i=M-1;i>=0;i--)
        if (fa[a][i]!=fa[b][i])
            a=fa[a][i],b=fa[b][i];
    return fa[a][0];
}
int main(){
    scanf("%lld%lld%lld",&n,&m,&q);
    for (ll i=1;i<=m;i++) scanf("%lld",&f[i]);
    for (ll i=1;i<=n;i++) scanf("%lld",&c[i]);
    for (ll i=1;i<n;i++){
        scanf("%lld%lld",&a,&b);
        insert(a,b);insert(b,a);
    }
    for (ll i=1;i<=n;i++){
        scanf("%lld",&p[i]);
        p2[i]=p[i];
    }
    B=pow(n,0.666)*0.5;
    d[1]=1;
    dfs(1);
    cnt++;
    while(ssz) bl[sta[ssz--]]=cnt;
    for (ll i=1;i<=q;i++){
        scanf("%lld%lld%lld",&op,&a,&b);
        if (op){
            t1++;
            if (bl[a]>bl[b]) swap(a,b);
            q1[t1].a=a;
            q1[t1].b=b;
            q1[t1].t=t2;
            q1[t1].id=t1;
        }else{
            t2++;
            q2[t2].x=a;
            q2[t2].c=b;
            q2[t2].ls=p2[a];
            p2[a]=b;
        }
    }
    sort(q1+1,q1+1+t1);
    q1[0].a=q1[0].b=1;
    for (ll i=1;i<=t1;i++){
        while(now<q1[i].t){
            now++;change(q2[now].x,q2[now].c);
        }
        while(now>q1[i].t){
            change(q2[now].x,q2[now].ls);now--;
        }
        revseg(q1[i-1].a,q1[i].a);
        revseg(q1[i-1].b,q1[i].b);
        ll t=lca(q1[i].a,q1[i].b);
        revpos(t);
        ans[q1[i].id]=tot;
        revpos(t);
    }
    for (ll i=1;i<=t1;i++) printf("%lld\n",ans[i]);
}

U - 只要题目名字够长里面配图够多,即使题面写得再烂题解再毒瘤你也一定会写的吧!

延时重构
解决这个问题大体有两种策略,将这两种策略平衡一下就可以得到合适的时间复杂度
\(n=1e3,m=1e5\)
1 计算两个三角形之间的影响,修改复杂度是\(O(1)\),查询复杂度是\(O(m)\),总的复杂度为\(O(m^2)\)
2 将三角形填入到整个大三角形,修改复杂度是\(O(n)\),查询复杂度是\(O(n)\),总的复杂度为\(O(m*n)\)
如果每隔\(k\)次操作再将三角形填入到整个大三角形里,就可以将之前的操作序列分成两部分
每次修改\(O(1)\),每次查询\(O(n+k)\),每次重构\(O(n^2)\)
总的复杂度\(O(\frac{m}{k}n^2+nm+mk)\)
\(\frac{m}{k}n^2+nm+mk\geq{nm+\sqrt{\frac{m}{k}n^2*mk}}\geq{3nm}\)
\(\frac{m}{k}n^2=mk\),即\(k=n\)时复杂度最小
\(O(nm)\)

#include <cstdio>
#include <cstring>
#define ll long long
const ll N=1002;
ll op,x,y,a,z,n,m,f[N][N],d[N][N],pre[N][N],cnt=0,sz=0;
struct node{
    ll x,y,a,z;
    node(ll x1,ll x2,ll x3,ll x4){
        x=x1,y=x2,a=x3,z=x4;
    }
    node(){
        x=y=a=z=0;
    }
}p[N];
void apply(){
    memset(d,0,sizeof(d));
    for (ll i=1;i<=sz;i++){
        for (ll j=0;j<p[i].a;j++){
            d[p[i].x+j][p[i].y]+=p[i].z;
            d[p[i].x+j][p[i].y+j+1]-=p[i].z;
        }
    }
    for (ll i=1;i<=n;i++){
        ll t=0;
        for (ll j=1;j<=i;j++){
            t+=d[i][j];
            f[i][j]+=t;
            pre[i][j]=pre[i][j-1]+f[i][j];
        }
    }
    sz=0;
}
void add(ll x,ll y,ll a,ll z){
    sz++;
    p[sz].x=x;
    p[sz].y=y;
    p[sz].a=a;
    p[sz].z=z;
}
ll sum(ll x){
    return (x+1)*x/2;
}
ll calc(node &p1,node &p2){
    p1.x=n-p1.x-p1.a+2;p2.x=n-p2.x-p2.a+2;
    ll tot=0,t1=p1.x+p1.y,t2=p2.x+p2.y;
    if (p1.x<=p2.x&&p1.y<=p2.y){
        if (t1+p1.a-1>=t2&&t1+p1.a<=t2+p2.a)
            tot=sum(t1+p1.a-t2);
        if (t1+p1.a>t2+p2.a)
            tot=sum(p2.a);
    }
    if (p2.x<p1.x&&p1.x<=p2.x+p2.a-1&&p1.y<=p2.y){
        if (p1.y+p1.a-1>=p2.y&&t1+p1.a<=t2+p2.a)
            tot=sum(p1.y+p1.a-p2.y);
        if (t1+p1.a>t2+p2.a)
            tot=sum(p2.x+p2.a-p1.x);
    }
    if (p2.y<p1.y&&p1.y<=p2.y+p2.a-1&&p1.x<=p2.x){
        if (p1.x+p1.a-1>=p2.x&&t1+p1.a<=t2+p2.a)
            tot=sum(p1.x+p1.a-p2.x);
        if (t1+p1.a>t2+p2.a)
            tot=sum(p2.y+p2.a-p1.y);
    }
    if (p1.y>p2.y&&p1.x>p2.x&&t1<=t2+p2.a-1){
        if (t1+p1.a<=t2+p2.a)
            tot=sum(p1.a);
        if (t1+p1.a>t2+p2.a)
            tot=sum(t2+p2.a-t1);
    }
    p1.x=n-p1.x-p1.a+2;p2.x=n-p2.x-p2.a+2;
    return tot*p1.z;
}
ll query(ll x,ll y,ll a){
    ll tot=0;node t(x,y,a,z);
    for (ll i=1;i<=sz;i++) tot+=calc(p[i],t);
    for (ll i=0;i<a;i++){
        tot+=pre[x+i][y+i]-pre[x+i][y-1];
    }
    return tot;
}
int main(){
    scanf("%lld%lld",&n,&m);
    for (ll i=1;i<=m;i++){
        scanf("%lld",&op);
        if (op==1){
            scanf("%lld%lld%lld%lld",&x,&y,&a,&z);
            add(x,y,a,z);
            cnt++;
            if (cnt==n){
                apply();
                cnt=0;
            }
        }else{
            scanf("%lld%lld%lld",&x,&y,&a);
            ll t=query(x,y,a);
            printf("%lld\n",t);
        }
    }
}

V - 一道普通的数据结构题

线段树
用线段树维护区间最大值,查询的时候就可以使用二分查找来找到区间大于\(k\)的第一个数
\(O(nlogn)\)

#include <cstdio>
#include <iostream>
using namespace std;
const int N=1000001;
int n,h[N],m,a,b,c,T;
struct MaxSTree{
    struct P{
        int l,r,v,p,tag;
    }t[4*N];
    int build(int x,int l,int r){
        t[x].l=l,t[x].r=r,t[x].tag=0;
        if (l==r){
            t[x].p=l;
            return t[x].v=h[l];
        }
        int mid=(l+r)/2;
        return t[x].v=max(build(x*2,l,mid),build(x*2+1,mid+1,r));
    }
    void pushdown(int x){
        if (!t[x].tag) return;
        t[x*2].v+=t[x].tag;
        t[x*2+1].v+=t[x].tag;
        t[x*2].tag+=t[x].tag;
        t[x*2+1].tag+=t[x].tag;
        t[x].tag=0;
    }
    int add(int x,int l,int r,int v){
        if (l==t[x].l&&r==t[x].r){t[x].tag+=v;return t[x].v+=v;}
        pushdown(x);
        int mid=(t[x].l+t[x].r)/2;
        if (r<=mid) return t[x].v=max(t[x*2+1].v,add(x*2,l,r,v));
        else if (l>=mid+1) return t[x].v=max(t[x*2].v,add(x*2+1,l,r,v));
        else return t[x].v=max(add(x*2,l,mid,v),add(x*2+1,mid+1,r,v));
    }
    int query(int x,int l,int r,int v){
        if (t[x].v<v) return -1;
        if (t[x].l==t[x].r) return t[x].p;
        pushdown(x);
        int mid=(t[x].l+t[x].r)/2;
        if (r<=mid) return query(x*2,l,r,v);
        else if (l>=mid+1) return query(x*2+1,l,r,v);
        else{
            int t=query(x*2,l,mid,v);
            if (t==-1) t=query(x*2+1,mid+1,r,v);
            return t;
        }
    }
}st;
int main(){
    scanf("%d",&T);
    while(T--){
        scanf("%d%d",&n,&m);
        for (int i=1;i<=n;i++) scanf("%d",&h[i]);
        st.build(1,1,n);
        for (int i=1;i<=m;i++){
            scanf("%d%d%d",&a,&b,&c);
            int t=st.query(1,a,b,c);
            if (t==-1){
                printf("-1\n");
            }else{
                printf("%d\n",t);
                st.add(1,t,t,-c);
            }
        }
    }
}

W - 我,不是说能力要平均值么 · 改二

斐波那契,线段树
斐波那契通项
\(F_n=\frac{\left(\frac{1+\sqrt{5}}{2}\right)^n-\left(\frac{1-\sqrt{5}}{2}\right)^n}{\sqrt{5}}\)
平方变形有
\(A=\frac{3+\sqrt{5}}{2}\)
\(B=\frac{3-\sqrt{5}}{2}\)
\(F_n^2=\frac{1}{5}(A^n+B^n-2*(-1)^n)\)
每次区间加可以看成是加了三个等比数列
线段树维护区间等比数列的首项以及区间和即可
\(O(nlogn)\)

#include <cstdio>
#include <ctime>
#include <cstdlib>
#define ll long long
const ll N=200010;
ll gen5=383008016,mod=1000000009,x1[3][N],rev[3],x2[3],n,m,w[N],op,a,b,c,cnt=0,tx1[3];
clock_t tm=0;
ll power(ll a,ll b){
    ll tot=1;
    while(b){
        if (b&1) tot=(tot*a)%mod;
        b/=2;
        a=(a*a)%mod;
    }
    return tot;
}
ll i(ll x){
    return ((x%mod)+mod)%mod;
}
inline ll mul(ll a,ll b){
    return (a*b)%mod;
}
ll divi(ll a,ll b){
    return mul(a,power(b,mod-2));
}
struct node{
    ll l,r,tag[3],v;
}p[N*4];
inline ll sum(ll a,ll q,ll l){
    if (q==2) return l%2?a:0;
    return mul(mul(a,i(1-x1[q][l])),rev[q]);
}
void pushup(ll x){
    ll ls=x*2,rs=x*2+1;
    p[x].v=(p[ls].v+p[rs].v)%mod;
}
void build(ll x,ll l,ll r){
    p[x].l=l;p[x].r=r;
    if (l==r){
        p[x].v=w[l];
        return;
    }
    ll mid=(p[x].l+p[x].r)/2,ls=x*2,rs=x*2+1;
    build(ls,l,mid);
    build(rs,mid+1,r);
    pushup(x);
}
void pushdown(ll x){
    ll ls=x*2,rs=x*2+1;
    for (ll i=0;i<3;i++){
        if (p[x].tag[i]==0) continue;
        p[ls].v=(p[ls].v+sum(p[x].tag[i],i,p[ls].r-p[ls].l+1))%mod;
        p[ls].tag[i]=(p[ls].tag[i]+p[x].tag[i])%mod;
        p[rs].v=(p[rs].v+sum(mul(p[x].tag[i],x1[i][p[rs].l-p[ls].l]),i,p[rs].r-p[rs].l+1))%mod;
        p[rs].tag[i]=(p[rs].tag[i]+mul(p[x].tag[i],x1[i][p[rs].l-p[ls].l]))%mod;
        p[x].tag[i]=0;
    }
}
void update(ll x,ll l,ll r,ll v){
    if (l<=p[x].l&&p[x].r<=r){
        for (ll i=0;i<3;i++)
        {
            p[x].v=(p[x].v+sum(mul(x2[i],mul(tx1[i],x1[i][p[x].l-l])),i,p[x].r-p[x].l+1))%mod;
            p[x].tag[i]=(p[x].tag[i]+mul(x2[i],mul(tx1[i],x1[i][p[x].l-l])))%mod;
        }
        return;
    }
    pushdown(x);
    ll mid=(p[x].l+p[x].r)/2,ls=x*2,rs=x*2+1;
    if (l<=mid) update(ls,l,r,v);
    if (r>=mid+1) update(rs,l,r,v);
    pushup(x);
}
ll query(ll x,ll l,ll r){
    if (l<=p[x].l&&p[x].r<=r){
        return p[x].v;
    }
    pushdown(x);
    ll mid=(p[x].l+p[x].r)/2,ls=x*2,rs=x*2+1,tot=0;
    if (l<=mid) tot=(tot+query(ls,l,r))%mod;
    if (r>=mid+1) tot=(tot+query(rs,l,r))%mod;
    pushup(x);
    return tot;
}
int main(){
    tm=clock();
    x1[0][0]=1;x1[0][1]=divi(3+gen5,2);x2[0]=divi(1,5);
    x1[1][0]=1;x1[1][1]=divi(i(3-gen5),2);x2[1]=divi(1,5);
    x1[2][0]=1;x1[2][1]=i(-1);x2[2]=divi(i(-2),5);
    rev[0]=divi(1,i(1-x1[0][1]));
    rev[1]=divi(1,i(1-x1[1][1]));
    rev[2]=divi(1,i(1-x1[2][1]));
    scanf("%lld%lld",&n,&m);
    for (ll i=1;i<=n;i++) scanf("%lld",&w[i]);
    build(1,1,n);
    for (ll i=2;i<=n;i++){
        for (ll j=0;j<3;j++){
            x1[j][i]=(x1[j][i-1]*x1[j][1])%mod;
        }
    }
    for (ll i=1;i<=m;i++){
        cnt=0;
        scanf("%lld%lld%lld",&op,&a,&b);
        if (op==1){
            ll t=divi(query(1,a,b),b-a+1);
            printf("%lld\n",t);
        }else{
            scanf("%lld",&c);
            for (ll j=0;j<3;j++)
                tx1[j]=power(x1[j][1],c);
            update(1,a,b,c);
        }
    }
}

X - 路口

树状数组,离散化,扫描线
扫描线维护每一行上有那些列是有街道的,再用树状数组求区间和就能得到这一行上的路口数
题目还要询问从第\(d\)行到第\(u\)行一共有多少个路口,仍然是区间和问题,使用树状数组解决
注意坐标范围很大,要使用树状数组注意先对坐标进行离散化
\(O(nlogn)\)

#include <cstdio>
#include <algorithm>
using namespace std;
#define ll long long 
#define P(a,b) make_pair(a,b)
const ll N=200100,M=N*8;
ll n,m,x1[N],y1[N],x2[N],y2[N],u[N],d[N],t[M],o1[N],o2[N],cnt=0;
ll p[M],ans[M],xcnt=0,ycnt=0,xp=0,yp=0,tot=0;
pair<int,pair<int,int> > x[N*2],y[N*2];
void reset(){
    sort(t,t+cnt);
    for (ll i=1;i<=n;i++){
        o1[i]=lower_bound(t,t+cnt,x1[i]-1)-t+1;
        o2[i]=lower_bound(t,t+cnt,y2[i]+1)-t+1;
        x1[i]=lower_bound(t,t+cnt,x1[i])-t+1;
        y1[i]=lower_bound(t,t+cnt,y1[i])-t+1;
        x2[i]=lower_bound(t,t+cnt,x2[i])-t+1;
        y2[i]=lower_bound(t,t+cnt,y2[i])-t+1;
    }
    for (ll i=1;i<=m;i++){
        u[i]=lower_bound(t,t+cnt,u[i])-t+1;
        d[i]=lower_bound(t,t+cnt,d[i])-t+1;
    }
}
ll lowbit(ll x){
	return x&(-x);
}
void add(ll x,ll v){
	while(x<M){
		p[x]+=v;
		x+=lowbit(x);
	}
}
ll sum(ll x){
	ll ans=0;
	while(x){
		ans+=p[x];
		x-=lowbit(x);
	}
	return ans;
}
int main(){
    scanf("%lld%lld",&n,&m);
    for (ll i=1;i<=n;i++){
        scanf("%lld%lld%lld%lld",&x1[i],&y1[i],&x2[i],&y2[i]);
        t[cnt++]=x1[i];t[cnt++]=y1[i];
        t[cnt++]=x2[i];t[cnt++]=y2[i];
        t[cnt++]=x1[i]-1,t[cnt++]=y2[i]+1;
    }
    for (ll i=1;i<=m;i++){
        scanf("%lld%lld",&d[i],&u[i]);
        d[i]--;
        t[cnt++]=d[i];t[cnt++]=u[i];
    }
    reset();
    for (ll i=1;i<=n;i++){
        if (x1[i]==x2[i]){
            x[xcnt++]=P(y1[i],P(x1[i],1));
            x[xcnt++]=P(o2[i],P(x1[i],-1));
        }else{
            y[ycnt++]=P(y1[i],P(x2[i],1));
            y[ycnt++]=P(y1[i],P(o1[i],-1));
        }
    }
    sort(x,x+xcnt);
    sort(y,y+ycnt);
    for (ll i=0;i<M;i++){
        while(xp<xcnt&&x[xp].first==i){
            add(x[xp].second.first,x[xp].second.second);
            xp++;
        }
        while(yp<ycnt&&y[yp].first==i){
            tot+=sum(y[yp].second.first)*y[yp].second.second;
            yp++;
        }
        ans[i]=tot;
    }
    for (ll i=1;i<=m;i++){
        printf("%lld\n",ans[u[i]]-ans[d[i]]);
    }
}

Y - 诶,我和你讲这瓜超休闲的

线性基,线段树
线段树维护区间线性基
上面的题解比较通俗易懂,故引用如下

对原序列\(a\)进行差分,使得\(b[i]=a[i]\hat{}a[i+1]\),那么\(a[l],a[l+1],...,a[r]\)可以构成的一个异或和,在\(a[l],b[l],b[l+1],...,b[r-1]\)中肯定也可以被构造出来,因此二者是等价的。

对于\(a\)序列的区间\([l,r]\)的更新实际上\(b\)序列上只有\(b[l-1]\)\(b[r]\)被影响,中间的数由于是两两所以抵消掉了。

所以现在就变成了单点更新线段树维护\(b\)序列的区间线性基,然后再用一个树状数组维护\(a\)序列被修改的值。

这里为了复用代码所以用了两个线段树
\(O(mlog^3n)\)

#include <cstdio>
#define ll long long
const ll N=400010,M=32;
ll n,m,l,r,c,op,a[N],b[N],tot[M],T;
struct node{
    ll l,r,f[M];
}p[N*4];
struct anode{
    ll l,r,tag,v;
}ap[N*4];
void print(ll *f){
    printf("# ");
    for (ll i=0;i<M;i++) if (f[i]) printf("%lld:%lld ",i,f[i]);
    printf("\n");
}
void insert(ll *f,ll x){
    for (ll i=M-1;i>=0;i--){
        if ((x>>i)&1){
            if (f[i]){
                x^=f[i];
            }else{
                f[i]=x;
                break;
            }
        }
    }
}
void pushup(ll x){
    ll ls=x*2,rs=x*2+1;
    for (ll i=0;i<M;i++) p[x].f[i]=p[ls].f[i];
    for (ll i=0;i<M;i++) insert(p[x].f,p[rs].f[i]);
}
void query(ll x,ll l,ll r){
    if (l<=p[x].l&&p[x].r<=r){
        for (ll i=0;i<M;i++) insert(tot,p[x].f[i]);
        return;
    }
    ll mid=(p[x].l+p[x].r)/2,ls=x*2,rs=x*2+1;
    if (l<=mid) query(ls,l,r);
    if (r>=mid+1) query(rs,l,r);
    return;
}
void update(ll x,ll l){
    if (l==p[x].l&&p[x].r==l){
        for (ll i=0;i<M;i++) p[x].f[i]=0;
        insert(p[x].f,b[l]);
        return;
    }
    ll mid=(p[x].l+p[x].r)/2,ls=x*2,rs=x*2+1;
    if (l<=mid) update(ls,l);
    if (l>=mid+1) update(rs,l);
    pushup(x);
}
void build(ll x,ll l,ll r){
    p[x].l=l,p[x].r=r;
    if (l==r){
        for (ll i=0;i<M;i++) p[x].f[i]=0;
        insert(p[x].f,b[l]);
        return;
    }
    ll mid=(l+r)/2,ls=x*2,rs=x*2+1;
    build(ls,l,mid);
    build(rs,mid+1,r);
    pushup(x);
}
void pushdown(ll x){
    ll ls=x*2,rs=x*2+1,t=ap[x].tag;
    ap[ls].v^=t;ap[rs].v^=t;
    ap[ls].tag^=t;ap[rs].tag^=t;ap[x].tag=0;
}
void aupdate(ll x,ll l,ll r,ll v){
    if (l<=ap[x].l&&ap[x].r<=r){
        ap[x].v^=v;
        ap[x].tag^=v;
        return;
    }
    pushdown(x);
    ll mid=(ap[x].l+ap[x].r)/2,ls=x*2,rs=x*2+1;
    if (l<=mid) aupdate(ls,l,r,v);
    if (r>=mid+1) aupdate(rs,l,r,v);
    return;
}
ll aquery(ll x,ll l){
    if (l==ap[x].l&&ap[x].r==l){
        return ap[x].v;
    }
    pushdown(x);
    ll mid=(ap[x].l+ap[x].r)/2,ls=x*2,rs=x*2+1;
    if (l<=mid) return aquery(ls,l);
    if (l>=mid+1) return aquery(rs,l);
}
void abuild(ll x,ll l,ll r){
    ap[x].l=l,ap[x].r=r;ap[x].tag=0;
    if (l==r){
        for (ll i=0;i<M;i++) ap[x].v=a[l];
        return;
    }
    ll mid=(l+r)/2,ls=x*2,rs=x*2+1;
    abuild(ls,l,mid);
    abuild(rs,mid+1,r);
}
int main(){
    scanf("%lld",&T);
    while(T--){
        scanf("%lld%lld",&n,&m);
        for (ll i=1;i<=n;i++) scanf("%lld",&a[i]);
        for (ll i=1;i<=n-1;i++){
            b[i]=a[i]^a[i+1];
        }
        build(1,1,n);
        abuild(1,1,n);
        for (ll i=1;i<=m;i++){
            scanf("%lld%lld%lld",&op,&l,&r);
            if (op==1){
                scanf("%lld",&c);
                if (l-1>=1) {b[l-1]^=c;update(1,l-1);}
                if (r<=n-1) {b[r]^=c;update(1,r);}
                aupdate(1,l,r,c);
            }else{
                for (ll i=0;i<M;i++) tot[i]=0;
                query(1,l,r-1);
                insert(tot,aquery(1,l));
                ll t=1;
                for (ll i=0;i<M;i++) if (tot[i]) t*=2;
                printf("%lld\n",t);
            }
        }
    }
}

Z - 綾波的游戏II

分块
每一块打标记记录该块内所有数是否相等即可
\(O(n\sqrt{n})\)

#include <cstdio>
#include <cmath>
const int N=100001,K=318;
int l[K],r[K],tag[K],istag[K],h,n,m,k,w[N],pos[N],a,b,c;
void add(int a,int b,int c){
    w[b]=c;
}
int len(int x){
    return r[x]-l[x]+1;
}
void pushdown(int x){
    if (istag[x]){
        for (int i=l[x];i<=r[x];i++) add(x,i,tag[x]);
        istag[x]=0;
    }
}
int solve(int a,int b,int c){
    int p=pos[a],q=pos[b],ans=0;
    if (p==q){
        pushdown(p);
        for (int i=a;i<=b;i++){
            if (w[i]!=c) ans++;
            add(p,i,c);
        }
    }else{
        pushdown(p);
        pushdown(q);
        for (int i=p+1;i<=q-1;i++){
            if (istag[i]){
                if (tag[i]!=c) ans+=len(i);
            }else{
                for (int j=l[i];j<=r[i];j++)
                    if (w[j]!=c) ans++;
            }
            istag[i]=1;
            tag[i]=c;
        }
        for (int i=a;i<=r[p];i++){
            if (w[i]!=c) ans++;
            add(p,i,c);
        }
        for (int i=l[q];i<=b;i++){
            if (w[i]!=c) ans++;
            add(q,i,c);
        }
    }
    return ans;
}
int main(){
    scanf("%d",&n);
    for (int i=1;i<=n;i++) scanf("%d",&w[i]);
    scanf("%d",&m);
    h=sqrt(n);
    for (int i=1;i<=h;i++)
        l[i]=(i-1)*h+1,r[i]=i*h;
    if (r[h]<n)
        h++,l[h]=r[h-1]+1,r[h]=n;
    for (int i=1;i<=h;i++)
        for (int j=l[i];j<=r[i];j++)
            pos[j]=i;
    for (int i=1;i<=m;i++){
        scanf("%d%d%d",&a,&b,&c);
        int t=solve(a,b,c);
        printf("%d\n",t);
    }
}
posted @ 2020-05-28 17:57  Byaidu  阅读(530)  评论(0编辑  收藏  举报