zoukankan      html  css  js  c++  java
  • smoj2806建筑物

    题面

    有R红色立方体,G绿色立方体和B蓝色立方体。每个立方体的边长是1。现在有一个N × N的木板,该板被划分成1×1个单元。现在要把所有的R+G+B个立方体都放在木板上。立方体必须放置在单元格内,单元格可以竖立放置多个立方体。
    
    放置在板上的立方体可以被视为“建筑物”。一个“建筑物”被称为“美丽建筑物”,当且仅当:人站在南面,向北面望过去,观察建筑物时,所有可见立方体都是相同的颜色。
    
    例如,在下图中,左侧建筑物是“美丽建筑物”,而右侧建筑物则不是。
    
    问题是:给出R,G,B,N,有多少种不同的“美丽建筑物”,答案模1000000007。
    
    输入:
    多组测试数据。
    
    第一行,一个整数group。表示有group组测试数据。1 <= group <= 8
    
    每组测试数据格式:
    
      一行,4个整数:R,G,B,N。  0<=R,G,B<26。 1 <= N <26。
    
    输出:
    共group行,每行一个整数。
    

    样例输入:

    4
    
    1  0  1  2
    
    
    1  1  2  1
    
    
    2  2  1  3
    
    0  0  10  12
    

    样例输出:

    4
    
    0
    
    162
    
    372185933
    
    

    思路:DP

    我们对于每一个颜色做为看得见的颜色做一次dp求解,最后再将答案统计起来就可以了。

    不妨设看见的是Red(R),被挡住的是Gueen(G)和Blue(B)

    那么我们从南向北看每一列都是独立的,所以就可以把大问题化为子问题。

    那么可以设出dp方程:

    f[i][x][y][z]表示前i列用了x个R,y个G,z个B的方案数
    f[i][x][y][z]+=f[i-1][x-a][y-b][z-c]*(这一列用了a个R,b个G,c个B的方案数)
    

    那么就解决下一个问题: 如何计算 这一列用了a个R,b个G,c个B的方案数。

    那么我们设

    g[x][y][z]表示一列使用x个R,y个G,z个B的方案数。
    

    计算一列满足条件的方案数时还要考虑高度,很明显,我们每一列能看到的R颜色数量就是这一列的最高高度。所以
    我们又设:

    s[i][x][y][z][h]表示一列内,前i行,使用x个R,y个G,z个B,最高高度为h的方案数
    

    那么g数组就是:

    i表示枚举的最高高度
    g[x][y][z]+=s[n][x][y][z][i];
    s[i][x][y][z][i]表示到第n行最高高度是i,使用x个R,y个G,z个B,最高高度为h的方案数。
    将他们全部相加就是g数组的值。
    

    S数组的转移比较复杂一点,分两种情况:

    1、这一行不是最高高度

    2、这一行变成了最高高度

    那么对于第一种情况:枚举自身的高度,用R颜色数量,G颜色数量,算出B颜色数量,然后进行转移

    对于第二种情况:枚举之前的最高高度,用R颜色数量,G颜色数量,算出B颜色数量,然后进行转移。

    第二种情况相比于第一种要多出一个条件,就是多出来的高度必须是R颜色的,不然就不满足方程性质。

    关于S数组的转移方式,枚举出来的方式只是使用的数量,所以还有计算出这些方块的排列方式,即可重复排列,计算公式:是总数的阶乘除以各种颜色数量的阶乘,在这道题为

    n=x+y+z
    n!/x!/y!/z!
    

    计算可重集排列可以使用逆元相关知识,或者暴力预处理也可以。

    S数组转移:

    a表示用R颜色数量,b表示用G颜色数量,c表示用B颜色数量
    第一种:
    s[n][x][y][z][h]+=s[n-1][x-a][y-b][z-c][h]*可重复排列(a,b,c)
    i表示枚举的之前最高高度
    第二种:
    s[n][x][y][z][h]+=s[n-1][x-a][y-b][z-c][i]*可重复排列(a-(h-i),b,c)
    a-(h-i)是因为有a-(h-i)个R颜色是必须选的,不能掉换位置。
    

    边界问题:下面代码有注释

    那么当S,G,F数组都处理完了就可以直接累加答案了。时间复杂度:O(n^8)

    写法

    关于这道题的写法,推荐使用记忆化搜索式写法,比较简单易懂,并且我这种写法转换成O(n^6)会写起来比较方便,所以推荐写成记忆化搜索形式。

    如果还有什么不明白的可以详见代码注释,下面已经标明了每一步的作用。

    //smoj2806 建筑物 O(n^8) 暴力写法40分
    #include<bits/stdc++.h>
    #define mod 1000000007
    #define int long long
    #define maxn 10001
    using namespace std;
    int mul_inv[maxn],mul[maxn],inv[maxn],R,G,B,n;
    int S[26][26][26][26][26],F[30][30][30][30],g[30][30][30],T;
    long long Rearrangeable(int x,int y,int z){				//计算可重集:n!/x!/y!/z!
    	int n=x+y+z;
    	return ((((mul_inv[x]*mul_inv[y])%mod)*mul_inv[z])%mod)*mul[n]%mod;	//使用逆元计算
    }
    int s(int i,int x,int y,int z,int h){					//计算S数组
    	long long sum=0;
    	if(i==1){			//边界
    		if(x==h&&(y+z)==0)return S[i][x][y][z][h]=1;	//如果剩下的全是x颜色和第一行最高高度是x的个数就说明可以
    		return S[i][x][y][z][h]=0;				//不然这种方案就不行
    	}
    	if(S[i][x][y][z][h]!=-1){return S[i][x][y][z][h]%mod;}	//记忆化
    	for(int h2=0;h2<=h;h2++){			//my height,枚举自己这一行的高度
    		for(int a=0;a<=x&&a<=h2;a++){			//枚举用了几个x颜色
    			for(int b=0;b<=y&&b+a<=h2;b++){		//枚举用了几个y颜色
    				int c=h2-a-b;					//计算用了几个z颜色
    				if(c<=z)		//用的z颜色个数要需要小于z的总个数
    				sum+=s(i-1,x-a,y-b,z-c,h)%mod 	//搜索下去累加答案
    					*Rearrangeable(a,b,c)%mod;	//乘上可重集
    				sum%=mod;
    			}
    		}
    	}
    	for(int h2=0;h2<h;h2++){			//those max height,枚举自己前面的最高高度
    		for(int a=0;a<=x&&a<=h;a++){
    			for(int b=0;b<=y&&a+b<=h;b++){
    				int c=h-a-b,a1=h-h2;			//a1表示自己超过之前最高高度几个
    				if(a1<=a&&c<=z)			//判断满不足满足条件 : 1、必须用的a1<=选择用的a  2、同上my height
    				sum+=s(i-1,x-a,y-b,z-c,h2)%mod
    					*Rearrangeable(a-a1,b,c)%mod;	//可重集能选的少了a1
    				sum%=mod;
    			}
    		}
    	}S[i][x][y][z][h]=sum%mod;
    	return S[i][x][y][z][h]%mod;		//返回
    }
    int getG(int x,int y,int z){							//计算g数组
    	long long sum=0;
    	if(g[x][y][z]!=-1)return g[x][y][z]%mod;			//记忆化
    	for(int i=0;i<=x;i++)sum+=s(n,x,y,z,i)%mod,sum%=mod;//累加全部高度的S数组
    	g[x][y][z]=sum%mod;
    	return g[x][y][z]%mod;				//返回
    }
    int f(int i,int x,int y,int z){							//计算F数组
    	long long sum=0;
    	if(i==1){return getG(x,y,z)%mod;}	//边界,第一列就把全部放完
    	if(F[i][x][y][z]!=-1)return F[i][x][y][z]%mod;		//记忆化
    	for(int a=0;a<=x;a++){
    		for(int b=0;b<=y;b++){
    			for(int c=0;c<=z;c++){
    				sum+=f(i-1,x-a,y-b,z-c)%mod 			//往下搜
    					*getG(a,b,c)%mod;					//乘上那一列的方案数
    				sum%=mod;
    			}
    		}
    	}F[i][x][y][z]=sum%mod;
    	return F[i][x][y][z];				//返回
    }
    signed main(){
    	freopen("2806.in","r",stdin);
    	freopen("2806.out","w",stdout);
    	memset(S,-1,sizeof(S));
    	mul[0]=1;mul_inv[0]=1;inv[1]=1;		//以下4行计算逆元、阶乘、阶乘逆元
    	for(int i=1;i<=100;i++)mul[i]=mul[i-1]*i%mod;
    	for(int i=2;i<=100;i++)inv[i]=(mod-mod/i)%mod*inv[mod%i]%mod;
    	for(int i=1;i<=100;i++)mul_inv[i]=mul_inv[i-1]%mod*inv[i]%mod;
    	scanf("%lld",&T);
    	while(T--){
    		memset(F,-1,sizeof(F));
    		memset(g,-1,sizeof(g));
    		long long ans=0;
    		scanf("%lld%lld%lld%lld",&R,&G,&B,&n);
    		ans+=f(n,R,G,B);ans%=mod;ans+=f(n,G,R,B);ans+=f(n,B,R,G);	//累加以3种不同颜色为可见颜色的方案
    		printf("%lld
    ",ans%mod);		//输出
    	}
    	return 0;
    }
    

    当你理解O(n^8)时,你就可以轻松理解O(n^6)的思路。

    第一层优化 O(N^6)

    其实我们在转移的dp方程的时候可以发现,被挡住的那G,B两个颜色几乎没有什么区别,他们不会影响方程的性质,只要R颜色能挡住他们,他们就不会影响一个方案的正确性。

    那么我们可以思考,可不可以将G和B两种颜色混合成一种颜色来降低复杂度呢?

    当然可以!将两种颜色混合成一种颜色后可以将复杂度降为O(N^6)的,大大提高效率。

    但问题来了,如果将两种混合成一种,计算可重复排列会出偏差,江来是要付泽任的,你民不民白!

    那么怎么解决呢?我们从全局来想,如果我们已经构成了一个合理的方案。那么现在,我们把全部的G和B都变为Y(Yellow),那这一样是看过去只有R的,我们想,在这全部的Y里,有x个是G,y
    个是B,那么我们从Y中取x个染成G,其余染成B,那么不就是原来的其中一种方案嘛!

    所以每次计算最后成上C(G+B,G)或C(G+B,B),两者是一样的。

    所以我们把3种颜色变为2种颜色,答案最后乘上一个组合数就可以了。时间复杂度:O(N^6),足矣!

    写法

    其实相比于O(N^8)的写法,O(N^6)的并没有多大的改造,我们可以将记忆化搜索刚开始传进去的

    f(n,R,G,B)
    

    改为

    f(n,R,G+B,0)
    

    就可以轻松实现O(N^8)到O(N^6)的飞跃。
    具体实现看代码,原来O(N^8)的注释就不标了

    //smoj2806 建筑物 O(n^6) 100分
    #include<bits/stdc++.h>
    #define mod 1000000007
    #define int long long
    #define maxn 10001
    using namespace std;
    int mul_inv[maxn],mul[maxn],inv[maxn],R,G,B,n;
    int S[26][26][5][51][26],F[30][30][30][60],g[30][30][60],T;
    long long Rearrangeable(int x,int y,int z){
    	int n=x+y+z;
    	return ((((mul_inv[x]%mod*mul_inv[y]%mod)%mod)*mul_inv[z]%mod)%mod)*mul[n]%mod;
    }
    int C(int n,int m){		//组合数计算
    	return (mul[n]%mod*((mul_inv[n-m]%mod*mul_inv[m]%mod)%mod))%mod;	//还是用逆元
    }
    int s(int i,int x,int y,int z,int h){		//没变
    	long long sum=0;
    	if(i==1){
    		if(x==h&&(y+z)==0)return S[i][x][y][z][h]=1;
    		return S[i][x][y][z][h]=0;
    	}
    	if(S[i][x][y][z][h]!=-1){return S[i][x][y][z][h]%mod;}
    	for(int h2=0;h2<=h;h2++){			//my height
    		for(int a=0;a<=x&&a<=h2;a++){
    			for(int b=0;b<=y&&b+a<=h2;b++){
    				int c=h2-a-b;
    				if(c<=z)
    				sum+=s(i-1,x-a,y-b,z-c,h)%mod*Rearrangeable(a,b,c)%mod;
    				sum%=mod;
    			}
    		}
    	}
    	for(int h2=0;h2<h;h2++){			//those max height
    		for(int a=0;a<=x&&a<=h;a++){
    			for(int b=0;b<=y&&a+b<=h;b++){
    				int c=h-a-b,a1=h-h2;
    				if(a1<=a&&c<=z)
    				sum+=s(i-1,x-a,y-b,z-c,h2)%mod*Rearrangeable(a-a1,b,c)%mod;
    				sum%=mod;
    			}
    		}
    	}
    	S[i][x][y][z][h]=sum%mod;
    	return S[i][x][y][z][h]%mod;
    }
    int getG(int x,int y,int z){			//没变
    	long long sum=0;
    	if(g[x][y][z]!=-1)return g[x][y][z]%mod;
    	for(int i=0;i<=x;i++)sum+=s(n,x,y,z,i)%mod,sum%=mod;
    	g[x][y][z]=sum%mod;
    	return g[x][y][z]%mod;
    }
    int f(int i,int x,int y,int z){			//没变
    	long long sum=0;
    	if(i==1){return getG(x,y,z)%mod;}
    	if(F[i][x][y][z]!=-1)return F[i][x][y][z]%mod;
    	for(int a=0;a<=x;a++){
    		for(int b=0;b<=y;b++){
    			for(int c=0;c<=z;c++){
    				sum+=f(i-1,x-a,y-b,z-c)%mod*getG(a,b,c)%mod;
    				sum%=mod;
    			}
    		}
    	}
    	F[i][x][y][z]=sum%mod;
    	return F[i][x][y][z];
    }
    signed main(){
    	freopen("2806.in","r",stdin);
    	freopen("2806.out","w",stdout);
    	memset(S,-1,sizeof(S));
    	memset(F,-1,sizeof(F));
    	memset(g,-1,sizeof(g));
    	mul[0]=1;mul_inv[0]=1;inv[1]=1;
    	for(int i=1;i<=100;i++)mul[i]=mul[i-1]*i%mod;
    	for(int i=2;i<=100;i++)inv[i]=(mod-mod/i)%mod*inv[mod%i]%mod;
    	for(int i=1;i<=100;i++)mul_inv[i]=mul_inv[i-1]%mod*inv[i]%mod;
    	scanf("%lld",&T);
    	while(T--){
    		//memset(S,-1,sizeof(S));
    		memset(F,-1,sizeof(F));
    		memset(g,-1,sizeof(g));
    		long long ans=0;
    		scanf("%lld%lld%lld%lld",&R,&G,&B,&n);
    		ans+=f(n,R,0,G+B)%mod*C(G+B,B)%mod; ans%=mod;		//将R作为可见颜色
    		ans+=f(n,G,0,R+B)%mod*C(R+B,B)%mod; ans%=mod;		//将G作为可见颜色
    		ans+=f(n,B,0,R+G)%mod*C(R+G,R)%mod; ans%=mod;		//将B作为可见颜色
    		printf("%lld
    ",ans%mod);
    	}
    	return 0;
    }
    

    那么这个复杂度就可以顺利地通过此题了,当然,还有下一层优化,可以进一步优化成O(N^5)的,在这里就先不说了,大家自己下去好好想想。

    谢谢观赏!

  • 相关阅读:
    常用函数工具记录贴
    phpCAS::handleLogoutRequests()关于java端项目登出而php端项目检测不到的测试
    Cas服务器设置(java),java、php客户端配置
    android导入项目出现R文件不能生成
    Error executing aapt: Return code -1073741819
    网页在线播发视频 在线查看文档
    jeecg的cq查询方式
    威佐夫博弈
    HDU 1850 (尼姆博奕)
    HDU2149 (巴什博弈)
  • 原文地址:https://www.cnblogs.com/hyfhaha/p/10678142.html
Copyright © 2011-2022 走看看