zoukankan      html  css  js  c++  java
  • CF979E 题解

    题 目 链 接

    题目大意

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

    (Solution)

    (P.S.)由于作者非常蒟蒻,做题的时候出了很多(bug),所以题解可能会比较详细从而有一些累赘的东西,如果需要直接看结论的(dalao)可跳转至代码部分(代码里有注释)

    很好的 毒瘤 (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) ,则方案数为 (sumlimits_{k=1}^{2k-1 leq qwq}{qwq choose 2k+1}=2^{qwq-1}),若为偶黑则不影响奇偶性,随便连即可,设之前有(qaq)个偶黑,方案数为(sumlimits_{k=0}^{qaq}{qaq choose k}=2^{qaq})
    • 当前点为偶白((ob)),若连到当前点的点为偶黑((oh)),则必须连着偶数个偶黑才能保证当前点为偶数,设之前偶黑数量为 (ovo) ,则方案数为:(sumlimits_{k=1}^{2k leq ovo}{ovo choose 2k}=2^{ovo-1}),若为奇黑则不影响奇偶性,随便连即可,设之前有(owo)个偶黑,方案数为(sumlimits_{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('
    ')
    	#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)
    ~(- - -) (- - -)~
      / /   / /
    */
    

    完结撒花!!!

  • 相关阅读:
    JavaScript 获得今天的日期 (yy-mm-dd)格式
    web前端筛选页面(类似大众点评)
    JS 获取浏览器的名称和版本信息
    java 接收json数据
    指定配置文件启动mongodb
    linux写一个定时任务
    windows杀死占用端口
    mongodb聚合操作汇总
    jpa常用查询
    idea 更换主题
  • 原文地址:https://www.cnblogs.com/lgj-lgj/p/12732448.html
Copyright © 2011-2022 走看看