[luogu1880] [NOI1995]石子合並
阿新 • • 發佈:2018-10-25
for tps eof pri cpp 產生 urn str pre 這段區間內的石子數之和,這個我們可以用前綴和維護
傳送門
Solution
看上去的確是一道dp題,還是區間dp的那種
但是你發現這是一個環,非常難受
所以我們想啊:如果這是一條鏈該多好
於是乎,我們直接把這個環拉直。1 2 3
但是怎麽繞回去呢?
再接一遍好了:1 2 3 1 2
這樣就可以了!!
那麽如何進行區間dp呢?
首先我們定義\(dp[i][len]\)為合並\([i,i+len-1]\)這段區間的最優值
那麽我們一定可以找到一個中點\(k\),使得我們先合並\(dp[i][k-i+1]\)和\(dp[k+1][i+len-k-1]\),然後我們再通過這兩個區間,合並出\(dp[i][len]\)
那麽石子合並產生的權值如何計算?其實就是\([i,i+len-1]\)
綜上所述,\(dp[i][len] = max/min(dp[i][len],dp[i][k-i+1]+dp[k+1][i+len-k-1]+w[i,i+len-1])(k \in [i,i+len-1])\)
#include <cstdio> #include <cstring> #include <algorithm> #define MAXN 205 #define INF 2147483647 int dp1[MAXN][MAXN],dp2[MAXN][MAXN]; int Prefix[MAXN]; int a[MAXN]; int N; int main() { scanf("%d",&N); std::memset(dp1,0,sizeof(dp1)); std::memset(dp2,0,sizeof(dp2)); for(register int i=1;i<(N<<1);++i) { for(register int j=2;j<=N;++j) dp2[i][j] = INF; } Prefix[0] = 0; for(register int i=1;i<=N;++i) { scanf("%d",&a[i]); Prefix[i] = a[i] + Prefix[i-1]; } for(register int i=N+1;i<(N<<1);++i) { Prefix[i] = Prefix[i-1] + a[i-N]; } for(register int len=2;len<=N;++len) { for(register int i=1;i+len-1<(N<<1);++i) { for(register int k=i;k<i+len-1;++k) { dp1[i][len] = std::max(dp1[i][len],dp1[i][k-i+1]+dp1[k+1][i+len-k-1]+Prefix[i+len-1]-Prefix[i-1]); dp2[i][len] = std::min(dp2[i][len],dp2[i][k-i+1]+dp2[k+1][i+len-k-1]+Prefix[i+len-1]-Prefix[i-1]); } } } int ans_max = 0; int ans_min = INF; for(register int i=1;i<=N;++i) { ans_max = std::max(ans_max,dp1[i][N]); ans_min = std::min(ans_min,dp2[i][N]); } printf("%d\n%d\n",ans_min,ans_max); return 0; }
[luogu1880] [NOI1995]石子合並