zoukankan      html  css  js  c++  java
  • [SCOI2016]围棋

    题意

    题目链接

    做法

    首先你得先轮廓线DP(不一定得会插头DP,但这两玩意好像没差)

    题解部分照搬:https://www.luogu.com.cn/blog/duyi/solution-p3290,讲的挺好的。

    先做补集转化。把“至少有一次匹配”,转化为求“禁止出现匹配”。然后用(3^{nm})减去禁止出现匹配的方案数,就是答案了。

    考虑轮廓线DP。

    (dp[i][j][mask][k][l])表示考虑到了第(i)行、第(j)列这个位置。(mask)是一个状压,表示当前轮廓线哪些位置可以与模板的第一行完全匹配。由于(1…c−1)这些列显然不可能完全匹配,所以(mask)只需要记录(m-c+1)个二进制位。(k)表示第(i)(1…j)位最多能匹配到模板串第一行的哪个位置,(l)表示第(i)(1…j)位最多能匹配到模板串第二行的哪个位置。

    显然,当(l=c)(mask)的第一个二进制位(也就是代表了(i,j)正上方的位置)为(1)时,就会出现一次和模板串的完整匹配,因此这种转移是不合法的。否则,其他情况下我们都可以转移。新的(k,l)可以用(KMP)求出。新的(mask)相比原来的(mask)要去掉第一位,然后新加入一位:若(k=c)则新加入的位为 (1),否则为(0)

    DP数组的前两维可以滚动使用,这样优化了空间。

    注意,在DP完一整行后,要把所有方案数,累加到(dp[i][j][mask][0][0])上:因为下一行又要重新开始匹配了。

    时间复杂度:(O(nm2^{m-c+1}c^2))

    #include<cstdio>
    #include<cstring>
    using  namespace  std;
    typedef  long  long  LL;
    const  LL  mod=1000000007;
    inline  LL  ksm(LL  x,int  y)
    {
    	LL  ans=1;
    	while(y)
    	{
    		if(y&1)ans=ans*x%mod;
    		x=x*x%mod;y>>=1;
    	}
    	return  ans;
    }
    LL  dp[2][4100][10][10];//滚动数组
    int  kmp[2][10],a[2][10];
    char  st[2][10];
    int  n,m,q,c;
    inline  int  get(int  k,int  x){return  (k&(1<<(x-c)))>0;}
    inline  void  change(int  &k,int  x,int  val)
    {
    	if(k&(1<<(x-1)))k^=(1<<(x-1));
    	k|=(val<<(x-1));
    }
    int  main()
    {
    	scanf("%d%d%d%d",&n,&m,&c,&q);
    	int  limit=(1<<(m-c+1))-1;
    	for(int  t=1;t<=q;t++)
    	{
    		for(int  i=0;i<=1;i++)
    		{
    			scanf("%s",st[i]+1);
    			kmp[i][0]=-1;a[i][0]=a[i][c+1]=-1;
    			int  k=-1;
    			for(int  j=1;j<=c;j++)
    			{
    				a[i][j]=(st[i][j]=='W'?1:(st[i][j]=='B'?2:0));
    				while(k!=-1  &&  st[i][k+1]!=st[i][j])k=kmp[i][k];
    				kmp[i][j]=++k;
    			}
    		}
    		int  now=0,pre=1;
    		memset(dp[now],0,sizeof(dp[now]));
    		dp[now][0][0][0]=1;//初始化为1 
    		for(int  i=1;i<=n;i++)
    		{
    			for(int  j=1;j<=m;j++)
    			{
    				now^=1;pre^=1;
    				memset(dp[now],0,sizeof(dp[now]));
    				for(int  k=0;k<=limit;k++)//之前的状态 
    				{
    					for(int  q=0;q<=c  &&  q<j;q++)//第零行 
    					{
    						for(int  p=0;p<=c  &&  p<j;p++)//第一行 
    						{
    							if(!dp[pre][k][q][p])continue;
    							for(int  o=0;o<=2;o++)
    							{
    								int  qq=q,pp=p;
    								while(qq!=-1  &&  a[0][qq+1]!=o)qq=kmp[0][qq];
    								while(pp!=-1  &&  a[1][pp+1]!=o)pp=kmp[1][pp];
    								qq++;pp++;
    								if(pp==c  &&  get(k,j)==1)continue;
    								int  kk=k;
    								if(j>=c)change(kk,j-c+1,qq==c);
    								dp[now][kk][qq][pp]=(dp[now][kk][qq][pp]+dp[pre][k][q][p])%mod;
    							}
    						}
    					}
    				}
    			}
    			now^=1;pre^=1;
    			memset(dp[now],0,sizeof(dp[now]));
    			for(int  k=0;k<=limit;k++)//之前的状态 
    			{
    				for(int  q=0;q<=c;q++)//第零行 
    				{
    					for(int  p=0;p<=c;p++)dp[now][k][0][0]=(dp[now][k][0][0]+dp[pre][k][q][p])%mod;
    				}
    			}
    		}
    		LL  ans=0;
    		for(int  k=0;k<=limit;k++)ans=(ans+dp[now][k][0][0])%mod;
    		printf("%lld
    ",(ksm(3,n*m)-ans+mod)%mod);
    	}
    	return  0;
    }
    

    小结

    这道题目好难,完全不会QAQ

    但是感悟也不是没有,其实轮廓线DP有的时候可以优化方格图上某些状压,当状压难以转移,或者可以转移但时间较大时不妨考虑轮廓线DP。

  • 相关阅读:
    (转)simhash进行文本查重
    项目团队管理随记
    最常见到的runtime exception 异常
    javascript判断嵌套对象属性是否存在
    vim高清大图赏析(附常用快捷键)
    如何避免系统发送垃圾邮件
    使用javascript将数字转化为金额
    分享一个音乐电台(好看的 ui + html5)
    测试代码高亮显示
    Cadence 操作技巧总结1:测试点的生成2
  • 原文地址:https://www.cnblogs.com/zhangjianjunab/p/13924113.html
Copyright © 2011-2022 走看看