[kuangbin带你飞]专题二十二 区间DP

这篇博客详细介绍了区间动态规划(区间DP)的概念及其应用。通过分析多个在线编程比赛的题目,如ZOJ-3537、LightOj-1422等,博主kuangbin阐述了区间DP的常见模式、状态转移方程的设计以及如何利用前缀和进行状态预处理。文章还提供了具体题目的题解和代码实现,帮助读者深入理解区间DP的解题思路。
摘要由CSDN通过智能技术生成

前言:

  1. 区间dp的题目一般有几个显然的模式,比如n的范围小于1000,且通过手动模拟发现没法贪心的题。
  2. 对于dp方程一般可以往 对区间内的贡献或者是对区间外的贡献这两个角度去考虑。(有时需要逆向思维去考虑)
  3. 对于dp状态的增加维,一般用于处理区间边界的状态。
  4. 做区间dp的题经常会用到前缀和来预处理状态之间转移的关系。

正文:

A ZOJ-3537

题解:

这题可以划分为两个问题,一个是判断凸多边形,另一个是区间dp求解最优划分三角形。
几何部分
套用模板(模板大致思路就是维护一个类似单调栈的东西分别求下凸壳以及上凸壳,求出的ch数组为逆时针序的多边形)

dp部分
dp[i][j]表示该区间内的点连成的多边形分割成三角形的最小花费。
不难想到一种分割dp[i][j]=dp[i][k]+dp[k][j]+cost(i,k)+cost(j,k)也就是对与区间i-j中取出一个点进行分割区间发现会多出来 i k 、 j k ik、jk ikjk两条边。

代码:

#include <map>
#include <set>
#include <ctime>
#include <cmath>
#include <queue>
#include <stack>
#include <ctime>
#include <string>
#include <vector>
#include <cstdio>
#include <cstring>
#include <cstdlib>
#include <iostream>
#include <algorithm>
using namespace std;
typedef long long ll;
typedef unsigned long long ull;
typedef double db;
#define PB push_back
#define MP make_pair
#define INF 1073741824
#define inf 1152921504606846976
#define pi 3.14159265358979323846
//#pragma comment(linker,"/STACK:10240000,10240000")
const int N=3e5+7,M=2e6;
const long long mod=1e9+7;
inline int read(){
   int ret=0;char ch=getchar();bool f=1;for(;!isdigit(ch);ch=getchar()) f^=!(ch^'-');for(;isdigit(ch);ch=getchar()) ret=(ret<<1)+(ret<<3)+ch-48;return f?ret:-ret;}
ll gcd(ll a,ll b){
   return b?gcd(b,a%b):a;}
ll ksm(ll a,ll b,ll mod){
   int ans=1;while(b){
   if(b&1) ans=(ans*a)%mod;a=(a*a)%mod;b>>=1;}return ans;}
ll inv2(ll a,ll mod){
   return ksm(a,mod-2,mod);}//ÄæÔª
//int head[N],NEXT[N],ver[N],tot;void link(int u,int v){ver[++tot]=v;NEXT[tot]=head[u];head[u]=tot;}
const db EPS=1e-9;
inline int sign(db a){
   return a<-EPS?-1:a>EPS;}
inline int cmp(db a,db b){
   return sign(a-b);}
#define cross(p1,p2,p3) ((p2.x-p1.x)*(p3.y-p1.y)-(p3.x-p1.x)*(p2.y-p1.y))
#define ocross(p1,p2,p3) sign(cross(p1,p2,p3))
struct P{
   
    ll x,y;
    P(){
   }
    P(ll _x,ll _y) : x(_x), y(_y){
   }
    P operator+(P p){
   return {
   x+p.x,y+p.y};}
    P operator-(P p){
   return {
   x-p.x,y-p.y};}
    P operator*(ll d){
   return{
   x*d,y*d};}
    P operator/(ll d){
   return{
   x/d,y/d};}
    bool operator<(P p)const {
   
        int c=cmp(x,p.x);
        if(c) return c==-1;
        return cmp(y,p.y)==-1;
    }
    bool operator==(P o)const {
   
        return cmp(x,o.x)==0&&cmp(y,o.y)==0;
    }
};
P p[340],ch[340];
ll dp[340][340];
int n;
ll m;
ll Abs(ll x){
   
    if(x<0) return -x;
    return x;
}
ll det(ll a,ll b,ll c,ll d){
   
    return a*d-b*c;
}
ll cost(int x,int y){
   
    //cout<<Abs(p[x].x+p[y].x)*Abs(p[x].y+p[y].y)%m<<endl;
    return (Abs(ch[x].x+ch[y].x)*Abs(ch[x].y+ch[y].y))%m;
}
bool cmp_sort(P a,P b){
   
    if(a.x==b.x){
   
        return a.y<b.y;
    }
    return a.x<b.x;
}
int ConvexHull(){
   
    sort(p,p+n);
    int cnt = 0;
    for(int i = 0; i < n; i++){
   
        while(cnt > 1 && ocross(ch[cnt-2],ch[cnt-1],p[i])<=0) cnt--;
        ch[cnt++] = p[i];
    }
    int k = cnt;
    for(int i = n-2; i >= 0; i--){
   
        while(cnt > k && ocross(ch[cnt-2],ch[cnt-1],p[i])<=0) cnt--;
        ch[cnt++] = p[i];
    }
    if(n > 1) cnt--;
    return cnt;
}
int main(){
   
    //freopen("1.txt","r",stdin);
    while(~scanf("%d%lld",&n,&m)){
   
        for(int i=0;i<n;i++){
   
            scanf("%lld%lld",&p[i].x,&p[i].y);
        }
        int mark;
        if(ConvexHull()<n){
   
            puts("I can't cut.");
            continue;
        }
        memset(dp,0x3f,sizeof(dp));
        for(int i=0;i<n;i++){
   
            dp[i][i]=0;
            dp[i][(i+1)%n]=0;
        }
        for(int i=2;i<n;i++){
   
            for(int j=i-2;j>=0;j--){
   
                //cout<<i<<' '<<j<<endl;
                for(int k=j+1;k<i;k++){
   
                    //cout<<k<<' '<<i<<' '<<j<<endl;
                    dp[j][i]=min(dp[j][i],dp[j][k]+dp[k][i]+(k-j>=2?cost(j,k):0)+(i-k>=2?cost(i,k):0));
                }
            }
        }
        printf("%lld\n",dp[0][n-1]);
    }
    //cout << "time: " << (long long)clock() * 1000 / CLOCKS_PER_SEC << " ms" << endl;
    return 0;
}

LightOj-1422

题解:

可以把穿衣脱衣的过程看出一个栈,之后又可以想象成区间的覆盖,这样就可以想到一种很套路的区间dp转移方程。即若a[i]==a[j] dp[i][j]=min(dp[i+1][j],dp[i][j-1]) 之后加上区间合并的操作即可。

代码:

#include <map>
#include <set>
#include <ctime>
#include <cmath>
#include <queue>
#include <stack>
#include <ctime>
#include <string>
#include <vector>
#include <cstdio>
#include <cstring>
#include <cstdlib>
#include <iostream>
#include <algorithm>
using namespace std;
typedef long long ll;
typedef unsigned long long ull;
typedef double db;
#define PB push_back
#define MP make_pair
#define INF 1073741824
#define inf 1152921504606846976
#define pi 3.14159265358979323846
//#pragma comment(linker,"/STACK:10240000,10240000")
const int N=3e5+7,M=2e6;
const long long mod=1e9+7;
inline int read(){
   int ret=0;char ch=getchar();bool f=1;for(;!isdigit(ch);ch=getchar()) f^=!(ch^'-');for(;isdigit(ch);ch=getchar()) ret=(ret<<1)+(ret<<3)+ch-48;return f?ret:-ret;}
ll gcd(ll a,ll b){
   return b?gcd(b,a%b):a;}
ll ksm(ll a,ll b,ll mod){
   int ans=1;while(b){
   if(b&1) ans=(ans*a)%mod;a=(a*a)%mod;b>>=1;}return ans;}
ll inv2(ll a,ll mod){
   return ksm(a,mod-2,mod);}//逆元
//int head[N],NEXT[N],ver[N],tot;void link(int u,int v){ver[++tot]=v;NEXT[tot]=head[u];head[u]=tot;}

int dp[120][120];
int a[120];
int main(){
   
    //freopen("1.txt","r",stdin);
    int t;
    int ti=0;
    int n;
    scanf("%d",&t);
    while(t--){
   
        scanf("%d",&n);
        for(int i=1;i<=n;i++){
   
            scanf("%d",&a[i]);
        }
        memset(dp,0x3f,sizeof(dp));
        for(int i=1;i<=n;i++){
   
            dp[i][i]=1;
        }
        for(int i=1;i<=n;i++){
   
            for(int j=i-1;j>=1;j--){
   
                if(a[i]==a[j]){
   
                    dp[j][i]=min(dp[j+1][i],dp[j][i-1]);
                }
                for(int k=j;k<i;k++){
   
                    dp[j][i]=min(dp[j][i],dp[j][k]+dp[k+1][i]);
                }
            }
        }
        printf("Case %d: %d\n",++ti,dp[1][n]);
    }

    //cout << "time: " << (long long)clock() * 1000 / CLOCKS_PER_SEC << " ms" << endl;
    return 0;
}

POJ-2955

题解:

简单的括号匹配问题,与上题类似,处理好头尾状态之后区间合并即可

代码:

#include <map>
#include <set>
#include <ctime>
#include <cmath>
#include <queue>
#include <stack>
#include <ctime>
#include <string>
#include <vector>
#include <cstdio>
#include <cstring>
#include <cstdlib>
#include <iostream>
#include <algorithm>
using namespace std;
typedef long long ll;
typedef unsigned long long ull;
typedef double db;
#define PB push_back
#define MP make_pair
#define INF 1073741824
#define inf 1152921504606846976
#define pi 3.14159265358979323846
//#pragma comment(linker,"/STACK:10240000,10240000")
const int N=3e5+7,M=2e6;
const long long mod=1e9+7;
inline int read(){
   int ret=0;char ch=getchar();bool f=1;for(;!isdigit(ch);ch=
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值