【解題報告】區間DP
阿新 • • 發佈:2020-07-17
刷題目錄
$\color{red} \text {【解題思路】}$
基本的區間DP題目,所有的技巧都在程式碼上有所體現,主要的就是有一個技巧就是拆環,我們需要把環拆成兩段然後就可以DP了。
AC code:
#include<bits/stdc++.h> using namespace std; const int maxn=505,inf=0x3f3f3f3f; int n,num[maxn*2],sum[maxn*2],dp_min[maxn][maxn*2],dp_max[maxn][maxn*2]; int minx=inf,maxx; int main() { scanf("%d",&n); for(int i=1;i<=n;++i) scanf("%d",&num[i]),num[i+n]=num[i]; for(int i=1;i<=2*n;++i) sum[i]=sum[i-1]+num[i]; //for(register int i=(n<<1)-1;i>0;--i) for(register int j=1;j<=n;++j) { //for(register int j=i+1;j<i+n;++j) for(register int s=1;s+j<=n*2;++s) { int e=s+j; dp_min[s][e]=inf; for(register int k=s;k<e;++k) { dp_min[s][e]=min(dp_min[s][e],dp_min[s][k]+dp_min[k+1][e]+sum[e]-sum[s-1]); //腦洞DP dp_max[s][e]=max(dp_max[s][e],dp_max[s][k]+dp_max[k+1][e]+sum[e]-sum[s-1]); //代價還要加上這次合併後的石子數量 } } } for(register int i=1;i<=n;++i) { minx=min(minx,dp_min[i][i+n-1]);//刷表找最小,把環拆開! maxx=max(maxx,dp_max[i][i+n-1]);// } printf("%d\n%d\n",minx,maxx); return 0; }
$\color{red} \text {【解題思路】}$
這道題和上面那道題操作一樣,大家可以看看程式碼然後總結一下區間DP的基本操作:
AC code:
/* Name: P1063 能量項鍊 Copyright: njc Author: Mudrobot Date: 2018/10/17 18:03:05 Description: Dynamic Programming */ #include<bits/stdc++.h> #define gc() getchar()//caution!!! #define N 204 using namespace std; /*inline char gc() { static char buf[1<<18],*fs,*ft; return (fs==ft&&(ft=(fs=buf)+fread(buf,1,1<<18,stdin)),fs==ft)?EOF:*fs++; }*/ template<class T> inline void read(T &aa) { register int k=0,f=1; register char c=gc(); for (;!isdigit(c);c=gc()) if(c=='-')f=-1; for (;isdigit(c);c=gc()) k=(k<<3)+(k<<1)+(c-'0'); aa=k*f; } template<class T> inline void out(T x){if(x>9)out(x/10);putchar(x%10+'0');} int n,head[N],tail[N],dp[N][N]; int main() { //freopen(".in", "r", stdin);freopen(".out", "w", stdout); read(n); for(int i=1;i<=n;++i){ read(head[i]); if(i==1) tail[n]=head[i]; else tail[i-1]=head[i]; } for(int i=1;i<=n;++i){ head[i+n]=head[i]; tail[i+n]=tail[i]; } for(int u=0;u<n;++u){ for(int i=1;i+u<=2*n;++i){ int j=i+u; if(i==j) dp[i][j]=0; for(int k=i;k<j;++k){ dp[i][j]=max(dp[i][j],dp[i][k]+dp[k+1][j]+head[i]*tail[k]*tail[j]); } } } int ans=0; for(int i=1;i<=n;++i) ans=max(ans,dp[i][i+n-1]); printf("%d",ans); //fclose(stdin);fclose(stdout); return 0; } /* 4 2 3 5 10 */
$\color{red} \text {【解題思路】}$
也是一道上面一樣的題目,不再多說!
AC code:
/* Name: P3146 [USACO16OPEN]248 Copyright: njc Author: Mudrobot Date: 2018/10/17 18:52:45 Description: Dynamic Programming */ #include<bits/stdc++.h> #define gc() getchar()//caution!!! #define N 253 using namespace std; /*inline char gc() { static char buf[1<<18],*fs,*ft; return (fs==ft&&(ft=(fs=buf)+fread(buf,1,1<<18,stdin)),fs==ft)?EOF:*fs++; }*/ template<class T> inline void read(T &aa) { register int k=0,f=1; register char c=gc(); for (;!isdigit(c);c=gc()) if(c=='-')f=-1; for (;isdigit(c);c=gc()) k=(k<<3)+(k<<1)+(c-'0'); aa=k*f; } template<class T> inline void out(T x){if(x>9)out(x/10);putchar(x%10+'0');} int dp[N][N],n,a[N]; int main() { //freopen(".in", "r", stdin);freopen(".out", "w", stdout); read(n); for(int i=1;i<=n;++i) read(a[i]); for(int u=0;u<n;++u){ for(int i=1;i+u<=n;++i){ int j=i+u; if(i==j) dp[i][j]=a[i]; for(int k=i;k<j;++k){ if(dp[i][k]==dp[k+1][j]) dp[i][j]=max(dp[i][j],dp[i][k]+1); } } } int ans=0; for(int i=1;i<=n;++i){ for(int j=i;j<=n;++j){ ans=max(ans,dp[i][j]); } } printf("%d",ans); //fclose(stdin);fclose(stdout); return 0; } /* 4 1 1 1 2 */
$\color{red} \text {【解題思路】}$
這道題非常的神奇,因為資料範圍,所以說有一些反人類。我們需要對區間dp的動態規劃陣列進行一些小小的處理,我們把原本應該放在dp陣列外面的值,和尾區間(放在二維那裡的數)調換一下位置即可!
具體實現細節請見程式碼:
AC code:
/*
Name: P3147 [USACO16OPEN]262144
Copyright: njc
Author: Mudrobot
Date: 2018/10/17 19:28:22
Description: Dynamic Programming
*/
#include<bits/stdc++.h>
#define gc() getchar()//caution!!!
#define N 270004
using namespace std;
/*inline char gc() {
static char buf[1<<18],*fs,*ft;
return (fs==ft&&(ft=(fs=buf)+fread(buf,1,1<<18,stdin)),fs==ft)?EOF:*fs++;
}*/
template<class T>
inline void read(T &aa) {
register int k=0,f=1;
register char c=gc();
for (;!isdigit(c);c=gc()) if(c=='-')f=-1;
for (;isdigit(c);c=gc()) k=(k<<3)+(k<<1)+(c-'0');
aa=k*f;
}
template<class T>
inline void out(T x){if(x>9)out(x/10);putchar(x%10+'0');}
int dp[60][N],n,x,ans;
int main()
{
//freopen(".in", "r", stdin);freopen(".out", "w", stdout);
read(n);for(int i=1;i<=n;++i) read(x),dp[x][i]=i+1;
for(int i=2;i<=58;++i){
for(int j=1;j<=n;++j){
if(!dp[i][j])dp[i][j]=dp[i-1][dp[i-1][j]];
if(dp[i][j]) ans=i;
}
}
out(ans);
//fclose(stdin);fclose(stdout);
return 0;
}
/*
4
1
1
1
2
*/
毒瘤題:DP方程很腦洞!
$\color{red} \text {【解題思路】}$
開兩個DP陣列,分別表示最後從左邊進來,和最後從右邊進來的兩個狀態,相互進行轉移即可!
首先一定要宣告,我們預設第一個人一定是從右邊進來!
AC code:
/*
Name: P3205 [HNOI2010]合唱隊
Copyright: njc
Author: Mudrobot
Date: 2018/10/17 20:24:13
Description: Dynamic Programming
*/
#include<bits/stdc++.h>
#define gc() getchar()//caution!!!
#define N 1005
#define MOD 19650827
using namespace std;
/*inline char gc() {
static char buf[1<<18],*fs,*ft;
return (fs==ft&&(ft=(fs=buf)+fread(buf,1,1<<18,stdin)),fs==ft)?EOF:*fs++;
}*/
template<class T>
inline void read(T &aa) {
register int k=0,f=1;
register char c=gc();
for (;!isdigit(c);c=gc()) if(c=='-')f=-1;
for (;isdigit(c);c=gc()) k=(k<<3)+(k<<1)+(c-'0');
aa=k*f;
}
template<class T>
inline void out(T x){if(x>9)out(x/10);putchar(x%10+'0');}
int n,a[N],dp_l[N][N],dp_r[N][N];
int main()
{
//freopen(".in", "r", stdin);freopen(".out", "w", stdout);
read(n);for(int i=1;i<=n;++i) read(a[i]);
for(int i=1;i<=n;++i) dp_r[i][i]=1;//第一個人最後一定是從右邊排進來的!
for(int u=1;u<n;++u){
for(int i=1;i+u<=n;++i){
int j=i+u;
dp_l[i][j]=(dp_l[i+1][j]*(a[i+1]>a[i])+dp_r[i+1][j]*(a[i]<a[j]))%MOD;
dp_r[i][j]=(dp_l[i][j-1]*(a[j]>a[i])+dp_r[i][j-1]*(a[j]>a[j-1]))%MOD;
}
}
printf("%d",(dp_l[1][n]+dp_r[1][n])%MOD);
//fclose(stdin);fclose(stdout);
return 0;
}
/*
4
1701 1702 1703 1704
*/
$\color{red} \text {【解題思路】}$
這道題也是非常腦洞的一道DP題,其實我們DP只要暴力轉移就行了,如果失敗,或者不是最優解,我們的函式會自然把他卡掉!
AC code:
/*
Name: P4302 [SCOI2003]字串摺疊
Copyright: njc
Author: Mudrobot
Date: 2018/10/16 18:46:18
Description: Dynamic Programming
*/
#include<bits/stdc++.h>
#define gc() getchar()//caution!!!
#define N 104
using namespace std;
/*inline char gc() {
static char buf[1<<18],*fs,*ft;
return (fs==ft&&(ft=(fs=buf)+fread(buf,1,1<<18,stdin)),fs==ft)?EOF:*fs++;
}*/
template<class T>
inline void read(T &aa) {
register int k=0,f=1;
register char c=gc();
for (;!isdigit(c);c=gc()) if(c=='-')f=-1;
for (;isdigit(c);c=gc()) k=(k<<3)+(k<<1)+(c-'0');
aa=k*f;
}
template<class T>
inline void out(T x){if(x>9)out(x/10);putchar(x%10+'0');}
char ch[N];
int lench,dp[N][N];//表示從一個點轉移到另外一個點
bool compare(int s1,int e1,int s2,int e2){
if((e2-s1+1)%(e1-s1+1)) return false;
int len=e1-s1+1;
for(int i=s2;i<=e2;++i){
if(ch[i-len]!=ch[i]) return false;
}
return true;
}
int count(int x){
int ans=0;
while(x){
ans++;x/=10;
}
return ans;
}
int main()
{
//freopen(".in", "r", stdin);freopen(".out", "w", stdout);
scanf("%s",ch+1);lench=strlen(ch+1);
for(int u=0;u<lench;++u){
for(int i=1;i+u<=lench;++i){
int j=i+u;
dp[i][j]=u+1;
for(int k=i;k<j;++k){
if(!compare(i,k,k+1,j)) dp[i][j]=min(dp[i][j],dp[i][k]+dp[k+1][j]);
else dp[i][j]=min(dp[i][j],dp[i][k]+2+count((u+1)/(k-i+1)));
}
}
}
printf("%d",dp[1][lench]);
//fclose(stdin);fclose(stdout);
return 0;
}
/*
NEERCYESYESYESNEERCYESYESYES
*/
總結:
其實區間DP就是從小區間合併到大區間,然後中間列舉斷點進行最優轉移即可,其餘的判斷條件另行考慮就行了!