2016級算法第三次上機-G.Winter is coming
904 Winter is coming
思路
難題。首先簡化問題, \(n\) 個0與 \(m\) 個1排成一列,連續的0不能超過x個,連續的1不能超過y個,求排列方法數。
顯然會想到這是動態規劃。最快想到的方法是 \(dp[i][j][x][y]\) 表示已經有i個北境兵j個野人參與排列,且末尾有x個連續北境士兵或y個連續野人士兵的方案數。這方法顯然是正確的,但是光是 \(dp[200][200][10][10]\) 數組已經十分接近本題內存限制了,保證MLE。狀態轉移方法是大模擬,四層for循環,每次增加一個人放在最後,討論各種情況。具體代碼可見MLE參考代碼,比較好理解。
不過這個方法已經和答案很接近了,只需要稍微優化一下。可以發現第三維和第四維很多空閑的空間被浪費了,我們沒有必要用兩個維度來分別記錄有幾個0或1,可以把第四維變成一個標誌位,0代表北境軍,1代表野人軍,而第三維記錄最後一段連續的人數,這樣空間變成原來的1/6,算是簡單的優化了一下,思想並沒有變。具體可見優化代碼,在此感謝孟堯提供。
本題還可以繼續優化,換種方式,dp[i][j][k]:已經有i個北境兵j個野人參與排列,第三維k是標誌位(0代表北境軍,1代表野人軍)的排列方法數。狀態轉移方程變為:: \(dp[i][j][0]=∑(dp[i-k][j][1])%MOD\) ;其中 \(k∈[1,min(i,x)]\) 。同理, \(dp[i][j][1]=∑(dp[i][j-k][0])%MOD\) ;其中 \(k∈[1,min(j,y)]\) 。相信你很快就能看懂,這裏用 \(dp[i-k][j][1]\) 來代表最後有k個0,相當於同時把三四維合並了,巧妙至極。具體可見最優參考代碼。
分析
本題不卡時間,卡的是內存。目的是讓大家在解決問題的時候有優化的思想(實際的目的是把它從中等題變成難題)。
DP只能意會,不可言傳。大家在做DP題的時候一定要理清思路,一般是先不管空間,畢竟以空間換時間,大多數題都是先卡時間再卡空間的。
以本題為例粗略講解一下DP,以後不會再講。記住DP具備的兩個要素:最優子結構和子問題重疊,見《算法導論》225頁。本問題明顯備最優子結構,最少的排列數是由多個較短一點的最少排列數組成。DP的多層循環也是有規律的,因為子問題的重疊,你得先把子問題算出來,才能計算更深層的。這裏i和j從小到大地計算,保證所加上的都是已經計算過的,才不會出現問題,如果這題加一個dp[i+1][j][1],那明顯不對了,因為這個還沒有計算過。狀態轉移方程有時候很微妙,需要一番數學推理。
最優參考代碼
//
// Created by AlvinZH on 2017/10/24.
// Copyright (c) AlvinZH. All rights reserved.
//
#include <cstdio>
#include <cstring>
#include <iostream>
#define MOD 1000007
using namespace std;
int n, m, x, y;
int dp[205][205][2];
int main()
{
while(~scanf("%d %d %d %d", &n, &m, &x, &y))
{
memset(dp, 0, sizeof(dp));
for (int i = 0; i <= x; ++i)
dp[i][0][0] = 1;
for (int i = 0; i <= y; ++i)
dp[0][i][1] = 1;
for (int i = 1; i <= n; ++i) {
for (int j = 1; j <= m; ++j) {
for (int k = 1; k <= min(i,x); ++k)
dp[i][j][0] = (dp[i][j][0] + dp[i-k][j][1]) % MOD;
for (int k = 1; k <= min(j,y); ++k)
dp[i][j][1] = (dp[i][j][1] + dp[i][j-k][0]) % MOD;
}
}
printf("%d\n", (dp[n][m][0] + dp[n][m][1]) % MOD);
}
}
/* 把第三四維合並,因為我們只要在狀態轉移的時候保證最後連續一段不超過x或y就好了,第三維用來記錄最後連續的是0還是1就好了。
* dp[i][j][k]:已經有i個北境兵j個野人參與排列,k為標誌位(0代表北境軍,1代表野人軍)的排列方法數。
*/
優化參考代碼
/*
Author: 孟爻(12593)
Result: AC Submission_id: 403884
Created at: Sun Nov 12 2017 23:05:10 GMT+0800 (CST)
Problem: 904 Time: 73 Memory: 11232
*/
#include <cstdio>
#include <cstring>
long f[205][205][15][2];
long M = 1000007;
long n,m,x,y;
int main() {
while(~scanf("%ld%ld%ld%ld",&n,&m,&x,&y))
{
memset(f,0,sizeof(f));
f[0][0][0][0]=1;
for(long i=0; i<=n; i++) {
for(long j=0; j<=m; j++) {
if(i) {
for(long k=1; k<=x; k++)
f[i][j][k][0]=(f[i][j][k][0]+f[i-1][j][k-1][0])%M;
for(long k=0; k<=y; k++)
f[i][j][1][0]=(f[i][j][1][0]+f[i-1][j][k][1])%M;
}
if(j) {
for(long k=1; k<=y; k++)
f[i][j][k][1]=(f[i][j][k][1]+f[i][j-1][k-1][1])%M;
for(long k=0; k<=x; k++)
f[i][j][1][1]=(f[i][j][1][1]+f[i][j-1][k][0])%M;
}
}
}
long ans=0;
for(long k=1; k<=x; k++)
ans=(ans+f[n][m][k][0])%M;
for(long k=1; k<=y; k++)
ans=(ans+f[n][m][k][1])%M;
printf("%ld\n",ans);
}
}
MLE代碼
//
// Created by AlvinZH on 2017/10/24.
// Copyright (c) AlvinZH. All rights reserved.
//
#include <cstdio>
#include <cstring>
#define MOD 1000007
int n, m, x, y;
int dp[205][205][12][12];
int main()
{
while(~scanf("%d %d %d %d", &n, &m, &x, &y))
{
memset(dp, 0, sizeof(dp));
dp[0][0][0][0] = 1;
for (int i = 0; i <= n; ++i) {
for (int j = 0; j <= m; ++j) {
for (int k = 0; k <= x; ++k) {
for (int l = 0; l <= y; ++l) {
if(dp[i][j][k][l] == 0) continue;
if(i != n && k != x)//末尾是北境兵
{
dp[i+1][j][k+1][0] += dp[i][j][k][l];
dp[i+1][j][k+1][0] %= MOD;
}
if(j != m && l != y)//末尾是野人兵
{
dp[i][j+1][0][l+1] += dp[i][j][k][l];
dp[i][j+1][0][l+1] %= MOD;
}
}
}
}
}
int ans = 0;
for (int i = 1; i <= x; ++i)
ans = (ans + dp[n][m][i][0]) % MOD;
for (int i = 1; i <= y; ++i)
ans = (ans + dp[n][m][0][i]) % MOD;
printf("%d\n", ans);
}
}
/*
* dp[i][j][x][y]表示已經有i個北境兵j個野人參與排列,且末尾有x個連續北境士兵或y個連續野人士兵的方案數。
*/
2016級算法第三次上機-G.Winter is coming