动态规划:区间DP、状压DP

区间DP:区间dp就是在区间上进行动态规划,求解一段区间上的最优解。主要是通过合并小区间的最优解进而得出整个大区间上最优解的dp算法。例题洛谷P1880P2858P1063POJ1390POJ2955POJ3042POJ1141(POJ的这几道比较难...

将大区间分割为小区间,dp[i][j]=dp[i][k]+dp[k+1][j]。但是考虑到状态的可到达性,直接对i、j循环遍历是不行的。这时候引入l作为区间长度,循环遍历l、i,j可以被表示为i+l

对于是一个环的区间来说,可以将原来单个的区间复制一遍形成双倍的区间,在这个大区间上dp即可。大区间的话,限制条件就要发生改变:i+l<2*n&&i<2*n

//P1880
#include<bits/stdc++.h>
using namespace std;
int n;
int num[300],sum[300],f1[300][300],f2[300][300];

int d(int i,int j)
{
    return sum[j]-sum[i-1];
}
int main()
{
    scanf("%d",&n);
    for(int i=1;i<=n+n;i++){
        scanf("%d",&num[i]);
        num[i+n]=num[i];
        sum[i]=sum[i-1]+num[i];
    }
    for(int p=1;p<n;p++){
        for(int i=1,j=i+p;(i<n+n)&&(j<n+n);i++,j=i+p){
            f2[i][j]=0x7fffffff;
            for(int k=i;k<j;k++){
                f1[i][j]=max(f1[i][j],f1[i][k]+f1[k+1][j]+d(i,j));
                f2[i][j]=min(f2[i][j],f2[i][k]+f2[k+1][j]+d(i,j));
                printf("%d\n",f2[i][j]);
            }
        }
    }
    int ans1=0,ans2=0x7fffffff;
    for(int i=1;i<=n;i++){
        ans1=max(f1[i][i+n-1],ans1);
        ans2=min(f2[i][i+n-1],ans2);
    }
    printf("%d\n%d\n",ans2,ans1);
    return 0;
}

 

//POJ1390
#include<stdio.h>
#include<string.h>
#include<algorithm>
using namespace std;
int dp[205][205][205],num[205],rec[205],n;
int solve(int l,int r,int k){
    if(dp[l][r][k]) return dp[l][r][k];
    if(l==r) return (num[r]+k)*(num[r]+k);
    dp[l][r][k]=solve(l,r-1,0)+(num[r]+k)*(num[r]+k);
    for(int i=l;i<r;i++){
        if(rec[i]==rec[r]){
            dp[l][r][k]=max(dp[l][r][k],solve(l,i,num[r]+k)+solve(i+1,r-1,0));
        }
    }
    return dp[l][r][k];
}
int main(){
    int t,ct=0;
    scanf("%d",&t);
    while(t--){
        scanf("%d",&n);
        memset(rec,0,sizeof(rec));
        memset(num,0,sizeof(num));
        memset(dp,0,sizeof(dp));
        int last=-1,tmp,cnt=0;
        for(int i=1;i<=n;i++){
            scanf("%d",&tmp);
            if(tmp==last) num[cnt]++;
            else{
                rec[++cnt]=tmp;
                num[cnt]++;
                last=tmp;
            }
        }
        printf("Case %d: %d\n",++ct,solve(1,cnt,0));
    }
}

 

//POJ2955
#include<stdio.h>
#include<string.h>
#include<algorithm>
using namespace std;
int dp[105][105],n;
char str[105];
int main(){
    while(scanf("%s",str)){
        if(str[0]=='e') break;
        memset(dp,0,sizeof(dp));
        n=strlen(str);
        for(int l=1;l<n;l++){
            for(int i=0;i+l<n;i++){
                int j=i+l;
                if((str[i]=='('&&str[j]==')')||(str[i]=='['&&str[j]==']')) dp[i][j]=dp[i+1][j-1]+2;
                for(int k=i;k<j;k++){
                    dp[i][j]=max(dp[i][j],dp[i][k]+dp[k+1][j]);
                }
            }
        }
        printf("%d\n",dp[0][n-1]);
    }
}

 


状态压缩是一类问题的通用思想。当一个问题不能用单一的状态思考时,将多个状态压缩为k进制的数(k为对每个点来说,可选择的状态数),最终状态数为k^n。状态压缩常用于搜索或者DP问题中。而且一个特征是n和m的范围在[12,20]区间,小点的话暴搜可以过。

状态常借助二进制位运算来判断和转移:

对于一个状态sup,从其中找到状态子集的方法:

当状态压缩用于DP中时,就成了状压DP。关键在于完成状态压缩,之后按照普通DP来确定状态量个转移方程即可。由于kn,理论复杂度通常比较大,但是一般有状态剪枝操作,可以简化复杂度。状压DP的一个特点是,通常需要对各种状态进行循环(这话比较抽象),而且将可能的剪枝操作尽可能放到上层循环中尽可能降低复杂度。例题洛谷P1433,P2704,P4011.

//P1433,旅行商问题,由n!变为2^n
//并不一定必须要求每个点只能经过一次,多次的话由二进制变为k进制(但是位运算也不能在k进制下用了)
#include<bits/stdc++.h>
using namespace std;
int n;
struct node
{
    double x,y;
}nodes[17];
double f[17][35000],x[17],y[17];

double getDis(int a,int b)
{
    return sqrt((x[a]-x[b])*(x[a]-x[b])+(y[a]-y[b])*(y[a]-y[b]));
}

int main()
{
    scanf("%d",&n);
    //nodes[0].x=0;
    //nodes[0].y=0;
    x[0]=y[0]=0;
    for(int i=1;i<=n;i++){
        scanf("%lf%lf",&x[i],&y[i]);
    }
    memset(f,127,sizeof(f));
    for(int s=1;s<=(1<<n)-1;s++)
    {
        for(int i=1;i<=n;i++){
            if((s&(1<<(i-1)))==0){
                continue;
            }
            if(s==(1<<(i-1))){
                f[i][s]=0;
                continue;
            }
            for(int j=1;j<=n;j++){
                if(s&(1<<(j-1))==0||i==j){
                    continue;
                }
                f[i][s]=min(f[i][s],f[j][s-(1<<(i-1))]+getDis(i,j));
            }
        }
    }
    double ans=-1;
    for(int i=1;i<=n;i++){
        double t=f[i][(1<<n)-1]+getDis(i,0);
        if(ans==-1||t<ans){
            ans=t;
        }
    }
    printf("%.2lf\n",ans);
    return 0;
}

P2704,这类问题常常是图上求最短路,但是有的路径要求有一定的状态才能通过。类似的还有POJ3411

//P2704
#include<bits/stdc++.h>
using namespace std;
int n,m,line[105],num[1050];
long long dp[5][1050][1050],ans;
string str;

int main()
{
    scanf("%d%d",&n,&m);
    for(int i=1;i<=n;i++){
        cin>>str;
        for(int j=0;j<m;j++){
            if(str[j]=='H') line[i]=(line[i]<<1)+1;
            else if(str[j]=='P') line[i]=(line[i]<<1);
        }
    }
    int maxstate=1<<m;
    for(int i=0;i<maxstate;i++){
        int tmp=i;
        while(tmp!=0){
            if(tmp%2==1) num[i]++;
            tmp/=2;
        }
    }

    for(int i=1;i<=n;i++){
        //sum[i%3]=0;
        for(int j=0;j<maxstate;j++){
            if(j&line[i]) continue;
            if(((j<<1)&j)!=0||((j<<2)&j)!=0||((j>>1)&j)!=0||((j>>2)&j)!=0) continue;

            for(int k=0;k<maxstate;k++){
                if(k&line[i-1]) continue;
                if(((k<<1)&k)!=0||((k<<2)&k)!=0||((k>>1)&k)!=0||((k>>2)&k)!=0) continue;
                if((j&k)==0){
                    for(int l=0;l<maxstate;l++){
                        if(l&line[i-2]) continue;
                        if(((l<<1)&l)!=0||((l<<2)&l)!=0||((l>>1)&l)!=0||((l>>2)&l)!=0) continue;
                        if((k&l)==0&&(j&l)==0){
                            dp[i%3][j][k]=max(dp[i%3][j][k],num[j]+dp[(i+2)%3][k][l]);
                        }
                    }
                }
            }
        }
    }
    for(int i=0;i<maxstate;i++){
        for(int j=0;j<maxstate;j++){
            ans=max(ans,dp[n%3][i][j]);
        }
    }
    printf("%lld\n",ans);
    return 0;
}

POJ2441,容易看出是状压DP,入手题,多关注优化的思路

#include<stdio.h>
#include<string.h>
int n,m,rec[25][25],dp[3000000];
int main(){
    scanf("%d%d",&n,&m);
    for(int i=1;i<=n;i++){
        scanf("%d",&rec[i][0]);
        for(int j=1;j<=rec[i][0];j++) scanf("%d",&rec[i][j]);
    }

    memset(dp,0,sizeof(dp));
    dp[0]=1;
    int maxstate=(1<<m)-1,ans=0;
    for(int i=1;i<=n;i++){
        for(int s=maxstate;s>=0;s--){
            if(dp[s]){
                for(int j=1;j<=rec[i][0];j++){
                    if((s&(1<<(rec[i][j]-1)))==0){
                        dp[s|(1<<(rec[i][j]-1))]+=dp[s];
                    }
                }
            }
            dp[s]=0;
        }
    }
    for(int i=0;i<=maxstate;i++){
        ans+=dp[i];
    }
    printf("%d\n",ans);
}

POJ2836,主要是对问题的分析,把握住思路之后就比较好做了

#include<stdio.h>
#include<algorithm>
#include<string.h>
#include<math.h>
using namespace std;
int n,cnt,dp[500000];
struct node{
    int x,y;
}nodes[20];
struct rect{
    int c,s;
}rects[400];
int main(){
    while(scanf("%d",&n)!=EOF){
        if(n==0) break;
        cnt=0;
        memset(dp,0x3f,sizeof(dp));

        for(int i=1;i<=n;i++){
            scanf("%d%d",&nodes[i].x,&nodes[i].y);
        }
        for(int i=1;i<=n;i++){
            for(int j=1;j<i;j++){
                int h=abs(nodes[i].y-nodes[j].y)==0?1:abs(nodes[i].y-nodes[j].y);
                int w=abs(nodes[i].x-nodes[j].x)==0?1:abs(nodes[i].x-nodes[j].x);
                rects[cnt].c=h*w;
                rects[cnt].s=0;
                for(int k=1;k<=n;k++){
                    if(nodes[k].x>=min(nodes[i].x,nodes[j].x)&&nodes[k].x<=max(nodes[i].x,nodes[j].x)
                       &&nodes[k].y>=min(nodes[i].y,nodes[j].y)&&nodes[k].y<=max(nodes[i].y,nodes[j].y)){
                        rects[cnt].s|=1<<(k-1);
                       }
                }
                cnt++;
            }
        }

        int maxstate=(1<<n)-1;
        dp[0]=0;
        for(int s=0;s<=maxstate;s++){
            if(dp[s]!=0x3f3f3f3f){
                for(int i=0;i<cnt;i++){
                    int state=s|rects[i].s;
                    if(state!=s){
                        dp[state]=min(dp[state],dp[s]+rects[i].c);
                    }
                }
            }
        }
        printf("%d\n",dp[maxstate]);
    }
    return 0;
}

 

 

#include<stdio.h>#include<string.h>int n,m,rec[25][25],dp[3000000];int main(){    scanf("%d%d",&n,&m);    for(int i=1;i<=n;i++){        scanf("%d",&rec[i][0]);        for(int j=1;j<=rec[i][0];j++) scanf("%d",&rec[i][j]);    }
    memset(dp,0,sizeof(dp));    dp[0]=1;    int maxstate=(1<<m)-1,ans=0;    for(int i=1;i<=n;i++){        for(int s=maxstate;s>=0;s--){            if(dp[s]){                for(int j=1;j<=rec[i][0];j++){                    if((s&(1<<(rec[i][j]-1)))==0){                        dp[s|(1<<(rec[i][j]-1))]+=dp[s];                    }                }            }            dp[s]=0;        }    }    for(int i=0;i<=maxstate;i++){        ans+=dp[i];    }    printf("%d\n",ans);}

posted @ 2020-10-09 16:02  太山多桢  阅读(140)  评论(0编辑  收藏  举报