CF1654F Minimal String Xoration & CF1654G Snowy Mountain 略记

个人认为是很妙的两道题,所以简单记一记

CF1654F Minimal String Xoration

模仿后缀排序,注意到将一个字符串的所有位置 \(\oplus 2^k\) 相当于将相邻的两个长为 \(2^k\) 的串调换位置,并且异或任意一个数相当于复合若干个这样的变换

求出一个 \(sa_{k,x},rk_{k,x}\) 表示所有异或上某一个值形成的串的 \(2^k\) 的前缀的排序结果,那么我们发现长为 \(2^{k+1}\) 的前缀串实际上是某两个前述前缀串拼起来的结果,套后缀排序,只不过 rk[i+w] 变成了 rk[i^w] 而已

#include <cstdio>
#include <cstring>
using namespace std;
int n,m,p,w;
char s[1<<18];
int buc[1<<18|3];
int sa[1<<18],rk[1<<18],id[1<<18],ork[1<<18];
bool cmp(int x,int y){return ork[x]==ork[y]&&ork[x^w]==ork[y^w];}
int main(){
	scanf("%d",&n);scanf("%s",s);
	for(int i=0;i<(1<<n);++i) ++buc[rk[i]=s[i]-'a'+1];
	for(int i=1;i<=26;++i) buc[i]+=buc[i-1];
	for(int i=(1<<n)-1;~i;--i) sa[--buc[rk[i]]]=i;
	m=26;
   	for(w=1;w<=(1<<n);w<<=1,m=p,p=0){
		for(int i=1;i<=m;++i) buc[i]=0;
		for(int i=0;i<(1<<n);++i) ++buc[rk[sa[i]^w]];
		for(int i=1;i<=m;++i) buc[i]+=buc[i-1];
		for(int i=(1<<n)-1;~i;--i) id[--buc[rk[sa[i]^w]]]=sa[i];
		for(int i=1;i<=m;++i) buc[i]=0;
		for(int i=0;i<(1<<n);++i) ++buc[rk[id[i]]];
		for(int i=1;i<=m;++i) buc[i]+=buc[i-1];
		for(int i=(1<<n)-1;~i;--i) sa[--buc[rk[id[i]]]]=id[i];
		for(int i=0;i<(1<<n);++i) ork[i]=rk[i];
		rk[sa[0]]=p=1;
		for(int i=1;i<(1<<n);++i) rk[sa[i]]=cmp(sa[i],sa[i-1])?p:++p;
		if(p==(1<<n)) break;
	}
	for(int i=0;i<(1<<n);++i) putchar(s[i^sa[0]]);
	putchar('\n');
	return 0;
}

CF1654G Snowy Mountain

感觉从这道题学到了好多套路和分析技巧,以下是对官方题解的一些理解与补充

首先假设我们最后走的长度为 \(len\) ,容易发现,我们最终终点一定是某一个盆地,多源 bfs 出高度 \(d_x\) ,那么发现我们有 \(d_x\) 步在增加动能,剩下的步都在减少动能,也就是说,我们最后一定会剩下 \(d_x-(len-d_x)=2d_x-len\) 单位的动能

考虑一个贪心,如果我们不是直接沿着最短路径滑向某一个盆地的,那么我们一定会经过 \(d_u=d_v\) 的边,如果在经过最后的这样的边时我们还有剩余动能,我们不如在这条边上反复滑来滑去增加距离,这样子的话最后一定一点动能都不剩,再继续沿着最短路径直接滑向某一个盆地(这个过程只会增加动能),最后剩余的动能就是 \(w(=d_u,d_v)\) 。那么这种情况中 \(w=2d_x-len\) ,可以得到 \(len=2d_x-w\) ,我们的任务就是求对每个点 \(x\) 求最优 \(w\)

\(w\) 就是所有能够满足动能限制的前提下被 \(x\) 到达且有一条 \(d_u=d_v\) 的树边的 \(u\) 的最小值,这个东西十分难以处理,很大的原因是这道题的出发点和需考虑的终点都有 \(O(n)\) 个,这个时候找性质就很神了

\(S=\{u|\exists (u,v)\in E,d_u=d_v\}\) ,发现 \(\sum_{u\in S} d_u\)\(O(n)\) 级别

这个东西很好理解,因为两个相邻点 \(d_u=d_v\) ,那么他们一定分别由两条长度为 \(d_u/d_v\) 的不交路径增广而来

一堆数的和是 \(O(n)\) 级别,那么数的种类就是 \(O(\sqrt n)\) 级别,那么我们真正需要考虑的终点的 \(d_u\) 种类也是 \(\sqrt(n)\) 级别的

具体地,设 \(f_{u,i}\) 表示从 \(u\) 到每一个 \(d_v=i\) 的点的路程中所有时刻的动能的最小值的相反数的最小值(此时先假设动能可以为负),第二维的大小就是 \(O(\sqrt n)\) 的了

发现这个东西转移顺序不太好搞,首先可以发现如果 \(d\) 不等那么就从 \(d\) 小的转移到 \(d\) 大的,但是 \(d\) 相同的位置是多个连通块

那么对每一个连通块 up and down dp 就可以解决转移顺序的问题了

#include <cstdio>
using namespace std;
int read(){
	char c=getchar();int x=0;
	while(c<48||c>57) c=getchar();
	do x=(x<<1)+(x<<3)+(c^48),c=getchar();
	while(c>=48&&c<=57);
	return x;
}
const int INF=0x3f3f3f3f;
const int N=200003;
int que[N],tl;
int d[N],n;
int f[N][700];
namespace tr{
	int hd[N],ver[N<<1],nxt[N<<1],tot;
	void add(int u,int v){nxt[++tot]=hd[u];hd[u]=tot;ver[tot]=v;}
}
namespace bl{
	int hd[N],ver[N<<1],nxt[N<<1],tot;
	void add(int u,int v){nxt[++tot]=hd[u];hd[u]=tot;ver[tot]=v;}
}
void chmn(int &u,int v){if(u>v) u=v;}
void chmx(int &u,int v){if(u<v) u=v;}
bool vis[N];
int p[N],pp[N],rk;
void updp(int u,int fa){
	vis[u]=1;
	for(int i=tr::hd[u],v;i;i=tr::nxt[i])
		if((v=tr::ver[i])^fa){
			if(d[v]!=d[u]) continue;
			updp(v,u);
			for(int t=1;t<=rk;++t)
				chmn(f[u][t],f[v][t]+1);
			f[u][pp[d[u]]]=1;
		}
}
void downdp(int u,int fa){
	for(int i=tr::hd[u],v;i;i=tr::nxt[i])
		if((v=tr::ver[i])^fa){
			if(d[v]!=d[u]) continue;
			for(int t=1;t<=rk;++t)
				chmn(f[v][t],f[u][t]+1);
			f[v][pp[d[v]]]=1;
			downdp(v,u);
		}
}
int main(){
	n=read();
	for(int i=1;i<=n;++i){
		int a=read();
		if(a==1) vis[que[++tl]=i]=1;
		else d[i]=INF;
	}
	for(int i=1;i<n;++i){
		int u=read(),v=read();
		tr::add(u,v);tr::add(v,u);
	}
	for(int it=1;it<=tl;++it){
		int u=que[it];
		for(int i=tr::hd[u],v;i;i=tr::nxt[i])
			if(!vis[v=tr::ver[i]]){
				d[v]=d[u]+1;
				vis[que[++tl]=v]=1;
			}
	}
	for(int u=1;u<=n;++u){
		vis[u]=0;bl::add(d[u],u);
		if(pp[d[u]]) continue;
		for(int i=tr::hd[u];i;i=tr::nxt[i]){
			int v=tr::ver[i];
			if(d[u]==d[v]) p[pp[d[u]]=++rk]=d[u];
		}
	}
	for(int o=0;o<=n;++o){
		for(int i=bl::hd[o];i;i=bl::nxt[i]){
			int u=bl::ver[i];
			for(int t=1;t<=rk;++t) f[u][t]=INF;
			for(int j=tr::hd[u];j;j=tr::nxt[j]){
				int v=tr::ver[j];
				if(d[v]!=d[u]-1) continue;
   				for(int t=1;t<=rk;++t)
					if(f[v][t]) chmn(f[u][t],f[v][t]-1);else f[u][t]=0;
			}
		}
		for(int i=bl::hd[o];i;i=bl::nxt[i]){
			int u=bl::ver[i];
			if(!vis[u]) updp(u,0),downdp(u,0);
		}
	}
	for(int u=1;u<=n;++u){
		int res=d[u];
		for(int t=1;t<=rk;++t)
			if(!f[u][t]) chmx(res,d[u]+d[u]-p[t]);
		printf("%d ",res);
	}
	putchar('\n');
	return 0;
}
posted @ 2022-03-22 22:36  yyyyxh  阅读(75)  评论(0编辑  收藏  举报