CF979E 题解

题 目 链 接

题目大意

有一个 \(n\) 个点的图,有一些点的颜色给定,另一些可以随意确定。另外,还可以在图上连一些从编号较小的节点向编号较大节点的边。
对于一个确定的图,统计图中节点颜色黑白交错的路径条数,如果奇偶性为 \(p\) ,那么该图就被定义为好图。
求好图的个数,答案对 \(10^9+7\) 取模。

\(Solution\)

很好的 毒瘤 \(dp\) 题目,然鹅写+调=\(2.5h\)写题解2h,做到自闭。
题意大概是这样的:有一个 \(n\) 个节点的图,其中给出了一些点的颜色,剩下点的颜色由你自己决定,所有边的连法由你自己决定。

首先由“从编号较小的节点向编号较大节点”可以看出此题要按照拓扑序 \(dp\)
从计数角度看,我们只需要记录到达一个点时该点的颜色以及该路径的长度,那么设 \(f_{i,jb,jh,ob,oh}\)表示到达第\(i\)个点时,这 \(i\) 个点(即现在构成的图)中有 \(jb\) 个白点是奇数条好的路径的结尾, \(jh\) 个黑点是奇数条好的路径的结尾,\(ob\) 个白点是偶数条好的路径的结尾,\(oh\)个黑点是奇数条好的路径的结尾的方案数,又\(jb+jh+ob+oh=i\),只需要知道其中\(4\)个数的值,剩下一个可以计算出来,于是省掉 \(oh\) 这一维,复杂度\(O(n^4)\)可以过。

我们现在来分析状态转移,分类讨论当前点是黑点\(or\)白点。

\((1).\)当前点为白点,则上一个点必为黑点,若要影响到当前点奇偶性,上一个点为奇黑,则当前点进行奇偶分类讨论:

  • 当前点为奇白\((jb)\),若连到当前点的点为奇黑\((jh)\),则必须连着奇数个奇黑才能保证当前点为奇数(上一个点和现在的点之间还有一条边会影响奇偶性,所以应该反过来考虑),设之前奇黑数量为 \(qwq\) ,则方案数为 \(\sum\limits_{k=1}^{2k-1 \leq qwq}{qwq \choose 2k+1}=2^{qwq-1}\),若为偶黑则不影响奇偶性,随便连即可,设之前有\(qaq\)个偶黑,方案数为\(\sum\limits_{k=0}^{qaq}{qaq \choose k}=2^{qaq}\)
  • 当前点为偶白\((ob)\),若连到当前点的点为偶黑\((oh)\),则必须连着偶数个偶黑才能保证当前点为偶数,设之前偶黑数量为 \(ovo\) ,则方案数为:\(\sum\limits_{k=1}^{2k \leq ovo}{ovo \choose 2k}=2^{ovo-1}\),若为奇黑则不影响奇偶性,随便连即可,设之前有\(owo\)个偶黑,方案数为\(\sum\limits_{k=0}^{owo}{owo \choose k}=2^{owo}\)
    嗯那么我们就把当前点为黑点的情况讨论完了。

\((2).\)当前点为黑点,统计方法与上面类似,只是把所有的\(black/white\)互换,这里不再赘述
颜文字大法好
那么我们现在就通过 毒瘤的 分类讨论完成了\(O(n^4)\)的做法,在此题的数据范围中已经足够优秀了。

然鹅翻题解的时候发现\(luogu\)上面的神仙提出了\(O(n)\)的做法,膜拜~
但是其实优化到 \(O(n)\) 的做法很好理解,从上面的分类讨论中不难看出,其实在讨论的过程中无论是 \(jb/jh/ob/oh\) ,方案数总是 \(2^{something}\) ,这启发我们将 \(jb/jh/ob/oh\) 的个数转换为 \(0/1\) ,既存在\(or\)不存在,接下来是对此的分类讨论(毒瘤啊)
对于当前点 \(i\) ,若 \(i\) 为白点,那么上一个点必须为黑点,考虑上一个点的奇偶性:
若连到偶黑,路径条数加上一个偶数,奇偶性不变,随便连,
现在讨论奇黑:

  • 若存在奇黑,存在奇黑,我们可以挑出一个奇黑点来控制奇偶性,即这个白点作为奇白和偶白的方案数各为 \(2^{i-2}\),和为 \(2^{i-1}\)
  • 若不存在,当前点自己算一条黑白相间的路径,只能作为奇白

那么我们就讨论完了白点,黑点自行分析不难不再赘述

下面贴的代码是\(O(n^4)\)的(里面加上了一点注释,可能会比较好理解),\(O(n)\)做法可以参考上面那篇博客

\(Code:\)

#include<bits/stdc++.h>
using namespace std;
namespace my_std
{
	typedef long long ll;
	typedef double db;
	#define pf printf
	#define pc putchar
	#define fr(i,x,y) for(register ll i=(x);i<=(y);++i)
	#define pfr(i,x,y) for(register ll i=(x);i>=(y);--i)
	#define go(x) for(ll i=head[u];i;i=e[i].nxt)
	#define enter pc('\n')
	#define space pc(' ')
	#define fir first
	#define sec second
	#define MP make_pair
	const ll inf=0x3f3f3f3f;
	const ll inff=1e15;
	inline ll read()
	{
		ll sum=0,f=1;
		char ch=0;
		while(!isdigit(ch))
		{
			if(ch=='-') f=-1;
			ch=getchar();
		}
		while(isdigit(ch))
		{
			sum=sum*10+(ch^48);
			ch=getchar();
		}
		return sum*f;
	}
	inline void write(ll x)
	{
		if(x<0)
		{
			x=-x;
			pc('-');
		}
		if(x>9) write(x/10);
		pc(x%10+'0');
	}
	inline void writeln(ll x)
	{
		write(x);
		enter;
	}
	inline void writesp(ll x)
	{
		write(x);
		space;
	}
}
using namespace my_std;
const ll N=55,mod=1e9+7;
ll n,p,a[N],f[N][N][N][N],mi[N]={1},ans;//不开longlong见祖宗 
int main(void)
{
	n=read(),p=read();
	fr(i,1,n) a[i]=read();
	fr(i,1,n) mi[i]=(mi[i-1]<<1)%mod;//mi[0]记得初始化,在上面已经初始化过了 
	if(a[1]==1||a[1]==-1) //第一个点为黑色  
	{
		f[1][1][0][0]=1; 
		if(p&&n==1) ans++;
	}
	if(!a[1]||a[1]==-1) //第一个点为白色  
	{
		f[1][0][1][0]=1;  
		if(p&&n==1) ans++;
	}
	fr(i,2,n) fr(jb,0,i) fr(jh,0,i-jb) fr(ob,0,i-jb-jh) //分别枚举奇白,奇黑,偶白 (tips:偶数点不能作为链头) 
	{
		ll oh=i-jb-jh-ob; //计算偶黑 
		if(a[i]==1||a[i]==-1) //白点 
		{
			ll add=0;
			//现在在枚举偶白 
			if((jb+ob)!=0) //前面有白点(因为偶数点不能作为链头) 
			{
				if(jb!=0) //奇白个数不为零,那么自己可以继承之前的(即现在在枚举偶白) 
				{
					if(jh==0) add=(add+f[i-1][jb-1][jh][ob])%mod; //奇黑为零,自己另立门户 
					else add=(add+mi[jh-1]*f[i-1][jb-1][jh][ob])%mod; //奇黑不为零,继承之前的路径 
				}
				//接下来枚举奇白 
				if(ob!=0&&jh!=0) add=(add+mi[jh-1]*f[i-1][jb][jh][ob-1]%mod); //偶白不为零(可以继承之前的路径),存在奇黑则随便连即可 
			}
			add=(add*mi[jb+ob+oh-1])%mod;
			f[i][jb][jh][ob]=(f[i][jb][jh][ob]+add)%mod;//状态转移 
		}
		if(a[i]==0||a[i]==-1) //黑点 
		{
			ll add=0;
			//现在枚举偶黑 
			if((jh+oh)!=0) //前面有黑点(因为偶数点不能作为链头) 
			{
				if(jh!=0) //奇黑个数不为零,那么自己可以继承之前的(即现在枚举偶黑) 
				{
					if(jb==0) add=(add+f[i-1][jb][jh-1][ob])%mod; //奇白为零,自己另立门户 
					else add=(add+mi[jb-1]*f[i-1][jb][jh-1][ob])%mod; //奇白不为零,继承之前的路径 
				}
				//接下来枚举奇黑 
				if(jb!=0&&oh!=0) add=(add+mi[jb-1]*f[i-1][jb][jh][ob]%mod)%mod; //偶黑不为零(可以继承之前的路径),存在奇白则随便连即可 
			}
			add=(add*mi[jh+ob+oh-1])%mod;
			f[i][jb][jh][ob]=(f[i][jb][jh][ob]+add)%mod;//状态转移 
		}
		if(i==n&&(jb+jh)%2==p) ans=(ans+f[i][jb][jh][ob])%mod;//统计答案, 
	}
	writeln(ans); //output 
	return 0;
}
/*
    ^ ^   ^ ^
   (OwO) (OwO)
~(- - -) (- - -)~
  /\ /\   /\ /\
*/

完结撒花!!!

posted @ 2020-04-19 17:31  L_G_J  阅读(272)  评论(0编辑  收藏  举报