zoukankan      html  css  js  c++  java
  • jzoj5990. 【北大2019冬令营模拟2019.1.6】Bear (状压dp)

    题面


    题解

    我永远讨厌dp.jpg

    搞了一个下午优化复杂度最后发现只要有一个小trick就可以A了→_→。全场都插头dp就我一个状压跑得贼慢……

    不难发现我们可以状压,对于每一行,用状态(S)表示有哪些格子是已经被上一行推倒了的,那么我们可以枚举本行所有格子的字母情况,然后计算一下这个时候下一行格子被推倒的情况,把这一行的贡献加到下一行就行了。

    简单来说就是记一个(f[pos][S])表示第(pos)行,格子被推倒的情况为(S)时的方案数,(dp[pos][S])为所有方案中推倒树的总数,那么假设一个选字母的方案会使下一行的推倒情况为(S'),会使这一行可以推倒(k)棵树,则有转移$$f[pos+1][S']+=f[pos][S]$$

    [dp[pos+1][S']+=f[pos][S]+k imes f[pos][S] ]

    最后(f[n+1][0])就是答案。这样的话能有(40)分(建议先看一下40分代码不然看不太懂AC代码的……)

    //minamoto
    #include<bits/stdc++.h>
    #define R register
    #define fp(i,a,b) for(R int i=a,I=b+1;i<I;++i)
    #define fd(i,a,b) for(R int i=a,I=b-1;i>I;--i)
    #define go(u) for(int i=head[u],v=e[i].v;i;i=e[i].nx,v=e[i].v)
    using namespace std;
    const int N=13,M=35,L=(1<<21)+5;
    int a[N][M],f[N][L],dp[N][L],g[N][L],n,m,P,lim,ans,vis[N];
    inline int add(R int x,R int y){return x+y>=P?x+y-P:x+y;}
    inline int mul(R int x,R int y){return 1ll*x*y-1ll*x*y/P*P;}
    void solve(int pos){
    	fp(i,0,lim-1)if(f[pos][i]){
    		fp(j,0,lim-1){
    			int S=0,res=0;
    			fp(k,0,m-1)vis[k]=i&(1<<k);
    			fp(k,0,m-1)if(!vis[k]){
    				if(j&(1<<k)){
    					if(k!=m-1&&!vis[k+1])vis[k]=vis[k+1]=1,++res;
    					else{
    						if(pos!=n)S|=(1<<k),++res;
    					}
    				}else{
    					if(pos!=n)S|=(1<<k),++res;
    					else if(k!=m-1&&!vis[k+1])vis[k]=vis[k+1]=1,++res;
    				}
    			}
    			f[pos+1][S]=add(f[pos+1][S],f[pos][i]);
    			dp[pos+1][S]=add(dp[pos+1][S],mul(res,f[pos][i]));
    			dp[pos+1][S]=add(dp[pos+1][S],dp[pos][i]);
    		}
    	}
    }
    int main(){
    //	freopen("testdata.in","r",stdin);
    	freopen("bear.in","r",stdin);
    	freopen("bear.out","w",stdout);
    	scanf("%d%d%d",&n,&m,&P),lim=(1<<m);
    	f[1][0]=1,dp[1][0]=0;
    	fp(i,1,n)solve(i);
    	printf("%d
    ",dp[n+1][0]);
    }
    

    然后我们发现复杂度高的主要原因是因为行数太多,不过列数很少,那么我们可以对列进行状压。然而这样的话会不符合推倒的顺序。

    我们考虑每一条副对角线,这条副对角线上肯定是从右上到左下的推倒顺序,于是我们可以对每一条副对角线进行状压,因为副对角线上元素个数为(min(n,m)),所以时间复杂度没问题

    信心满满的交上去结果只有(70)分,因为按上面那种方式枚举行的推倒情况和行的字母不太好,对于那些已经被推倒的格子,它们不管怎么选都没有影响,所以我们可以只枚举那些没有被推倒的格子,那些已经被推倒的格子直接把贡献加上去就可以了,这样的话复杂度就是(O(3^n imes)乱七八糟的常数())

    还是一句话,注意细节

    //minamoto
    #include<bits/stdc++.h>
    #define R register
    #define fp(i,a,b) for(R int i=a,I=b+1;i<I;++i)
    #define fd(i,a,b) for(R int i=a,I=b-1;i>I;--i)
    #define go(u) for(int i=head[u],v=e[i].v;i;i=e[i].nx,v=e[i].v)
    using namespace std;
    const int N=55,M=35,L=(1<<12)+5;
    int a[N][M],f[N][L],dp[N][L],n,m,P,ans,vis[N];
    int id[N][M],sz[L],bin[N];
    inline int add(R int x,R int y){return x+y>=P?x+y-P:x+y;}
    inline int mul(R int x,R int y){return 1ll*x*y-1ll*x*y/P*P;}
    void solve(int pos){
    	int cnt=pos-max(0,pos-n)-max(0,pos-m);
    	int stx,sty,edx,edy,dx,dy;
    	if(pos<=m)stx=pos,sty=1;
    		else stx=m,sty=pos-m+1;
    	if(pos<=n)edx=1,edy=pos;
    		else edx=pos-n+1,edy=n;
    	int qaq=pos+1>m,c=pos+1-max(0,pos+1-n)-max(0,pos+1-m);
    	int lim=(1<<cnt)-1;
    	fp(i,0,(1<<cnt)-1)if(f[pos][i]){
    		int T=lim^i,p=bin[sz[i]],flag=-2;
    		for(R int j=T;flag+=(j==T);j=(j-1)&T){
    			int res=0,S=0;
    			fp(k,0,c-1)vis[k]=0;
    			dx=stx,dy=sty;
    			fp(k,0,cnt-1){
    				if(!(i&(1<<k))){
    					if(j&(1<<k)){
    						if(dx!=m&&!vis[k-qaq])vis[k-qaq]=1,++res,S|=(1<<(k-qaq));
    						else if(dy!=n)vis[k+1-qaq]=1,++res,S|=(1<<(k-qaq+1));
    					}else{
    						if(dy!=n)vis[k+1-qaq]=1,++res,S|=(1<<(k-qaq+1));
    						else if(dx!=m&&!vis[k-qaq])vis[k-qaq]=1,++res,S|=(1<<(k-qaq));
    					}
    				}--dx,++dy;
    			}
    			f[pos+1][S]=add(f[pos+1][S],mul(f[pos][i],p));
    			dp[pos+1][S]=add(dp[pos+1][S],mul(mul(f[pos][i],res),p));
    			dp[pos+1][S]=add(dp[pos+1][S],mul(dp[pos][i],p));
    		}
    	}
    }
    int main(){
    //	freopen("testdata.in","r",stdin);
    	freopen("bear.in","r",stdin);
    	freopen("bear.out","w",stdout);
    	scanf("%d%d%d",&n,&m,&P);
    	fp(i,1,(1<<(min(n,m)))-1)sz[i]=sz[i>>1]+(i&1);
    	bin[0]=1;fp(i,1,30)bin[i]=mul(bin[i-1],2);
    	f[1][0]=1,dp[1][0]=0;
    	fp(i,1,n+m-2)solve(i);
    	printf("%d
    ",mul(add(dp[n+m-1][0],dp[n+m-1][1]),2));
    }
    
  • 相关阅读:
    sentinel使用内置规则检测威胁——自定义规则是使用的KQL
    在Azure Sentinel中使用威胁情报——可以自己订阅,自己创建一条indicator来使用基于情报的检测
    sm2国密算法的纯c语言版本,使用于单片机平台(静态内存分配)
    JDK-8180048 : Interned string and symbol table leak memory during parallel unlinking
    CMS垃圾收集器小实验之CMSInitiatingOccupancyFraction参数
    记spring boot线上项目内存优化
    springboot 配置log4j2日志,并输出到文件
    SpringBoot 日志管理之自定义Appender
    Linux 上 定时备份postgresql 数据库的方法
    linux下执行sh脚本,提示Command not found解决办法
  • 原文地址:https://www.cnblogs.com/bztMinamoto/p/10233864.html
Copyright © 2011-2022 走看看