CWOI DS 专题 1

A - 相逢是问候

考虑拓展欧拉定理,那么在 \(\mathcal{O}(\log p)\) 层的幂塔后指数就会变为常数,所以暴力在线段树上更新即可,复杂度 \(\mathcal{O}(n\log p)\)。似乎需要光速幂,细节有点多。

点击查看代码
#include<bits/stdc++.h>
#define int long long
using namespace std;
const int N=5e4+5,M=(1<<15)-1,D=60;
inline int read(){
	int x=0,f=1;char ch=getchar();
	while (!isdigit(ch)){if (ch=='-') f=-1;ch=getchar();}
	while (isdigit(ch)){x=x*10+ch-48;ch=getchar();}
	return x*f;
}
int getphi(int n){
	int res=n;
	for(int i=2;i*i<=n;i++){
		if(n%i==0){
			res=res/i*(i-1);
			while(n%i==0)n/=i;
		}		
	}
	if(n>1)res=res/n*(n-1);
	return res;
}
int eul(int n,int p){
	return (n>=p)?(n%p+p):n;
}
int n,m,c,p,cntp,phi[D],c1[D][M+5],c2[D][M+5];
void init(){
	cntp=0,phi[cntp]=p;
	while(phi[cntp]>1)cntp++,phi[cntp]=getphi(phi[cntp-1]);
	for(int i=0;i<=cntp;i++){
		c1[i][0]=c2[i][0]=1;
		for(int j=1;j<=M;j++)c1[i][j]=eul(c1[i][j-1]*c,phi[i]);
		c2[i][1]=eul(c1[i][M]*c,phi[i]);
		for(int j=2;j<=M;j++)c2[i][j]=eul(c2[i][j-1]*c2[i][1],phi[i]);
	}
}
int cpow(int n,int i){
	return eul(c1[i][n&M]*c2[i][n>>15],phi[i]);
}
int calc(int x,int cnt,int i){
	if(!cnt)return eul(x,phi[i]);
	if(i==cntp)return c?1:0;
	return cpow(calc(x,cnt-1,i+1),i);
}
int a[N][D];
struct segtree{
    #define ls rt<<1
    #define rs rt<<1|1
    #define lson l,mid,ls
    #define rson mid+1,r,rs
    struct Node{
        int s,tag;
    }c[N<<2];
    void pushup(int rt){
        c[rt].s=(c[ls].s+c[rs].s)%p;
        c[rt].tag=min(c[ls].tag,c[rs].tag);
    }
    void build(int l,int r,int rt){
    	c[rt].tag=0;
        if(l==r){
            c[rt].s=a[l][0];
            return;
        }
        int mid=(l+r)>>1;build(lson),build(rson);
        pushup(rt);
    }
	void update(int l,int r,int rt,int L,int R){
		if(c[rt].tag>cntp)return;
		if(l==r){
			c[rt].tag++,c[rt].s=a[l][c[rt].tag];
			return;
		}
		int mid=(l+r)>>1;
		if(L<=mid)update(lson,L,R);
		if(R>mid)update(rson,L,R);
		pushup(rt);
	}
    int query(int l,int r,int rt,int L,int R){
        if(L<=l&&r<=R)return c[rt].s;
        int mid=(l+r)>>1,res=0;
        if(L<=mid)res+=query(lson,L,R),res%=p;
        if(R>mid)res+=query(rson,L,R),res%=p;
        return res;
    }
    #undef ls
    #undef rs
    #undef lson
    #undef rson
}Tr;
signed main(){
	n=read(),m=read(),p=read(),c=read();init();
	for(int i=1;i<=n;i++){
		a[i][0]=read();
		for(int j=1;j<=cntp+1;j++)a[i][j]=calc(a[i][0],j,0)%p;
		a[i][0]%=p;
	}
	Tr.build(1,n,1);
	while(m--){
		int op=read();
		if(op==0){
			int l=read(),r=read();
			Tr.update(1,n,1,l,r);
		}
		if(op==1){
			int l=read(),r=read();
			printf("%lld\n",Tr.query(1,n,1,l,r)%p);
		}
	}
	return 0;
}

B - 楼房重建

兔队线段树模板题。

首先问题等价于单点改,查询 \(\dfrac{H_i}{i}\) 有几个严格前缀最大值。考虑兔队线段树。具体地,线段树维护 \(s(l,r)\) 表示 \([l,r]\) 有几个严格前缀最大值,\(m(l,r)\) 表示 \([l,r]\) 的最大值。当合并两个区间 \([l,mid]\)\((mid,r]\) 的时候,令 \(c(l,r,k)\) 表示在 \([l,r]\) 中有多少个严格前缀最大值大于 \(k\),那么 \(s(l,r)=s(l,mid)+c(mid+1,r,m(l,mid))\)。现在问题转化为了如何快速维护 \(c\),发现这可以直接利用线段树的信息来求解。

具体地,若到达叶子可以直接比较;若 \(m(l,mid)\ge k\),说明 \((mid,r]\) 内的所有前缀最大值都大于 \(k\),有 \(c(l,r,k)=c(l,mid,k)+s(l,r)-s(l,mid)\);否则说明左区间不会影响答案,\(c(l,r,k)=c(mid+1,r,k)\)。因为每次只会递归一侧,复杂度是 \(\mathcal{O}(n\log^2n)\) 的。

进一步的,考虑区间信息没有可减性的情况。修改 \(s(l,r)\) 的定义:\(s(l,r)\) 表示考虑 \([l,r]\),在 \((mid,r]\) 内有几个严格前缀最大值。可以类似的得到 \(s(l,r)=c(mid+1,r,m(l,mid))\),当 \(m(l,mid)\ge k\)\(c(l,r,k)=c(l,mid,k)+s(l,r)\),反之 \(c(l,r,k)=c(mid+1,r,k)\)。调用 \(c(1,n,0)\) 即为答案。

点击查看代码
#include<bits/stdc++.h>
#define int long long 
#define db double
using namespace std;
const int inf=1e18; 
inline int read(){
	int x=0,f=1;char ch=getchar();
	while (!isdigit(ch)){if (ch=='-') f=-1;ch=getchar();}
	while (isdigit(ch)){x=x*10+ch-48;ch=getchar();}
	return x*f;
}
int a[100005];db v[100005];
struct segtree{
	#define ls p<<1
	#define rs p<<1|1
	#define lson l,mid,ls
	#define rson mid+1,r,rs
	struct Node{
		int s;db ma;
	}c[400005];
	int ask(int l,int r,int p,db k){
		if(l==r)return (c[p].ma>k);
		int mid=(l+r)>>1;
		if(c[ls].ma<k)return ask(rson,k);
		else return ask(lson,k)+c[p].s;
	} 
	void pushup(int l,int r,int p){
		c[p].ma=max(c[ls].ma,c[rs].ma);
		int mid=(l+r)>>1;c[p].s=ask(rson,c[ls].ma);
	}
	void build(int l,int r,int p){
		if(l==r){c[p].s=0,c[p].ma=0;return;}
		int mid=(l+r)>>1;
		build(lson),build(rson);
		pushup(l,r,p);
	}
	void update(int l,int r,int p,int x,int k){
		if(l==r){a[l]=k,v[l]=k*1.0/l,c[p].s=0,c[p].ma=v[l];return;}
		int mid=(l+r)>>1;
		if(x<=mid)update(lson,x,k);
		else update(rson,x,k);
		pushup(l,r,p);
	}
	#undef ls
	#undef rs
	#undef lson
	#undef rson
}Tr;
signed main(){
	int n=read(),m=read();Tr.build(1,n,1);
	while(m--){
		int x=read(),y=read();Tr.update(1,n,1,x,y);
		printf("%lld\n",Tr.ask(1,n,1,0.0));
	}
	return 0;
}

C - 二分图 /【模板】线段树分治

把每条边出现的时间段分成 \(\log k\) 段,丢到每个线段树上每个区间对应的 vector 里,然后 dfs。考虑并查集判断二分图,每次从父亲进入儿子的时候加上儿子的边,退出时撤销,可以用可撤销并查集维护,复杂度 \(\mathcal{O}(n\log^2n)\)

点击查看代码
#include<bits/stdc++.h>
#define int long long
#define mk make_pair
#define fi first
#define se second
using namespace std;
typedef pair<int,int>pii;
const int inf=1e18;
inline int read(){
	int x=0,f=1;char ch=getchar();
	while (!isdigit(ch)){if (ch=='-') f=-1;ch=getchar();}
	while (isdigit(ch)){x=x*10+ch-48;ch=getchar();}
	return x*f;
}
int fa[200005],h[200005];stack<pii>s;
int find(int x){
	return ((x==fa[x])?x:find(fa[x]));
}
void merge(int x,int y){
	if(x==y)return;
	if(h[x]<h[y])swap(x,y);
	s.push(mk(y,(h[x]==h[y]))),fa[y]=x,h[x]+=(h[x]==h[y]);
}
vector<pii>vec[400005];int n,m,k,ans[100005];
void insert(int l,int r,int p,int L,int R,int x,int y){
	if(L<=l&&r<=R){vec[p].push_back(mk(x,y));return;}
	int mid=(l+r)>>1;
	if(L<=mid)insert(l,mid,p<<1,L,R,x,y);
	if(R>mid)insert(mid+1,r,p<<1|1,L,R,x,y);
}
void recyc(int top){
	while((int)s.size()>top){
		int u=s.top().fi,c=s.top().se;s.pop();
		h[fa[u]]-=c,fa[u]=u;
	}
}
void solve(int l,int r,int p){
	int mid=(l+r)>>1,oldtop=(int)s.size();
	for(int i=0;i<(int)vec[p].size();i++){
		int u=vec[p][i].fi,v=vec[p][i].se;
		int fu=find(u),fv=find(v);
		if(fu==fv){
			for(int j=l;j<=r;j++)ans[j]=0;
			recyc(oldtop);
			return;
		}
		merge(find(u+n),fv),merge(fu,find(v+n));
	}
	if(l!=r)solve(l,mid,p<<1),solve(mid+1,r,p<<1|1);
	else ans[l]=1;
	recyc(oldtop);
}
signed main(){
	n=read(),m=read(),k=read();
	for(int i=1;i<=n+n;i++)fa[i]=i,h[i]=0;
	for(int i=1;i<=m;i++){
		int x=read(),y=read(),l=read()+1,r=read()+1;
		if(l!=r)insert(1,k,1,l,r-1,x,y);
	}
	solve(1,k,1);
	for(int i=1;i<=k;i++)puts(ans[i]?"Yes":"No");
	return 0;
}

D - 岛屿探险

是我不会的题。

看到 \(\min(b,d)\) 第一时间想到分类讨论。当 \(b>d\) 时条件是 \(a\oplus c\le d\),这个显然可以丢到 trie 上维护。但是 \(b\le d\) 时条件变为 \(a\oplus c\le b\),不好做。

发现这个式子具有高度对称性,考虑把询问挂到 trie 上。具体地,我们在插入一对 \((a,b)\) 时,如果 \(b\) 当前位为 1,说明前面与 \(a\oplus b\) 相同,这一位为 0 的这些 \(c\) 合法,这就是一个子树加,打个标记。

最后还需要把 \(b>d\)\(b\le d\) 的情况分离开。考虑把点和询问丢到一起按 \(b\)\(d\) 从小到大排序,直接 CDQ 就完了。

点击查看代码
#include<bits/stdc++.h>
#define int long long
using namespace std;
const int inf=1e18;
inline int read(){
	int x=0,f=1;char ch=getchar();
	while (!isdigit(ch)){if (ch=='-') f=-1;ch=getchar();}
	while (isdigit(ch)){x=x*10+ch-48;ch=getchar();}
	return x*f;
}
struct Trie1{//solver for b<=d
	int T=1,son[13500005][2],tag[13500005];
	int ch(int p,int o){
		if(!son[p][o])son[p][o]=++T;
		return son[p][o];
	}
	void clear(){
		for(int i=1;i<=T;i++)son[i][0]=son[i][1]=tag[i]=0;
		T=1;
	}
	void insert(int a,int b){
		int u=1;
		for(int i=23;i>=0;i--){
			int oa=(a>>i)&1ll,ob=(b>>i)&1ll;
			if(ob)tag[ch(u,oa)]++;
			u=ch(u,oa^ob);
		}
		tag[u]++;
	}
	int ask(int c){
		int u=1,res=0;
		for(int i=23;i>=0;i--){
			int oc=(c>>i)&1ll;
			res+=tag[u],u=ch(u,oc);
		}
		res+=tag[u];
		return res;
	}
}Tr1;
int root[100005];
struct Trie2{//solver for b>d
	int T,son[13500005][2],cnt[13500005];
	void clear(){
		for(int i=1;i<=T;i++)son[i][0]=son[i][1]=cnt[i]=0;
		T=0;
	}
	void insert(int p,int q,int x){
		for(int i=23;i>=0;i--){
			int o=(x>>i)&1ll;
			if(!son[p][o])son[p][o]=++T;
			son[p][o^1]=son[q][o^1];
			cnt[p]=cnt[q]+1,p=son[p][o],q=son[q][o];
		}
		cnt[p]=cnt[q]+1;
	}
	int ask(int p,int a,int b){
		int res=0;
		for(int i=23;i>=0;i--){
			int oa=(a>>i)&1ll,ob=(b>>i)&1ll;
			if(ob==0)p=son[p][oa];
			else res+=cnt[son[p][oa]],p=son[p][oa^1];
		}
		res+=cnt[p];
		return res;
	}
}Tr2;
struct Que{
	int id,pos,a,b,op,val;
}q[300005],p[300005];
int cmp1(Que x,Que y){
	if(x.b^y.b)return x.b<y.b;
	return x.op>y.op;
}
int cmp2(Que x,Que y){
	if(x.pos^y.pos)return x.pos<y.pos;
	return x.op<y.op;
}
int tot,B[100005],ans[100005];
void solve(int l,int r){
	if(l==r)return;
	int mid=(l+r)>>1,cnt=0;solve(l,mid);solve(mid+1,r);
	cnt=0;Tr1.clear();//b<=d
	for(int i=l;i<=mid;i++)if(q[i].op==1)p[++cnt]=q[i];
	for(int i=mid+1;i<=r;i++)if(q[i].op==2)p[++cnt]=q[i];
	sort(p+1,p+cnt+1,cmp2);
	for(int i=1;i<=cnt;i++){
		if(p[i].op==1)Tr1.insert(p[i].a,p[i].b);
		else ans[p[i].id]+=p[i].val*Tr1.ask(p[i].a);
	}
	cnt=tot=0;Tr2.clear();//b>d
	for(int i=mid+1;i<=r;i++)if(q[i].op==1)B[++tot]=q[i].pos,p[++cnt]=q[i];
	sort(p+1,p+cnt+1,cmp2);sort(B+1,B+tot+1);
	for(int i=1;i<=cnt;i++)root[i]=++Tr2.T,Tr2.insert(root[i],root[i-1],p[i].a);
	for(int i=l;i<=mid;i++){
		if(q[i].op==1)continue;
		int pos=upper_bound(B+1,B+tot+1,q[i].pos)-B-1;
		if(pos<1||pos>tot||B[pos]>q[i].pos)continue;
		ans[q[i].id]+=q[i].val*Tr2.ask(root[pos],q[i].a,q[i].b);
	}
}
signed main(){
	int n=read(),m=read(),qt=0;
	for(int i=1,a,b;i<=n;i++)a=read(),b=read(),q[++qt]=(Que){i,i,a,b,1,0};
	for(int i=1,l,r,c,d;i<=m;i++)l=read(),r=read(),c=read(),d=read(),q[++qt]=(Que){i,l-1,c,d,2,-1},q[++qt]=(Que){i,r,c,d,2,1};
	sort(q+1,q+qt+1,cmp1);solve(1,qt);
	for(int i=1;i<=m;i++)printf("%lld\n",ans[i]);
	return 0;
}

E - 归程

kruskal 重构树。

把所有边按海拔从大到小排序,然后依次加边。每次加 \((u,v,l,a)\) 的时候新建一个点权为 \(a\) 的节点,然后把 \(u,v\) 所在连通块的根与新点连边。那么在原图中 \(u\to v\) 路径上海拔最小的点就是新图上 \(\text{lca}(u,v)\) 的点权。对于每个询问,我们可以从 \(v\) 往上跳到最后一个点权大于 \(p\) 的点 \(u\),那么 \(u\) 子树内所有点是可以互相到达的,只需要求出这些点中到 1 最近的距离,这个可以 dijkstra 预处理,找 \(u\) 可以倍增。数组不要开小。

点击查看代码
#include<bits/stdc++.h>
#define ll long long
#define mk make_pair
#define fi first
#define se second
using namespace std;
typedef pair<ll,int>pii;
const ll inf=1e18;
inline int read(){
	int x=0,f=1;char ch=getchar();
	while (!isdigit(ch)){if (ch=='-') f=-1;ch=getchar();}
	while (isdigit(ch)){x=x*10+ch-48;ch=getchar();}
	return x*f;
}
struct edge{
	int v,w,nxt;
}e[1600005];
int tot,head[400005];
void add(int u,int v,int w){
	e[++tot]=(edge){v,w,head[u]},head[u]=tot;
}
int n,m,cur,q,k,s,vis[200005];ll lastans,d[400005];
void dijkstra(int s){
	for(int i=1;i<=n;i++)d[i]=inf,vis[i]=0;
	priority_queue<pii,vector<pii>,greater<pii> >q;
	d[s]=0;q.push(mk(d[s],s));
	while(!q.empty()){
		int u=q.top().se;q.pop();
		if(vis[u])continue;
		vis[u]=1;
		for(int i=head[u];i;i=e[i].nxt){
			int v=e[i].v,w=e[i].w;
			if(d[v]>d[u]+w)d[v]=d[u]+w,q.push(mk(d[v],v));
		}
	}
}
int pa[400005][25],dep[400005];
void dfs(int u,int fa){
	dep[u]=dep[fa]+1,pa[u][0]=fa;if(u>n)d[u]=inf;
	for(int i=0;i<=22;i++)pa[u][i+1]=pa[pa[u][i]][i];
	for(int i=head[u];i;i=e[i].nxt){
		int v=e[i].v;if(v==fa)continue;
		dfs(v,u);d[u]=min(d[u],d[v]);
	}
}
int getlca(int x,int y){
	if(dep[x]>dep[y])swap(x,y);
	for(int i=22;i>=0&&dep[x]!=dep[y];i--)if(dep[y]-dep[x]>=(1<<i))y=pa[y][i];
	if(x==y)return x;
	for(int i=22;i>=0;i--)if(pa[x][i]!=pa[y][i])x=pa[x][i],y=pa[y][i];
	return pa[x][0];
}
int p[400005];
int jump(int u,int v){
	for(int i=22;i>=0;i--)if(pa[u][i]&&p[pa[u][i]]>v)u=pa[u][i];
	return u;
}
struct Node{
	int u,v,l,a;
}g[400005];
int cmp(Node x,Node y){
	return x.a>y.a;
}
int fa[400005];
int find(int x){
	return ((x==fa[x])?x:fa[x]=find(fa[x]));
}
void solve(){
	n=read(),m=read(),lastans=0,cur=n;
	for(int i=1;i<=m;i++){
		g[i].u=read(),g[i].v=read(),g[i].l=read(),g[i].a=read();
		add(g[i].u,g[i].v,g[i].l),add(g[i].v,g[i].u,g[i].l);
	}
	sort(g+1,g+m+1,cmp);dijkstra(1);
	tot=0;for(int i=1;i<=2*n;i++)head[i]=0,fa[i]=i,p[i]=0;
	for(int i=1;i<=m;i++){
		int u=g[i].u,v=g[i].v,a=g[i].a,fu=find(u),fv=find(v);
		if(fu!=fv)p[++cur]=a,add(cur,fu,1),add(fu,cur,1),add(cur,fv,1),add(fv,cur,1),fa[fu]=fa[fv]=cur;
	}
	dfs(cur,0);
	q=read(),k=read(),s=read();
	while(q--){
		int v=(read()+1ll*k*lastans%n-1)%n+1,p=(read()+1ll*k*lastans%(s+1))%(s+1);
		v=jump(v,p);printf("%lld\n",(lastans=d[v]));
	}
	tot=0;for(int i=1;i<=2*n;i++)head[i]=0;
}
signed main(){
	int T=read();
	while(T--){
		solve();
	} 
	return 0;
}

F - 可持久化并查集

按秩合并,可持久化父亲和高度/大小。

感觉还是维护高度好一点吧,虽然大小也是对的。

点击查看代码
#include<bits/stdc++.h>
using namespace std;
const int inf=1e9;
inline int read(){
	int x=0,f=1;char ch=getchar();
	while (!isdigit(ch)){if (ch=='-') f=-1;ch=getchar();}
	while (isdigit(ch)){x=x*10+ch-48;ch=getchar();}
	return x*f;
}
int n,m,ver,_[200005];
struct segtree{
	#define ls c[p].lc
	#define rs c[p].rc
	#define lson l,mid,ls
	#define rson mid+1,r,rs
	struct Node{
		int c,lc,rc;
	}c[7000005];
	int T,root[200005];
	int build(int l,int r){
		int p=++T;
		if(l==r){c[p].c=_[l];return p;}
		int mid=(l+r)>>1;
		ls=build(l,mid),rs=build(mid+1,r);
		return p;
	}
	int update(int l,int r,int q,int x,int k){
		int p=++T;c[p]=c[q];
		if(l==r){c[p].c=k;return p;}
		int mid=(l+r)>>1;
		if(x<=mid)ls=update(l,mid,c[q].lc,x,k);
		else rs=update(mid+1,r,c[q].rc,x,k);
		return p;
	}
	int query(int l,int r,int p,int x){
		if(l==r)return c[p].c;
		int mid=(l+r)>>1;
		if(x<=mid)return query(lson,x);
		else return query(rson,x);
	}
	#undef ls
	#undef rs
	#undef lson
	#undef rson
}TrFa,TrSiz;
int find(int x){
	while(1){
		int fa=TrFa.query(1,n,TrFa.root[ver],x);
		if(fa!=x)x=fa;else break;
	}
	return x;
}
void merge(int x,int y){
	int a=find(x),b=find(y);if(a==b)return;
	int sza=TrSiz.query(1,n,TrSiz.root[ver],a),szb=TrSiz.query(1,n,TrSiz.root[ver],b);
	if(sza>szb)swap(x,y),swap(a,b),swap(sza,szb);
	TrFa.root[ver]=TrFa.update(1,n,TrFa.root[ver],a,b);
	TrSiz.root[ver]=TrSiz.update(1,n,TrSiz.root[ver],b,sza+szb);
}
void recall(int k){
	TrFa.root[ver]=TrFa.root[k];
	TrSiz.root[ver]=TrSiz.root[k];
}
int ask(int x,int y){
	int a=find(x),b=find(y);
	return (a==b);
}
signed main(){
	n=read(),m=read();
	for(int i=1;i<=n;i++)_[i]=i;
	TrFa.root[0]=TrFa.build(1,n);
	for(int i=1;i<=n;i++)_[i]=1;
	TrSiz.root[0]=TrSiz.build(1,n);
	for(ver=1;ver<=m;ver++){
		TrFa.root[ver]=TrFa.root[ver-1];
		TrSiz.root[ver]=TrSiz.root[ver-1];
		int op=read(),a,b;
		if(op==1)a=read(),b=read(),merge(a,b);
		else if(op==2)a=read(),recall(a); 
		else a=read(),b=read(),printf("%d\n",ask(a,b));
	}
	return 0;
}

G - 整数序列

考虑根号分治。记一个阈值 \(S\),当 \(cnt_x\)\(cnt_y\) 都小于 \(S\) 的时候可以跑 \(\mathcal{O}(\sum(cnt_x+cnt_y))\) 的暴力;当 \(cnt_x\)\(cnt_y\) 都不小于 \(S\) 时这样的 \(x,y\) 不超过 \(\dfrac{n}{S}\) 个,令这些数为 \(p_1\ldots p_m\),极限情况有 \(\sum\limits_{i=1}^mcnt_i=n\),两两配对后复杂度为 \(\sum\limits_{i=1}^m\sum\limits_{j=i+1}^mcnt_i+cnt_j<m\sum\limits_{i=1}^mcnt_i=mn\),故这部分复杂度是 \(\mathcal{O}(\dfrac{n^2}{S})\)

考虑一大一小的情况。令 \(cnt_x<S,cnt_y\ge S\)。容易发现并不是所有的为 \(y\) 的位置都有可能成为端点,具体的,对于一段连续 \(l\) 个 x,向左向右各找 \(l+1\) 个 y,找过的跳过,那么这些 y 就是可能成为端点的位置,然后跑暴力,复杂度 \(\mathcal{O}(\sum cnt_x\log cnt_y)\)

为什么要找 \(l+1\) 个 y 捏?因为你现在找的左端点其实是原来的左端点 -1 啊。

调试的过程有点久,主要是因为在家里没什么动力写题。

点击查看代码
#include<bits/stdc++.h>
#define int long long
using namespace std;
const int inf=1e18; 
inline int read(){
	int x=0,f=1;char ch=getchar();
	while (!isdigit(ch)){if (ch=='-') f=-1;ch=getchar();}
	while (isdigit(ch)){x=x*10+ch-48;ch=getchar();}
	return x*f;
}
int n,q,T,a[300005],b[300005],id[300005],siz[300005],pos[600005],B[600005];
vector<int>v[300005],sum[300005];set<int>s[300005];
map<int,int>vis[300005];
signed main(){
	n=read(),q=read(),T=500;
	for(int i=1;i<=n;i++)a[i]=read(),v[a[i]].push_back(i),id[i]=(int)v[a[i]].size()-1;
	for(int i=1;i<=n;i++)b[i]=read();
	for(int i=1;i<=n;i++){
		siz[i]=(int)v[i].size();sum[i].resize(siz[i]+5);
		if(!siz[i])continue;
		for(int j=0;j<siz[i];j++)s[i].insert(v[i][j]);
		sum[i][0]=b[v[i][0]];for(int j=1;j<siz[i];j++)sum[i][j]=sum[i][j-1]+b[v[i][j]];
	}
	for(int i=0;i<=n+n;i++)B[i]=inf;
	while(q--){
		int x=read(),y=read(),ans=-inf;
		if(vis[x].count(y)){printf("%lld\n",vis[x][y]);continue;}
		if((siz[x]<=T&&siz[y]<=T)||(siz[x]>T&&siz[y]>T)){
			int cx=0,cy=0,tot=0;
			while(cx<siz[x]&&cy<siz[y]){
				if(v[x][cx]<v[y][cy])pos[++tot]=v[x][cx],cx++;
				else pos[++tot]=v[y][cy],cy++;
			}
			while(cx<siz[x])pos[++tot]=v[x][cx],cx++;
			while(cy<siz[y])pos[++tot]=v[y][cy],cy++;
			int cnt=0,val=0;B[cnt+siz[y]]=val;
			for(int i=1;i<=tot;i++){
				cnt+=((a[pos[i]]==x)?1:-1),val+=b[pos[i]];
				ans=max(ans,val-B[cnt+siz[y]]);
				B[cnt+siz[y]]=min(B[cnt+siz[y]],val);
			}
			cnt=0,val=0;B[cnt+siz[y]]=inf;
			for(int i=1;i<=tot;i++){
				cnt+=((a[pos[i]]==x)?1:-1),val+=b[pos[i]];
				B[cnt+siz[y]]=inf;
			}
		}
		else{
			if(siz[x]>siz[y])swap(x,y);
			int tot=0;
			for(int i=0;i<siz[x];i++){
				if(s[y].empty())break;
				auto it=s[y].lower_bound(v[x][i]);
				if(it==s[y].begin())continue;
				it=prev(it),pos[++tot]=*it;
				if(it==s[y].begin()){s[y].erase(it);continue;}
				auto tmp=prev(it);s[y].erase(it),it=tmp,pos[++tot]=*it,s[y].erase(it);
			}
			for(int i=0;i<siz[x];i++){
				if(s[y].empty())break;
				auto it=s[y].lower_bound(v[x][i]);
				if(it==s[y].end())continue;
				pos[++tot]=*it;
				auto tmp=next(it);s[y].erase(it),it=tmp;
				if(it==s[y].end())continue;
				pos[++tot]=*it,s[y].erase(it);
			}
			for(int i=1;i<=tot;i++)s[y].insert(pos[i]);
			for(int i=0;i<siz[x];i++)pos[++tot]=v[x][i];
			sort(pos+1,pos+tot+1);
			int cnt=0,val=0,lstx=-1,lsty=-1;B[cnt+siz[y]]=val;
			for(int i=1;i<=tot;i++){
				if(a[pos[i]]==x)cnt+=id[pos[i]]-lstx,val+=sum[x][id[pos[i]]]-((lstx==-1)?0:sum[x][lstx]),lstx=id[pos[i]];
				else cnt-=id[pos[i]]-lsty,val+=sum[y][id[pos[i]]]-((lsty==-1)?0:sum[y][lsty]),lsty=id[pos[i]];
				ans=max(ans,val-B[cnt+siz[y]]);
				B[cnt+siz[y]]=min(B[cnt+siz[y]],val);
			}
			cnt=0,lstx=-1,lsty=-1;B[cnt+siz[y]]=inf;
			for(int i=1;i<=tot;i++){
				if(a[pos[i]]==x)cnt+=id[pos[i]]-lstx,lstx=id[pos[i]];
				else cnt-=id[pos[i]]-lsty,lsty=id[pos[i]];
				B[cnt+siz[y]]=inf;
			}
		}
		printf("%lld\n",(vis[x][y]=vis[y][x]=ans));
	}
	return 0;
}

H - Count on a tree II/【模板】树分块

考虑在树上每隔 \(S\) 个点就撒一个关键点,预处理关键点间的颜色情况,存到 bitset 里。时间复杂度 \(\mathcal{O}(n\log n+n+\dfrac{n^3}{S^2w})\),空间复杂度 \(\mathcal{O}(\dfrac{n^3}{S^2w})\)

查询就是令 \(t=\text{lca}(u,v)\),先从 \(u,v\) 暴力跳到一个关键点,然后跳到 \(t\) 之下最后一个关键点,然后再暴力跳到 \(t\) 即可。时间复杂度 \(\mathcal{O}(m(\log n+S+\dfrac{n}{S}+\dfrac{n}{w}))\)

\(S\)\(\sqrt{n}\) 时有最优时间复杂度 \(\mathcal{O}((n+m)\log n+m\sqrt{n}+\dfrac{nm+n^2}{w})\),可以适当调大 \(S\) 来获得更优的空间复杂度。

点击查看代码
#include<bits/stdc++.h>
using namespace std;
const int inf=1e9;
inline int read(){
	int x=0,f=1;char ch=getchar();
	while (!isdigit(ch)){if (ch=='-') f=-1;ch=getchar();}
	while (isdigit(ch)){x=x*10+ch-48;ch=getchar();}
	return x*f;
}
struct edge{
	int v,nxt;
}e[80005];
int tot,head[40005];
void add(int u,int v){
	e[++tot]=(edge){v,head[u]},head[u]=tot;
}
int n,m,lastans,siz,cur,tag[40005],a[40005],rnk[40005],pa[40005][20],dep[40005];
void dfs1(int u,int fa,int dis){
	if(dis==siz)tag[u]=++cur,rnk[cur]=u,dis=0;
	dep[u]=dep[fa]+1,pa[u][0]=fa;
	for(int i=0;i<=16;i++)pa[u][i+1]=pa[pa[u][i]][i];
	for(int i=head[u];i;i=e[i].nxt){
		int v=e[i].v;if(v==fa)continue;
		dfs1(v,u,dis+1);
	}
}
int top,s[40005],pre[40005];bitset<40005>B[55][55],tmp;
void dfs2(int u,int fa){
	if(tag[u]){
		tmp.reset();tmp.set(a[u]);B[tag[u]][tag[u]].set(a[u]);
		int p=pa[u][0];while(p&&!tag[p])tmp.set(a[p]),p=pa[p][0];
		for(int i=1;i<=top;i++)B[s[i]][tag[u]]=B[s[i]][s[top]]|tmp;
		pre[u]=rnk[s[top]],s[++top]=tag[u];
	}
	for(int i=head[u];i;i=e[i].nxt){
		int v=e[i].v;if(v==fa)continue;
		dfs2(v,u);
	}
	if(tag[u])top--;
}
int getlca(int x,int y){
	if(dep[x]>dep[y])swap(x,y);
	for(int i=16;i>=0&&dep[x]!=dep[y];i--)if(dep[y]-dep[x]>=(1<<i))y=pa[y][i];
	if(x==y)return x;
	for(int i=16;i>=0;i--)if(pa[x][i]!=pa[y][i])x=pa[x][i],y=pa[y][i];
	return pa[x][0];
}
int cnt,b[40005];
signed main(){
	n=read(),m=read(),lastans=0,siz=1000;
	for(int i=1;i<=n;i++)a[i]=b[++cnt]=read();
	sort(b+1,b+cnt+1);cnt=unique(b+1,b+cnt+1)-b-1;
	for(int i=1;i<=n;i++)a[i]=lower_bound(b+1,b+cnt+1,a[i])-b;
	for(int i=1,u,v;i<n;i++)u=read(),v=read(),add(u,v),add(v,u);
	dfs1(1,0,siz);dfs2(1,0);
	while(m--){
		int u=read()^lastans,v=read(),lca=getlca(u,v);tmp.reset();
		while(!tag[u]&&u!=lca)tmp.set(a[u]),u=pa[u][0];
		while(!tag[v]&&v!=lca)tmp.set(a[v]),v=pa[v][0];
		int tu=u,tv=v;
		while(pre[u]&&dep[pre[u]]>=dep[lca])u=pre[u];
		while(pre[v]&&dep[pre[v]]>=dep[lca])v=pre[v];
		tmp|=B[tag[u]][tag[tu]]|B[tag[v]][tag[tv]];
		while(u&&u!=lca)tmp.set(a[u]),u=pa[u][0];
		while(v&&v!=lca)tmp.set(a[v]),v=pa[v][0];
		tmp.set(a[lca]);
		printf("%d\n",(lastans=tmp.count()));
	}
	return 0;
}

I - 二逼平衡树(树套树)

开一颗线段树,每个线段树节点开一颗平衡树,不想写。

考虑序列分块套值域分块。定义 \(f_{i,j}\) 表示前 \(i\) 个块值落在值域上第 \(j\) 个块的数的个数,\(g_{i,j}\) 表示前 \(i\) 个块落在值域上 \(j\) 这个点的个数。实现略麻烦,但思路清晰,复杂度 \(\mathcal{O}(n\sqrt{n})\),需要离散化。

点击查看代码
#include<bits/stdc++.h>
#define int long long
using namespace std;
const int inf=1e18,INF=2147483647;
inline int read(){
	int x=0,f=1;char ch=getchar();
	while (!isdigit(ch)){if (ch=='-') f=-1;ch=getchar();}
	while (isdigit(ch)){x=x*10+ch-48;ch=getchar();}
	return x*f;
}
int n,nsiz,nnum,nbel[50005],nL[505],nR[505];
int tot,tsiz,tnum,tbel[100005],tL[505],tR[505];
int m,a[50005],b[100005],f[505][505],g[505][100005],cntf[505],cntg[100005];
struct Que{
	int op,l,r,v;
}q[50005];
int getrank(int l,int r,int v){
	int res=0;
	if(nbel[r]-nbel[l]<=1){
		for(int i=l;i<=r;i++)res+=(a[i]<v);
		return res+1;
	}
	for(int i=l;i<=nR[nbel[l]];i++)res+=(a[i]<v);
	for(int i=1;i<tbel[v];i++)res+=f[nbel[r]-1][i]-f[nbel[l]][i];
	for(int i=tL[tbel[v]];i<v;i++)res+=g[nbel[r]-1][i]-g[nbel[l]][i];
	for(int i=nL[nbel[r]];i<=r;i++)res+=(a[i]<v);
	return res+1;
}
void add(int l,int r,int v){
	for(int i=l;i<=nR[nbel[l]];i++)cntf[tbel[a[i]]]+=v,cntg[a[i]]+=v;
	for(int i=nL[nbel[r]];i<=r;i++)cntf[tbel[a[i]]]+=v,cntg[a[i]]+=v;
}
int getval(int l,int r,int v){
	int sum=0;add(l,r,1);
	for(int i=1;i<=tnum;i++){
		if(f[nbel[r]-1][i]-f[nbel[l]][i]+cntf[i]&&sum+f[nbel[r]-1][i]-f[nbel[l]][i]+cntf[i]+1>v){
			for(int j=tL[i];j<=tR[i];j++){
				if(g[nbel[r]-1][j]-g[nbel[l]][j]+cntg[j]&&sum+g[nbel[r]-1][j]-g[nbel[l]][j]+cntg[j]+1>v)return add(l,r,-1),b[j];
				sum+=g[nbel[r]-1][j]-g[nbel[l]][j]+cntg[j];
			}
			return add(l,r,-1),-INF;	
		}
		sum+=f[nbel[r]-1][i]-f[nbel[l]][i]+cntf[i];
	}
	return add(l,r,-1),-INF;
}
void update(int p,int v){
	for(int i=nbel[p];i<=nnum;i++)f[i][tbel[a[p]]]--,g[i][a[p]]--;
	a[p]=v;
	for(int i=nbel[p];i<=nnum;i++)f[i][tbel[a[p]]]++,g[i][a[p]]++;
}
int getpre(int l,int r,int v){
	add(l,r,1);
	for(int i=v-1;i>=tL[tbel[v]];i--){
		if(g[nbel[r]-1][i]-g[nbel[l]][i]+cntg[i])return add(l,r,-1),b[i];
	}
	for(int i=tbel[v]-1;i>=1;i--){
		if(f[nbel[r]-1][i]-f[nbel[l]][i]+cntf[i]){
			for(int j=tR[i];j>=tL[i];j--){
				if(g[nbel[r]-1][j]-g[nbel[l]][j]+cntg[j])return add(l,r,-1),b[j];
			}
			return add(l,r,-1),-INF;
		}
	}
	return add(l,r,-1),-INF;
}
int getsuf(int l,int r,int v){
	add(l,r,1);
	for(int i=v+1;i<=tR[tbel[v]];i++){
		if(g[nbel[r]-1][i]-g[nbel[l]][i]+cntg[i])return add(l,r,-1),b[i];
	}
	for(int i=tbel[v]+1;i<=tnum;i++){
		if(f[nbel[r]-1][i]-f[nbel[l]][i]+cntf[i]){
			for(int j=tL[i];j<=tR[i];j++){
				if(g[nbel[r]-1][j]-g[nbel[l]][j]+cntg[j])return add(l,r,-1),b[j];
			}
			return add(l,r,-1),INF;
		}
	}
	return add(l,r,-1),INF;
}
signed main(){
	n=read(),m=read();
	for(int i=1;i<=n;i++)a[i]=b[++tot]=read();
	for(int i=1;i<=m;i++){
		int op=read(),l,r,v;
		if(op==3)l=read(),v=read(),q[i]=(Que){op,l,l,v};
		else l=read(),r=read(),v=read(),q[i]=(Que){op,l,r,v};
		if(op!=2)b[++tot]=q[i].v;
	}
	sort(b+1,b+tot+1);tot=unique(b+1,b+tot+1)-b-1;
	for(int i=1;i<=n;i++)a[i]=lower_bound(b+1,b+tot+1,a[i])-b;
	for(int i=1;i<=m;i++)if(q[i].op!=2)q[i].v=lower_bound(b+1,b+tot+1,q[i].v)-b;
	nsiz=(int)sqrt(n),nnum=(n+nsiz-1)/nsiz;
	tsiz=(int)sqrt(tot),tnum=(tot+tsiz-1)/tsiz;
	for(int i=1;i<=n;i++)nbel[i]=(i-1)/nsiz+1;
	for(int i=1;i<=tot;i++)tbel[i]=(i-1)/tsiz+1;
	for(int i=1;i<=nnum;i++)nL[i]=(i-1)*nsiz+1,nR[i]=i*nsiz;
	for(int i=1;i<=tnum;i++)tL[i]=(i-1)*tsiz+1,tR[i]=i*tsiz;
	nR[nnum]=n,tR[tnum]=tot;
	for(int i=1;i<=nnum;i++){
		for(int j=1;j<=tnum;j++)f[i][j]=f[i-1][j];
		for(int j=1;j<=tot;j++)g[i][j]=g[i-1][j];
		for(int j=nL[i];j<=nR[i];j++)f[i][tbel[a[j]]]++,g[i][a[j]]++;
	}
	for(int i=1;i<=m;i++){
		int op=q[i].op,l=q[i].l,r=q[i].r,v=q[i].v;
		if(op==1)printf("%lld\n",getrank(l,r,v));
		else if(op==2)printf("%lld\n",getval(l,r,v));
		else if(op==3)update(l,v);
		else if(op==4)printf("%lld\n",getpre(l,r,v));
		else printf("%lld\n",getsuf(l,r,v));
	}
	return 0;
}

J - 维护数列

平衡树板子。

点击查看代码
#include<bits/stdc++.h>
#define ull unsigned long long
using namespace std;
const int inf=1e9,P=1145141;
inline int read(){
	int x=0,f=1;char ch=getchar();
	while (!isdigit(ch)){if (ch=='-') f=-1;ch=getchar();}
	while (isdigit(ch)){x=x*10+ch-48;ch=getchar();}
	return x*f;
}
int a[500005];
mt19937 rnd(19260817);
struct TangGeneral{
	#define ls(p) (c[p].lc)
	#define rs(p) (c[p].rc)
	#define val(p) (c[p].val)
	#define pri(p) (c[p].pri)
	#define siz(p) (c[p].siz)
	#define sum(p) (c[p].sum)
	#define lseg(p) (c[p].lseg)
	#define rseg(p) (c[p].rseg)
	#define seg(p) (c[p].seg)
	#define cov(p) (c[p].cov)
	#define rev(p) (c[p].rev)
	struct Node{
		int lc,rc,val,pri,siz,sum,lseg,rseg,seg,cov,rev;
	}c[500005];
	int T,Root;stack<int>s;
	void build(){
		T=0;while(!s.empty())s.pop();
	}
	int newnode(int val){
		int u,seed=rnd()%P;
		if(s.empty())u=++T;
		else u=s.top(),s.pop();
		c[u]=(Node){0,0,val,seed,1,val,val,val,val,-inf,0};
		return u;
	}
	void pushup(int p){
		siz(p)=siz(ls(p))+siz(rs(p))+1;
		sum(p)=sum(ls(p))+sum(rs(p))+val(p);
		lseg(p)=max(lseg(ls(p)),sum(ls(p))+val(p)+max(0,lseg(rs(p))));
		rseg(p)=max(rseg(rs(p)),max(0,rseg(ls(p)))+val(p)+sum(rs(p)));
		seg(p)=max({(ls(p)?seg(ls(p)):-inf),(rs(p)?seg(rs(p)):-inf),max(0,rseg(ls(p)))+val(p)+max(0,lseg(rs(p)))});
	}
	void pushdown(int p){
		if(cov(p)!=-inf){
			val(ls(p))=cov(p),val(rs(p))=cov(p);
			sum(ls(p))=siz(ls(p))*cov(p),sum(rs(p))=siz(rs(p))*cov(p);
			lseg(ls(p))=rseg(ls(p))=seg(ls(p))=(cov(p)>=0?siz(ls(p)):1)*cov(p);
			lseg(rs(p))=rseg(rs(p))=seg(rs(p))=(cov(p)>=0?siz(rs(p)):1)*cov(p);
			cov(ls(p))=cov(p),cov(rs(p))=cov(p);
			cov(p)=-inf;
		}
		if(rev(p)){
			swap(lseg(ls(p)),rseg(ls(p))),swap(lseg(rs(p)),rseg(rs(p)));
			swap(ls(ls(p)),rs(ls(p))),swap(ls(rs(p)),rs(rs(p)));
			rev(ls(p))^=1;rev(rs(p))^=1;
			rev(p)=0;		
		}
	}
	void splits(int p,int siz,int &x,int &y){
		pushdown(p);if(!p){x=y=0;return;}
		if(siz(ls(p))+1<=siz)x=p,splits(rs(p),siz-siz(ls(p))-1,rs(x),y);
		else y=p,splits(ls(p),siz,x,ls(y));
		pushup(p);
	}
	int merge(int x,int y){
		if(!x||!y)return x+y;
		if(pri(x)>pri(y)){
			pushdown(x);rs(x)=merge(rs(x),y);
			return pushup(x),x;
		}
		else{
			pushdown(y);ls(y)=merge(x,ls(y));
			return pushup(y),y;
		}
	}
	void recyc(int p){
		s.push(p);
		if(ls(p))recyc(ls(p));
		if(rs(p))recyc(rs(p));
	}
	void insert(int pos,int tot){
		int x,y;splits(Root,pos,x,y);
		for(int i=1;i<=tot;i++)x=merge(x,newnode(a[i]));
		Root=merge(x,y);
	}
	void erase(int pos,int tot){
		int x,y,z;splits(Root,pos-1,x,y);splits(y,tot,y,z);
		recyc(y);Root=merge(x,z);
	}
	void cover(int pos,int tot,int col){
		int x,y,z;splits(Root,pos-1,x,y);splits(y,tot,y,z);
		val(y)=col,sum(y)=siz(y)*col,lseg(y)=rseg(y)=seg(y)=(col>=0?siz(y):1)*col,cov(y)=col;
		Root=merge(merge(x,y),z);		
	}
	void rever(int pos,int tot){
		int x,y,z;splits(Root,pos-1,x,y);splits(y,tot,y,z);
		swap(ls(y),rs(y));swap(lseg(y),rseg(y));rev(y)^=1;
		Root=merge(merge(x,y),z);
	}
	int asksum(int pos,int tot){
		int x,y,z;splits(Root,pos-1,x,y);splits(y,tot,y,z);
		int res=sum(y);Root=merge(merge(x,y),z);
		return res;
	}
	int askseg(){
		return seg(Root);
	}
	#undef ls
	#undef rs
	#undef val
	#undef pri
	#undef siz
	#undef sum
	#undef lseg
	#undef rseg
	#undef seg
	#undef cov
	#undef rev
}Tr;
char op[25];
signed main(){
	int n=read(),m=read();Tr.build();
	for(int i=1;i<=n;i++)a[i]=read();
	Tr.insert(0,n);
	while(m--){
		scanf("%s",op);
		if(op[2]=='S'){
			int pos=read(),tot=read();
			for(int i=1;i<=tot;i++)a[i]=read();
			Tr.insert(pos,tot);
		}
		else if(op[2]=='L'){
			int pos=read(),tot=read();
			Tr.erase(pos,tot);
		}
		else if(op[2]=='K'){
			int pos=read(),tot=read(),col=read();
			Tr.cover(pos,tot,col);	
		}
		else if(op[2]=='V'){
			int pos=read(),tot=read();
			Tr.rever(pos,tot);			
		}
		else if(op[2]=='T'){
			int pos=read(),tot=read();
			printf("%d\n",Tr.asksum(pos,tot));
		}
		else{
			printf("%d\n",Tr.askseg()); 
		}
	}
	return 0;
}

K - 密码箱

L - 宝石

M - The Tree

神奇 trick 题。考虑对询问分块,每次把涉及到的所有点拉出来建虚树,记录原图上两点间点个数和白色点个数。几个操作都可以暴力维护,因为只会涉及至多 \(\sqrt{q}\) 个点。修改完后根据虚树上两点间白点个数可以还原原图,二操作可以看打的标记,复杂度 \(\mathcal{O}((n+q)\sqrt{q})\)

有别的做法,还不会。

点击查看代码
#include<bits/stdc++.h>
using namespace std;
const int inf=1e9;
inline int read(){
	int x=0,f=1;char ch=getchar();
	while (!isdigit(ch)){if (ch=='-') f=-1;ch=getchar();}
	while (isdigit(ch)){x=x*10+ch-48;ch=getchar();}
	return x*f;
}
struct edge{int v,w,d;};vector<int>g[100005];vector<edge>G[100005];
int n,q,siz,num,col[100005],cnt[100005],cov[100005],tag[100005];
void build(int u,int rt,int w,int d){
	for(auto v:g[u]){
		if(tag[v])G[rt].push_back((edge){v,w,d}),build(v,v,0,0);
		else build(v,rt,w+(!col[v]),d+1);
	}
}
void update(int u){
	if(col[u]==0)return col[u]=1,void();
	cnt[u]++;for(auto x:G[u])if(cnt[u]>x.w)update(x.v);
}
void cover(int u){
	cnt[u]=col[u]=0,cov[u]=1;
	for(auto &x:G[u])x.w=x.d,cover(x.v);
}
void rebuild(int u,int c,int t){
	if(tag[u])c=cnt[u],t|=cov[u];
	else{
		if(t)col[u]=0;
		if(!col[u]&&c)c--,col[u]=1;
	}
	for(auto v:g[u])rebuild(v,c,t);
}
int op[100005],p[100005];
signed main(){
	n=read(),q=read(),siz=(int)sqrt(q),num=(q+siz-1)/siz;tag[1]=1;
	for(int i=2;i<=n;i++)g[read()].push_back(i);
	for(int i=1;i<=n;i++)col[i]=0;
	for(int i=1;i<=q;i++)op[i]=read(),p[i]=read();
	for(int i=1;i<=num;i++){
		int l=(i-1)*siz+1,r=min(i*siz,q);
		for(int j=1;j<=n;j++)G[j].clear(),cov[j]=cnt[j]=0;
		for(int j=l;j<=r;j++)tag[p[j]]++;
		build(1,1,0,0);
		for(int j=l;j<=r;j++){
			if(op[j]==1)update(p[j]);
			else if(op[j]==2)cover(p[j]);
			else puts(col[p[j]]?"black":"white");
		}
		rebuild(1,0,0);
		for(int j=l;j<=r;j++)tag[p[j]]--;
	}
	return 0;
}

N - Bear and Bowling

O - 你的名字。

哎,卡常。

考虑根号分治。当 \(k\le T\) 时我们对每种可能的 \(k\) 预处理 \(a_i\bmod k\),然后分成 \(\sqrt{n}\) 块,每块块内维护前后缀最小值,对所有块再跑 ST 表。当询问两端点在同一块内时暴力查询,不在同一块内时分成整块和散块 \(\mathcal{O}(1)\) 查询,复杂度 \(\mathcal{O}(T(n+\sqrt{n}\log\sqrt{n})+\sum[k_i\le T])\);当 \(k>T\) 时发现 \(\dfrac{V}{k}\) 不会太大,可以枚举 \(i\),每次只考虑 \(a_j\in[ik,(i+1)k)\) 的位置,然后类似上述做法处理。

发现此时空间复杂度是 \(\mathcal{O}(\dfrac{mV}{T})\) 级别的,不太行。不妨转为枚举 \(ik\),此时不需要额外开空间。同时由于本题求最小值,我们只用保证 \(a_j\ge ik\) 即可,于是可以从大到小枚举,每次逐渐加入,单次修改复杂度为 \(\mathcal{O}(\sqrt{n}+(1+2+4+\ldots \sqrt{n}))=\mathcal{O}(\sqrt{n})\) 级别,总复杂度 \(\mathcal{O}(n\sqrt{n}+V\ln V+\sum[k_i>T])\)

注意本题非常卡常,有几个细节需要注意:

  • 适当调整块长,经试验取在 630 左右最优;

  • 快读,以及其他所有常用、基本的卡常技巧,以及不要用 vector;

  • 玄学的估价函数:可以大概预估一下两种方法需要的大概时间,然后考虑选哪种;

点击查看代码
#include<bits/stdc++.h>
using namespace std;
const int inf=1e9;
struct IO {
#define MAXSIZE (1 << 20)
#define isdigit(x) (x >= '0' && x <= '9')
  char buf[MAXSIZE], *p1, *p2;
  char pbuf[MAXSIZE], *pp;
#if DEBUG
#else
  IO() : p1(buf), p2(buf), pp(pbuf) {}

  ~IO() { fwrite(pbuf, 1, pp - pbuf, stdout); }
#endif
  char gc() {
#if DEBUG
    return getchar();
#endif
    if (p1 == p2) p2 = (p1 = buf) + fread(buf, 1, MAXSIZE, stdin);
    return p1 == p2 ? ' ' : *p1++;
  }
  bool blank(char ch) {
    return ch == ' ' || ch == '\n' || ch == '\r' || ch == '\t';
  }
  template <class T>
  void read(T &x) {
    double tmp = 1;
    bool sign = 0;
    x = 0;
    char ch = gc();
    for (; !isdigit(ch); ch = gc())
      if (ch == '-') sign = 1;
    for (; isdigit(ch); ch = gc()) x = x * 10 + (ch - '0');
    if (ch == '.')
      for (ch = gc(); isdigit(ch); ch = gc())
        tmp /= 10.0, x += tmp * (ch - '0');
    if (sign) x = -x;
  }
  void read(char *s) {
    char ch = gc();
    for (; blank(ch); ch = gc())
      ;
    for (; !blank(ch); ch = gc()) *s++ = ch;
    *s = 0;
  }
  void read(char &c) {
    for (c = gc(); blank(c); c = gc())
      ;
  }
  void push(const char &c) {
#if DEBUG
    putchar(c);
#else
    if (pp - pbuf == MAXSIZE) fwrite(pbuf, 1, MAXSIZE, stdout), pp = pbuf;
    *pp++ = c;
#endif
  }
  template <class T>
  void write(T x) {
    static T sta[35];
    T top = 0;
    do {
      sta[top++] = x % 10, x /= 10;
    } while (x);
    while (top) push(sta[--top] + '0');
  }
  template <class T>
  void write(T x, char lastChar) {
    write(x), push(lastChar);
  }
} io;
struct Que{
	int l,r,k,id;
}q[300005];
inline int cmpq(Que x,Que y){
	return x.k<y.k;
}
int n,m,V,T,a[300005],b[300005],c[300005],ans[300005],lv[100005],rv[100005],lp[100005],rp[100005];
int siz,num,bel[300005],L[635],R[635],pr[635][635],sf[635][635],Log[635],f[12][635];
inline int qry(int l,int r){
	int il=bel[l],ir=bel[r],o=Log[(ir-1)-(il+1)+1];
	if(ir-il==1)return min(sf[il][l-L[il]+1],pr[ir][r-L[ir]+1]);
	return min({sf[il][l-L[il]+1],pr[ir][r-L[ir]+1],f[o][(il+1)],f[o][(ir-1)-(1<<o)+1]});
}
inline void solve(int k){
	for(int i=1;i<=n;++i)c[i]=a[i]%k;
	for(int i=1;i<=num;++i){
		pr[i][1]=c[L[i]];
		for(int j=L[i]+1;j<=R[i];++j)pr[i][j-L[i]+1]=min(pr[i][j-L[i]],c[j]);
		sf[i][R[i]-L[i]+1]=c[R[i]];
		for(int j=R[i]-1;j>=L[i];--j)sf[i][j-L[i]+1]=min(sf[i][j-L[i]+2],c[j]);
	}
	for(int i=1;i<=num;++i)f[0][i]=pr[i][R[i]-L[i]+1];
	for(int j=1;j<=Log[num];++j){
		for(int i=1;i+(1<<j)-1<=num;i++){
			f[j][i]=min(f[j-1][i],f[j-1][i+(1<<(j-1))]);
		}
	}
	for(int x=lv[k];x<=rv[k];++x)if(q[x].r-q[x].l+1>siz)ans[q[x].id]=qry(q[x].l,q[x].r);
}
inline int cmpa(int x,int y){
	return a[x]>a[y];
}
signed main(){
	io.read(n),io.read(m),siz=630,num=(n+siz-1)/siz;
	for(int i=1;i<=n;++i)bel[i]=(i-1)/siz+1;
	for(int i=1;i<=num;++i)L[i]=(i-1)*siz+1,R[i]=i*siz;
	R[num]=n;
	Log[1]=0;for(int i=2;i<=num+2;++i)Log[i]=Log[i>>1]+1;
	for(int i=1;i<=n;++i)io.read(a[i]),V=max(V,a[i]),b[i]=i;
	for(int i=1;i<=m;++i)io.read(q[i].l),io.read(q[i].r),io.read(q[i].k),q[i].id=i,ans[i]=inf;
	sort(q+1,q+m+1,cmpq);sort(b+1,b+n+1,cmpa);
	for(int i=1;i<=n;++i)lp[a[b[i]]]=(lp[a[b[i]]]?lp[a[b[i]]]:i),rp[a[b[i]]]=i;
	for(int i=1;i<=m;++i){
		int l=q[i].l,r=q[i].r,k=q[i].k,id=q[i].id;T=max(T,k);
		if(r-l+1<=siz){
			for(int j=l;j<=r;++j)ans[id]=min(ans[id],a[j]%k);
			continue;
		}
		lv[k]=(lv[k]?lv[k]:i),rv[k]=i;
	}
	int w=3*n+siz*Log[siz]-n*siz/m;
	for(int i=1;i<=T;++i)if(w<2.7*(V/i-1)*(rv[i]-lv[i]+1))solve(i),lv[i]=0;
	for(int i=1;i<=num;++i)for(int j=1;j<=R[i]-L[i]+1;++j)pr[i][j]=sf[i][j]=inf;
	for(int j=0;j<=Log[num];++j)for(int i=1;i+(1<<j)-1<=num;++i)f[j][i]=inf;
	for(int i=V;i>=1;i--){
		for(int x=lp[i];x<=rp[i];++x){
			int p=b[x],id=bel[p];
			for(int i=p;i<=R[id];++i)pr[id][i-L[id]+1]=a[p];
			for(int i=L[id];i<=p;++i)sf[id][i-L[id]+1]=a[p];
			for(int j=0;j<=Log[num];++j){
				int lt=max(1,id-(1<<j)+1),rt=min(id,num-(1<<j)+1);
				for(int i=lt;i<=rt;++i)f[j][i]=a[p];
			}
		}
		for(int j=1;j*j<=i;++j){
			if(i%j)continue;
			if(lv[j]){
				for(int x=lv[j];x<=rv[j];++x){
					int l=q[x].l,r=q[x].r,id=q[x].id;
					if(r-l+1>siz)ans[id]=min(ans[id],qry(l,r)-i);
				}
			}
			if(lv[i/j]&&i/j!=j){
				for(int x=lv[i/j];x<=rv[i/j];++x){
					int l=q[x].l,r=q[x].r,id=q[x].id;
					if(r-l+1>siz)ans[id]=min(ans[id],qry(l,r)-i);
				}
			}
		}
	}
	for(int i=1;i<=m;++i){
		int l=q[i].l,r=q[i].r,id=q[i].id;
		if(r-l+1>siz&&lv[q[i].k])ans[id]=min(ans[id],qry(l,r));
	}
	for(int i=1;i<=m;++i)io.write(ans[i],'\n');
	return 0;
}

P - Breadboard Capacity (hard version)

Q - Fusion tree

考虑对每个点开 trie 维护答案。如果一个点记录自己周围所有点的信息,那么在菊花图上一次修改会导致所有节点都需要修改。考虑每个节点只记录自己所有儿子的信息,询问时单独判断父亲。这样一次修改只会影响一个点。

单点减和维护异或和平凡,考虑如何维护全局加一的操作。这是一个经典的 trick。不同于平时的 trie,这道题的 trie 是从低到高维护。容易发现全局加一就是找到最小的那个 0 把它变成 1,然把后面所有的 1 变成 0。对应在这颗 trie 上就是交换左右儿子,然后递归处理现在的 \(son_{u,0}\)(即原来的 \(son_{u,1}\))直到不存在 \(son_{u,0}\)

点击查看代码
#include<bits/stdc++.h>
#define int long long 
using namespace std;
const int inf=1e18;
inline int read(){
	int x=0,f=1;char ch=getchar();
	while (!isdigit(ch)){if (ch=='-') f=-1;ch=getchar();}
	while (isdigit(ch)){x=x*10+ch-48;ch=getchar();}
	return x*f;
}
struct edge{
	int v,nxt;
}e[1000005];
int head[500005],tot;
void add(int u,int v){
	e[++tot]=(edge){v,head[u]},head[u]=tot;
}
int T,root[500005],son[10000005][2],c[10000005],w[10000005];
void pushup(int p){
	c[p]=0,w[p]=0;
	if(son[p][0])c[p]^=c[son[p][0]],w[p]^=w[son[p][0]]<<1;
	if(son[p][1])c[p]^=c[son[p][1]],w[p]^=(w[son[p][1]]<<1)|c[son[p][1]];
}
void ins(int &p,int k,int v){
	if(!p)p=++T;
	if(k>19){c[p]^=1;return;}
	ins(son[p][(v>>k)&1ll],k+1,v);
	pushup(p);
}
void addall(int p){
	if(!p)return;
	swap(son[p][0],son[p][1]);
	addall(son[p][0]);
	pushup(p);
}
int Fa[500005],a[500005],tag[500005];
void dfs(int u,int fa){
	Fa[u]=fa,tag[u]=0;
	for(int i=head[u];i;i=e[i].nxt){
		int v=e[i].v;if(v==fa)continue;
		ins(root[u],0,a[v]);dfs(v,u);
	}
}
int ask(int p){
	return w[p];
}
void upd(int u,int k){
	if(Fa[u])ins(root[Fa[u]],0,a[u]+tag[Fa[u]]);
	a[u]-=k;
	if(Fa[u])ins(root[Fa[u]],0,a[u]+tag[Fa[u]]);
}
void add(int u){
	if(Fa[u])upd(Fa[u],-1);
	tag[u]++;addall(root[u]);
}
int qry(int u){
	return (a[Fa[u]]+tag[Fa[Fa[u]]])^ask(root[u]);
}
signed main(){
	int n=read(),m=read();
	for(int i=1,u,v;i<n;i++)u=read(),v=read(),add(u,v),add(v,u);
	for(int i=1;i<=n;i++)a[i]=read();
	dfs(1,0);
	while(m--){
		int op=read();
		if(op==1){
			int x=read();
			add(x);
		}
		else if(op==2){
			int x=read(),v=read();
			upd(x,v);
		}
		else {
			int x=read();
			printf("%lld\n",qry(x));
		}
	}
	return 0;
}
posted @ 2023-09-22 08:55  xx019  阅读(43)  评论(1编辑  收藏  举报