题解:洛谷 P3109([USACO14OPEN] Code Breaking G)

1. Description

给定一棵 \(n\) 个节点的树,要求在每个点中填上 \(0\sim 9\) 的数。
\(m\) 个限制,每一个限制形如 \(u\ \bar{ABCDE}\),表示从 \(u\)\(u\)\(1\sim 4\) 级祖先组成的数字不等于 \(\bar{ABCDE}\)
试求出不满足至少一条限制的方案数。

2. Solution

首先不难想到一个暴力容斥的写法,枚举不满足那些限制,容斥系数为 \((-1)^k\),其中 \(k\) 表示不满足的限制数量,时间复杂度为 \(O(n2^m)\)
然后自然可以想到通过将容斥系数并入 DP 来求解答案,具体的,我们定义 \(f_{u,S}\) 表示如果我们希望 \(u\) 的祖先的状态是 \(S\),这个 \(S\) 是一个数列,如果 \(S_i=-1\),则 \(i\) 级祖先没有限制,则对从 \(u\) 点开始的所有限制对应的数 \(S\),将 \(f_{u,S}\) 设为 \(-1\),同时,将 \(f_{u,S^\prime},S^\prime=\{-1,-1,-1,-1,-1\}\) 设为 \(1\)
注意:\(S\) 中如果出现 \(-1\),那么必然只会出现一段连续的 \(-1\) 后缀,这是显然的,因为限制都是对于连续的若干个节点做出的。
然后考虑合并 \(u\) 的儿子 \(v\)\(u\) 本身的信息。
这里也需要注意:此时 \(u\) 的状态中,限制是包含 \(u\) 这个节点本身的,而 \(v\) 的状态中,限制则是不包含 \(v\) 本身的。
考虑两个状态 \(f_{u,S}\)\(f_{v,T}\),则他们可以合并的条件是:\(\forall i\in[1,5],S_i=T_i\ or\ S_i=-1\ or\ T_i=1\),合并到他们两个中 \(-1\) 较少的状态,也就是对于祖先限制更多的状态。
最后,我们需要让 \(u\) 的状态中的限制不包含 \(u\) 本身,这一过程是简单的,直接平移数列,在末尾加 \(-1\) 即可,此时如果原来对 \(u\) 是没有限制,那么就需要将方案数乘 \(10\)
此时我们就可以得到一个 \(O(nm^2)\) 的做法,仍然无法通过。
这里就需要注意力了,我们发现,\(S\)\(T\) 的合并其实是一个匹配前缀的过程,因此考虑使用 trie 合并来进行转移,则最后去除对 \(u\) 的限制的过程也可描述为合并根的所有儿子。
时间复杂度的分析与线段树合并类似,时间复杂度为 \(O(n+5m)\),可过。

3. Code

/*by ChenMuJiu*/
/*略去缺省源与快读快写*/
const int N=2e4+5,M=3e5+5,mod=1234567;
const int pw[]={1,10,100,1000,10000,100000,100000};
int n,m;
int rt[N];
struct ST{
	int a[5];
	ST(){
		a[0]=a[1]=a[2]=a[3]=a[4]=-1;
	}
	bool operator <(const ST &T)const{
		if(a[0]^T.a[0])return a[0]<T.a[0];
		if(a[1]^T.a[1])return a[1]<T.a[1];
		if(a[2]^T.a[2])return a[2]<T.a[2];
		if(a[3]^T.a[3])return a[3]<T.a[3];
		return a[4]<T.a[4];
	}
	int& operator [](const int &T){
		return a[T];
	}
	ST trans()const{
		ST res;
		for(int i=0;i<4;i++)
			res.a[i]=a[i+1];
		res.a[4]=-1;
		return res;
	}
};
vector<ST>lim[N];
vector<int>e[N];
int add(int x,int y){
	x+=y;
	return x>=mod?x-mod:x;
}
int sub(int x,int y){
	x-=y;
	return x<0?x+mod:x;
}
int mul(int x,int y){
	long long res=1ll*x*y;
	return res>=mod?res%mod:res;
}
int binpow(int a,int b){
	int res=1;
	while(b){
		if(b&1)res=mul(res,a);
		b>>=1;
		a=mul(a,a);
	}
	return res;
}
struct Trie{
	int num;
	int ch[M][10];
	int tag[M],val[M];
	int New(){
		int p=++num;
		for(int i=0;i<10;i++)
			ch[p][i]=0;
		tag[p]=1,val[p]=0;
		return p;
	}
	void Tag(int p,int v){
		tag[p]=mul(tag[p],v);
		val[p]=mul(val[p],v);
	}
	void pushdown(int p){
		if(tag[p]==1)return ;
		for(int i=0;i<10;i++)
			if(ch[p][i])Tag(ch[p][i],tag[p]);
		tag[p]=1;
	}
	void insert(int u,ST s){
		int p=u;
		for(int i=0;i<5;i++){
			if(!ch[p][s[i]])ch[p][s[i]]=New();
			p=ch[p][s[i]];
		}
		val[p]=mod-1;
	}
	int merge1(int u,int v,int sumu,int sumv){//第一种合并,维护的是 DP 转移  
		if(u==0&&v==0)return 0;
		if(u==0){
			Tag(v,sumu);
			return v;
		}
		if(v==0){
			Tag(u,sumv);
			return u;
		}
		pushdown(u),pushdown(v);
		int tmp=sub(mul(add(sumu,val[u]),add(sumv,val[v])),mul(sumu,sumv));
		for(int i=0;i<10;i++)
			ch[u][i]=merge1(ch[u][i],ch[v][i],add(sumu,val[u]),add(sumv,val[v]));
		val[u]=tmp;
		return u;
	}
	int merge2(int u,int v){
		if(u==0&&v==0)return 0;
		if(u==0)return v;
		if(v==0)return u;
		pushdown(u),pushdown(v);
		for(int i=0;i<10;i++)
			ch[u][i]=merge2(ch[u][i],ch[v][i]);
		val[u]=add(val[u],val[v]);
		return u;
	}
}trie;
void dfs(int u){
	rt[u]=trie.New();
	trie.val[rt[u]]=1;
	for(auto tmp:lim[u])
		trie.insert(rt[u],tmp);
	for(int v:e[u]){
		if(v==u)continue;
		dfs(v);
		rt[u]=trie.merge1(rt[u],rt[v],0,0);			
	}
	int tmprt=0;
	for(int i=0;i<10;i++)
		tmprt=trie.merge2(tmprt,trie.ch[rt[u]][i]);
	if(!tmprt)tmprt=trie.New();
	trie.val[tmprt]=add(trie.val[tmprt],mul(trie.val[rt[u]],10));
	rt[u]=tmprt;
}
signed main(){
	read(n),read(m);
	for(int i=2,fa;i<=n;i++){
		cin>>fa;
		e[fa+1].push_back(i);
	}
	for(int i=1,u;i<=m;i++){
		cin>>u;
		ST tmp;
		char c[5];
		cin>>c;
		for(int i=0;i<5;i++){
			tmp.a[i]=c[i]-'0';
		}
		lim[u+1].push_back(tmp);
	}
	dfs(1);
	write(sub(binpow(10,n),trie.val[rt[1]]));
}
posted @ 2026-01-13 18:50  陈牧九  阅读(3)  评论(0)    收藏  举报