SDOI2017

[SDOI2017]硬币游戏
[SDOI2017]天才黑客
题目中的代价与这次走的边和上次走的边有关。
显然可以这样建边:
设f[x][i]表示x节点上次走的边为i的最小代价。
f[x][i]向x有边的节点f[y][j]连边。
两条边的代价是trie树上的lca
但是显然无法ac。
考虑换一种建图方式。
把一条边拆成两个点:出点和入点,点之间连权值为边权的边
对于两条边(a,b),(c,d)来讲,如果b=c,那么第一个边的出点向第二个边的入点连一条权值为dep[lca]的边。
但是显然也无法通过。
这样子建图点数是原图的边数,无法优化,可以考虑优化新图的边数。
把一个点的所有入/出点按照lca排序,新建一排点表示每条边。
如果我们按照sa那样求height数组表示相邻两个串的lca的深度,则每条边的代价就是它对应的区间的height数组的min值。
考虑一个点的贡献,是一个区间,用单调栈处理然后线段树优化连边,时间复杂度为两个log。
考虑继续进行优化。新建4排点:a1,a2,b1,b2,a1,a1用于处理前缀,b1,b2用于处理后缀。
设b[i]表示所有被排序的点的第i个。
还是把每条边拆成一个点。
a1,a2上的每个点i向i-1连边,b1,b2的每个点i->i+1连边。
现在处理前缀连边,注意到每条边的意义是:i节点可以通过dep[lca(b[i],b[i-1])]的代价访问到前面的点。所以a1的第i个点向a2的第i-1个点连边。
处理b1,b2同理。
最终把a2,b2向原来的边连边权为0的边即可。
在跑最短路时要加上原来的边权。
时间复杂度mlogm,常数较大。

#include<bits/stdc++.h>
using namespace std;
#define N 1000010
#define int long long
int n,m,k,d[N],a,b,c[N],f[N][20],de[N],h[N],v[N],nxt[N],ec,id[N],cc,p1[N],p2[N],s1[N],s2[N],va[N],c1,ct,dd[N],vi[N],cv;
struct no{
	int x,d;
};
int operator <(no x,no y){
	return x.d>y.d;
}
int cp(int x,int y){
	return id[d[abs(x)]]<id[d[abs(y)]];
}
vector<no>g[N];
vector<int>in[N],ou[N];
priority_queue<no>q;
void add(int x,int y){v[++ec]=y;nxt[ec]=h[x];h[x]=ec;}
void adj(int x,int y,int z){g[x].push_back((no){y,z});}
void dfs(int x,int fa){
	f[x][0]=fa;
	id[x]=++cc;
	for(int i=h[x];i;i=nxt[i])
		if(v[i]!=fa){
			de[v[i]]=de[x]+1;
			dfs(v[i],x);
		}
}
int lc(int x,int y){
	if(de[x]>de[y])swap(x,y);
	for(int i=19;~i;i--)
		if(de[f[y][i]]>=de[x])
			y=f[y][i];
	if(x==y)return x;
	for(int i=19;~i;i--)
		if(f[x][i]!=f[y][i])
			x=f[x][i],y=f[y][i];
	return f[x][0];
}
int dij(){
	while(!q.empty()){
		no x=q.top();
		q.pop();
		if(vi[x.x])continue;
		vi[x.x]=1;
		for(int i=0;i<g[x.x].size();i++)
			if(dd[g[x.x][i].x]>dd[x.x]+g[x.x][i].d+c[g[x.x][i].x]){
				dd[g[x.x][i].x]=dd[x.x]+g[x.x][i].d+c[g[x.x][i].x];
				q.push((no){g[x.x][i].x,dd[g[x.x][i].x]});
			}
	}
}
signed main(){
	//freopen("r.in","r",stdin);
	//freopen("w.out","w",stdout);
	int t;
	cin>>t;
	while(t--){
		cin>>n>>m>>k;
		memset(dd,63,sizeof(dd));
		memset(c,0,sizeof(c));
		for(int i=1;i<=m;i++){
			int x,y;
			cin>>x>>y>>c[i]>>d[i];
			in[y].push_back(i);
			ou[x].push_back(i);
			if(x==1){
				q.push((no){i,c[i]});
				dd[i]=c[i];
			}
		}
		for(int i=1;i<k;i++){
			int x,y,z;
			cin>>x>>y>>z;
			add(x,y);
			add(y,x);
		}
		dfs(1,0);
		f[1][0]=1;
		for(int i=1;i<20;i++)
			for(int j=1;j<=k;j++)
				f[j][i]=f[f[j][i-1]][i-1];
		ct=m;
		for(int i=1;i<=n;i++){
			c1=0;
			for(int j=0;j<in[i].size();j++)
				va[++c1]=in[i][j];
			for(int j=0;j<ou[i].size();j++)
				va[++c1]=-ou[i][j];
			sort(va+1,va+c1+1,cp);
			for(int j=1;j<=c1;j++){
				p1[j]=++ct;
				s1[j]=++ct;
				p2[j]=++ct;
				s2[j]=++ct;
			}
			for(int j=1;j<=c1;j++){
				if(j>1){
					adj(p1[j],p1[j-1],0);
					adj(p2[j],p2[j-1],0);
				}
				if(j<c1){
					adj(s1[j],s1[j+1],0);
					adj(s2[j],s2[j+1],0);
				}
				if(va[j]>0){
					adj(va[j],p1[j],0);
					adj(va[j],s1[j],0);
				}
				else{
					adj(s2[j],-va[j],0);
					adj(p2[j],-va[j],0);
				}
			}
			for(int j=2;j<=c1;j++){
				int v=de[lc(d[abs(va[j])],d[abs(va[j-1])])];
				adj(p1[j],p2[j-1],v);
			}
			for(int j=1;j<c1;j++){
				int v=de[lc(d[abs(va[j])],d[abs(va[j+1])])];
				adj(s1[j],s2[j+1],v);
			}
		}
		dij();
		for(int i=2;i<=n;i++){
			int r=1e18;
			for(int j=0;j<in[i].size();j++)
				r=min(r,dd[in[i][j]]);
			cout<<r<<'
';
		}
		ec=cc=ct=0;
		memset(h,0,sizeof(h));
		memset(vi,0,sizeof(vi));
		for(int i=0;i<N;i++){
			in[i].clear();
			ou[i].clear();
			g[i].clear();
		}
	}
}

[SDOI2017]新生舞会
简单题,直接分数规划后费用流即可
[SDOI2017]遗忘的集合
[SDOI2017]相关分析
[SDOI2017]数字表格
[SDOI2017]文本校正
分类讨论。文本串共有3!种拼接方式。
如果原串abc代表新串abc,直接判断。
如果原串abc代表新串bca。
原串a|bc代表新串bc|a
枚举a的长度,分两端哈希。
如果原串abc代表新串cab,可以仿照上面一种情况做。
如果原串abc代表新串cba。
考虑把T倒序插入S
即生成新的串(ss=s_1t_ns_2t_n-1...s_nt_1)
如果一个长度为偶数,开头坐标为奇数的串[l,r]是回文串。
如果a为ss[l]在s中对应的编号,b在ss[r]在s中对应的编号,中间有k个属于t的字符。
(s[a...a+k-1]==t[b-k+1...b])
所以问题就是判定ss是否能够划分为3个回文串。
manacher一下原串。枚举第一个回文串,接下来就是判断双回文串。
根据jcvb字符串课件,一个双回文串st一定能表示成ab,其中a为st的最长回文前缀或者b为st的最长回文后缀。
(f[i])表示以i开头的最长回文串,(g[i])表示以i结尾的最长回文串,可以dp解决,具体看代码。
如果前面的回文串是([1,i]),且则需要判定(ss[i...n*2])是否为双回文串。
要判定(a)是否是(ss[i...n*2])的最长回文前缀,则要判定(ss[f[i]+1...n*2])是否是回文串。manacher处理。
要判定(b)是否是(ss[i...n*2])的最长回文后缀,使用一个队列(q)存储(ss[1...n*2])的所有回文后缀开始的位置(i).
如果(q)的头部(<i),则删除(q)的头部。
删完以后(q)的头部(x)就是(ss[i...n*2])的最长回文后缀。
则判定(ss[i...x-1])是否是回文串即可。
如果原串(abc)代表新串(acb)
考虑倒着枚举(a)的长度,则题目要求每次在开头插入一个字符/判断是否合法。
可以按照上面的方法做。
但是每次开头的(ss[2])都会变化,不太能做。
注意到我们寻找最长双回文串的过程,等价于寻找最大的p,使得(s[i...p]==t[p+1,n]),使用哈希计算剩余的部分即可。
如果原串(abc)代表新串(bac),可以仿照上面一种情况做。
代码很难写。

#include<bits/stdc++.h>
using namespace std;
#define int long long
#define N 2000010
#define mo 998244353
#define bb 1000001
int T,n,m,s[N],t[N],s1[N],t1[N],pr[N],sf[N],ss[N],tp;
struct hs{
	int n,bs[N],hs[N];
	void gt(int *a,int m){
		n=m;
		bs[0]=1;
		for(int i=1;i<=m;i++)
			bs[i]=bs[i-1]*bb%mo;
		for(int i=1;i<=m;i++)
			hs[i]=(hs[i-1]+a[i]*bs[i])%mo;
	}
	int gg(int l,int r){
		return (hs[r]-hs[l-1]+mo)%mo*bs[n-r]%mo;
	}
	void cl(){
		for(int i=1;i<=n;i++)
			bs[i]=hs[i]=0;
		n=0;
	}
}a,b;
struct km{
	int n,f[N],b[N];
	void gt(int *a,int m){
		n=m;
		for(int i=1;i<=m;i++)
			b[i]=a[i];
		int j=0;
		for(int i=2;i<=m;i++){
			while(j&&b[j+1]!=b[i])
				j=f[j];
			if(b[j+1]==b[i])j++;
			f[i]=j;
		}
	}
	void gg(int &p,int c){
		while(p&&b[p+1]!=c)
			p=f[p];
		if(b[p+1]==c)
			p++;
	}
	void cl(){
		for(int i=1;i<=n;i++)
			b[i]=f[i]=0;
		n=0;
	}
}c,d;
struct pl{
	int n,f[N],b[N];
	void gt(int *a,int m){
		n=m;
		for(int i=1;i<=n;i++)
			b[i]=a[i];
		int p=0,md=0;
		b[m+1]=1e9;
		for(int i=1;i<=n;i++){
			if(p>i)
				f[i]=min(p-i,f[md*2-i]);
			else f[i]=0;
			while(i>f[i]&&i+f[i]+1<=n&&b[i-f[i]]==b[i+f[i]+1])
				f[i]++;
			if(i+f[i]>p){
				p=i+f[i];
				md=i;
			}
		}
	}
	int gg(int l,int r){
		if((r-l+1)&1)return 0;
		int lv=(r-l+1)/2,le=l+lv-1;
		if(f[le]>=lv)return 1;
		return 0;
	}
	void cl(){
		for(int i=1;i<=n+1;i++)
			f[i]=b[i]=0;
		n=0;
	}
}e;
signed main(){
	//freopen("r.in","r",stdin);
	cin>>T;
	while(T--){
		a.cl();
		b.cl();
		c.cl();
		d.cl();
		e.cl();
		cin>>n>>m;
		int ok=1,o1=1;
		for(int i=1;i<=n;i++)
			scanf("%lld",&s[i]);
		for(int i=1;i<=n;i++){
			scanf("%lld",&t[i]);
			if(s[i]!=t[i])o1=0;
		}
		if(o1){
			puts("YES");
			continue;
		}
		o1=0;
		a.gt(s,n);
		b.gt(t,n);
		for(int i=1;i<n;i++)//abc bca
			if(!o1&&a.gg(1,i)==b.gg(n-i+1,n)&&a.gg(i+1,n)==b.gg(1,n-i)){
				puts("YES");
				o1=1;
			}
		for(int i=2;i<=n;i++)//abc cab
			if(!o1&&a.gg(1,i)==b.gg(n-i+1,n)&&a.gg(i+1,n)==b.gg(1,n-i)){
				puts("YES");
				o1=1;
			}
		for(int i=1;i<=n;i++){
			s1[i]=s[n-i+1];
			t1[i]=t[n-i+1];
		}
		c.gt(s1,n);
		d.gt(t1,n);
		int p1=0,p2=0;
		for(int i=n;i>1;i--){//abc acb
			c.gg(p1,t[i]);
			d.gg(p2,s[i]);
			if(p2&&!o1&&a.gg(1,i-1)==b.gg(1,i-1)&&a.gg(i+p2,n)==b.gg(i,n-p2)){
				puts("YES");
				o1=1;
			}
			if(p1&&!o1&&a.gg(1,i-1)==b.gg(1,i-1)&&a.gg(i,n-p1)==b.gg(i+p1,n)){
				o1=1;
                                puts("YES");
			}
		}
		c.cl();
		d.cl();
		c.gt(s,n);
		d.gt(t,n);
		p1=p2=0;
		for(int i=1;i<n;i++){//abc bac
			c.gg(p1,t[i]);
			d.gg(p2,s[i]);
			if(!o1&&a.gg(i+1,n)==b.gg(i+1,n)&&a.gg(p1+1,i)==b.gg(1,i-p1)){
				puts("YES");
				o1=1;
			}
			if(!o1&&a.gg(i+1,n)==b.gg(i+1,n)&&a.gg(1,i-p2)==b.gg(p2+1,i)){
				puts("YES");
				o1=1;
			}
		}
		p1=p2=1;
		for(int i=1;i<=2*n;i++){
			if(i%2==1){
				s1[i]=s[p1];
				p1++;
			}
			else{
				s1[i]=t[n-p2+1];
				p2++;
			}
		}
		e.gt(s1,n*2);
		for(int i=1;i<=n*2;i++)
			sf[i]=pr[i]=i;
		for(int i=1;i<=n*2;i++){
			pr[i-e.f[i]+1]=max(pr[i-e.f[i]+1],i+e.f[i]);
			sf[i+e.f[i]]=min(sf[i+e.f[i]],i-e.f[i]+1);
		}
		for(int i=2;i<=n*2;i++)
			pr[i]=max(pr[i],pr[i-1]-1);
		for(int i=n*2-1;i;i--)
			sf[i]=min(sf[i],sf[i+1]+1);
		tp=0;
		for(int i=1;i<=n*2;i++)
			if(pr[i]==n*2)ss[++tp]=i;
		int po=1;
		if(T==2){
			T++;
			T--;
		}
		for(int i=2;i<=n*2;i+=2)
			if(sf[i]==1){
				int l=i+1,r=pr[i+1];
				if(!o1&&e.gg(r+1,n*2)){
					puts("YES");
					o1=1;
				}
				while(ss[po]<=i)po--;
				l=ss[po];
				r=n;
				if(!o1&&e.gg(i+1,l-1)&&po){
					puts("YES");
					o1=1;
				}
			}
		if(!o1)puts("NO");
		for(int i=1;i<=2*n;i++)
			pr[i]=sf[i]=0;
	}
}

[SDOI2017]苹果树
[SDOI2017]龙与地下城
https://www.cnblogs.com/cszmc2004/p/12963732.html
[SDOI2017]树点涂色
用lct维护颜色相同的连续段。
如果把每个节点x的重儿子p[x]设为和它颜色相同的点的编号。
则一次染色操作就是把x->根节点的点access一下,使用重链把这些点连起来。
如果设f[x]表示x->根节点的虚边个数。
由于每次染的颜色都是不同的,所以f[x]就是x节点到根节点的色数。
使用一个线段树st。
如果一条重边->虚边,则子树dfs序+1否则-1。
查询就是区间最大值。
[SDOI2017]序列计数
[SDOI2017]切树游戏

原文地址:https://www.cnblogs.com/cszmc2004/p/13228204.html