1. 程式人生 > 實用技巧 >【解題報告】區間DP

【解題報告】區間DP

刷題目錄

$\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就是從小區間合併到大區間,然後中間列舉斷點進行最優轉移即可,其餘的判斷條件另行考慮就行了!

其中 outer space invaders 釋出了題解,大家直接點進去看就行了,這裡就不再寫了!