一次洛谷月赛的T1,当时因为是信心赛,认为第一题应该不会太难,结果想了很久,直接额放弃正解选择暴力。。。简直就是巨坑的五维DP。。。mmd
题目背景
博弈正在机房颓一个叫做《模拟城市2.0》的游戏。
2048年,经过不懈努力,博弈终于被组织委以重任,成为D市市委书记!
他勤学好问,励精图治,很快把D市建设成富强民主文明和谐的美好城市。为了进一步深化发展,他决定在海边建立一个经济开发区。
题目描述
已知开发区的建筑地块是一个n×n的矩形,而开发区可以建造三种建筑: 商业楼,住宅楼,教学楼。这任何两座建筑可以堆叠,可以紧密相邻。他需要建造正好a座商业楼,b座住宅楼,c座教学楼。但是,城市建成后要应付检查,如果安排的太混乱会被批评。不过幸运的是,只有一条公路经过了该开发区的一侧,就是说,检察人员全程只能看到开发区的一面。
因此,他需要使得开发区建成后,从正面看去,只有一种类型的建筑。
一共有多少种满足条件的方案呢? 请输出方案数,并对10^9+7取模。
注意,对于同一个n,会有多组数据。
输入输出格式
输入格式:
第一行两个整数n,T
接下来T行,每行三个整数,表示该组数据的a,b,c
输出格式:
输出共T行,每行一个整数:表示各数据答案取模10^9+7的结果。
输入输出样例
说明
对于20%的数据,n≤2 a,b,c≤3 T≤5
对于另外10%的数据,n≤3 a,b,c≤4 T≤5
对于另外20%的数据,b=0
对于另外10%的数据,T≤10
对于全部100%的数据,a,b,c,n≤25 T≤5×105
样例1
样例2
Solution:
正解DP+组合数。(可能有多种方法,这里说其中的一种)我们发现,从正面看,横排放置的方块会互相影响(因为后面的可能比前面高,状态太复杂且不好转移),我们可以考虑纵向放置的方案数。纵列和纵列之间不会相互遮挡,因此方案数很好统计。
所以我们需要处理出纵列合法的方案数。虽然有三种方块,但我们只是需要一种漏在外面,所以可以把另外两种先不考虑,把它们合成一个状态。
处理任意一列合法方案数,可以dfs处理,也可以dp。设f[i][j][k][x][y]表示放置到第 i个格子,高度为 j,已经放置的最高高度为 k,已经用了 x个能看见的方块,y 个不能看见的方块的方案数。边界条件:最高可以放到max(a,b,c),我们发现如果高度超过了所有种类颜色的数量,那么我们不可能使得这种情况合法。需要注意的是, y的最大值应当是2*max(a,b,c),因为我们把另外两种方块整合在一起了。那么就可以按顺序暴力的放置了。每个格子可能有两种操作,在它上面放一个新的格子,或者把下一个格子放到下一行去。如果下个位置的高度超过了枚举的最高高度,那么前面就没有格子可以遮挡它,它必须放能看见的种类,并更新最高高度。否则说明下个位置的高度不够高,会被前面已经放置的格子遮挡,那么放什么种类就都可以啦。
状态转移方程:
放到下一行:
f[i+1][0][k][x][y]+=f[i][k][k][x][y];
放到上面:
if (j==k)
f[i][j+1][k+1][x+1][y]+=f[i][j][k][x][y];
else
f[i][j+1][k][x+1][y]+=f[i][j][k][x][y],
f[i][j+1][k][x][y+1]+=f[i][j][k][x][y];
复杂度O(n5)
为了方便统计,标程的 i计算到了n+1这样我们就可以处理出,因为i=n的所有情况都统计在了f[n+1][0]中。
这样我们就可以处理出g[i][j]表示在某一列上,用了i 种能看见的方块,j 种遮挡住的方块的方案数。每一行的方案数处理出来了,我们就可以开始真正的dp了。
设dp[i][j][k]表示从左往右(正面)放到第i 列,用了j 种能看见的方块, k种遮挡的方块的方案数。dp[i+1][j+x][y+k]+=dp[i][j][k]*g[x][y];
复杂度O(n5)
处理出dp数组之后,实际上我们就得到了答案的一部分。如果只有两种方块可供放置,那么dp数组就是答案了就是我先前说的b=0的部分。
但是我们有三种方块。考虑让一种方块漏出来,那么另外两种方块的位置是可以随意组合的(因为并不会漏在外面)。所以还需要处理一下组合数。数据范围很小,可以直接预处理。如果只让一种(如住宅楼)能看见,那么方案数已经显而易见了。
dp[n][a][b+c]*C[c+b][b];
dp表示使得a 漏在外面,b,c遮住的方案数。C 数组是组合数,表示一共有b+c个位置,b、c随意组合的方案数。
那么最终答案就呼之欲出了。
ans=(dp[n][a][b+c]*C[b+c][b])+(dp[n][b][c+a]*C[c+a][c])+(dp[n][c][a+b]*C[a+b][a]);
我们发现其实对于同一个n ,处理出来的dp数组是完全一致的,我们可以将a,b,c处理到范围的最大值25,就可以O(1)查询得到对于不同a,b,c的答案了。
时间复杂度O(2*n5+常数),完全是可以过的(除非评测鸡太差)。
输入数据很大,为了尽量减少常数的影响,建议使用快速读入。不过经过测试, scanf 理论是可以通过的。此题没有刻意卡常,各位可以随意踩std。
代码:
1 #include<bits/stdc++.h> 2 #define mod 1000000007 3 #define N 27 4 using namespace std; 5 inline int read() 6 { 7 int x=0,f=1;char ch=getchar(); 8 while(ch<'0'||ch>'9'){if(ch=='-')f=-1;ch=getchar();} 9 while(ch>='0'&&ch<='9'){x=x*10+ch-'0';ch=getchar();} 10 return x*f; 11 } 12 int f[N][N][N][N][N<<1],g[N][N<<1],C[N<<1][N<<1],dp[N][N][N<<1]; 13 int ans,a,b,c,n,m,T; 14 inline void add(int &x,int y){x+=y;if (x>=mod) x-=mod;} 15 main() 16 { 17 n=read(),T=read(); 18 m=25; 19 f[0][0][0][0][0]=1; 20 for (int i=0;i<n;i++) 21 for (int j=0;j<=m;j++) 22 for (int k=j;k<=m;k++) 23 for (int x=k;x<=m;x++) 24 for (int y=0;y<=(m<<1);y++) 25 if (f[i][j][k][x][y]) 26 { 27 add(f[i+1][0][k][x][y],f[i][j][k][x][y]); 28 if (j==k) 29 add(f[i][j+1][k+1][x+1][y],f[i][j][k][x][y]); 30 else 31 add(f[i][j+1][k][x+1][y],f[i][j][k][x][y]), 32 add(f[i][j+1][k][x][y+1],f[i][j][k][x][y]); 33 } 34 for (int k=0;k<=m;k++) 35 for (int x=k;x<=m;x++) 36 for (int y=0;y<=(m<<1);y++) 37 add(g[x][y],f[n][0][k][x][y]); 38 C[0][0]=1; 39 for(int i=1;i<=(m<<1);i++) 40 { 41 C[i][0]=1; 42 for(int j=1;j<=i;j++) 43 { 44 C[i][j]=C[i-1][j-1]+C[i-1][j]; 45 if (C[i][j]>=mod) C[i][j]-=mod; 46 } 47 } 48 dp[0][0][0]=1; 49 for (int i=0;i<n;i++) 50 for (int j=0;j<=m;j++) 51 for (int k=0;k<=(m<<1);k++) 52 if (dp[i][j][k]) 53 for(int x=0;j+x<=m;x++) 54 for(int y=0;y+k<=(m<<1);y++) 55 add(dp[i+1][j+x][y+k],(1ll*dp[i][j][k]*g[x][y])%mod); 56 while(T--) 57 { 58 a=read(),b=read(),c=read(); 59 ans=0; 60 add(ans,(1ll*dp[n][a][b+c]*C[b+c][b])%mod); 61 add(ans,(1ll*dp[n][b][c+a]*C[c+a][c])%mod); 62 add(ans,(1ll*dp[n][c][a+b]*C[a+b][a])%mod); 63 printf("%d ",ans); 64 } 65 }