区间dp专题练习

区间dp专题练习

题意

1.Equal Sum Partitions

? 这嘛东西,\(n^2\)自己写去

\[\ \]

\[\ \]

2.You Are the One

感觉自己智力被吊打

\(dp[i][j]\)表示 , 对于当前的一个空栈 , \(i\)\(j\)这一段都出栈的最小花费

显然是长得一副区间(诡)dp(异)的样子 , 如何转移呢?(建议自己想想吧)

对于一个\(dp[i][j]\),因为这个\(i\)必须是最先入栈的 , 所以我们可以枚举它的出栈时间\(k\) , 那么总贡献就是

\[dp[i+1][k]+(k-i)*a[i]+sum[k+1..j] \cdot (k-i+1)\]

即先出栈的\(i+1...k\)的贡献和+i自己的贡献+\(k+1..j\)这一段的贡献和它们被推迟\(k-i+1\)位产生的贡献

rep(i,1,n) rep(j,i+1,n) dp[i][j]=1e9;
rep(i,1,n) dp[i][i]=a[i];
drep(i,n,1) 
    rep(j,i,n) 
        rep(k,i,j) 
            dp[i][j]=min(dp[i][j],dp[i+1][k]+dp[k+1][j]+(k-i)*a[i]+(k-i+1)*(s[j]-s[k]));

\[\ \]

\[\ \]

3.Groups

where is my head?

每个人的决策其实就是把一群人分成一组,所以可以直接一个个区间接上去

细节:满足条件的人大于\(R-L+1\)时要取\(min\)

区间dp?

rep(i,1,n) a[i]=rd(),b[i]=rd();
rep(i,0,n) rep(j,0,n) c[i][j]=0;
rep(i,0,n) dp[i]=0;
rep(i,1,n) c[a[i]][n-b[i]]++;
rep(i,0,n-1) rep(j,i+1,n) dp[j]=max(dp[j],dp[i]+min(j-i,c[i][j]));
printf("%d\n",dp[n]);

4.Sky Soldiers

又是假区间dp

const int N=1010;
const double eps=1e-6;

int n,m;
map  M;
typedef map ::iterator iter;
int p[N];
double x[N];
int cnt;
double dp[N][51];//前i个,放j个站
double sum[N][N];


int main(){
    while(~scanf("%d%d",&n,&m)&&n&&m) {
        M.clear();
        rep(i,1,n) {
            rep(j,1,rd()) {
                int p=rd();
                double x;scanf("%lf",&x);
                M[p]+=x;
            }
        }
        cnt=0;
        for(iter it=M.begin();it!=M.end();++it) {
            p[++cnt]=it->first;
            x[cnt]=it->second;
        }//暴力离散
        double ans=1e11;
        rep(i,0,cnt) rep(j,0,m) dp[i][j]=1e11;
        rep(i,1,cnt) rep(j,1,cnt) sum[i][j]=sum[i][j-1]+x[j]*abs(p[i]-p[j]);//预处理便于算贡献
        rep(i,1,cnt) dp[i][1]=sum[i][i];
        rep(i,1,cnt) {
            rep(j,1,m-1) if(dp[i][j]<1e10) {
                rep(k,i+1,cnt) {
                    int mid=(p[i]+p[k])>>1;
                    mid=lower_bound(p+1,p+cnt+1,mid)-p;
                    double s=min(sum[i][mid]-sum[i][i]+sum[k][k]-sum[k][mid],sum[i][mid-1]-sum[i][i]+sum[k][k]-sum[k][mid-1]);//贡献
                    dp[k][j+1]=min(dp[k][j+1],dp[i][j]+s);
                }
            }
        }
        rep(i,1,cnt) ans=min(ans,dp[i][m]+sum[i][cnt]-sum[i][i]);
        printf("%.2lf\n",ans);
    }
}

\[ \ \]

\[ \ \]

5.Play Game

假区间dp again

\(dp[l1][r1][l2][r2]\)表示两个序列分别还剩下\(l1,r1\),\(l2,r2\)的答案 , 很基础但是有细节

int n,m;
int a[N],b[N];
int dp[N][N][N][N];
int sum1[N],sum2[N];

int main(){
    rep(kase,1,rd()) {
        n=rd();
        rep(i,1,n) sum1[i]=a[i]=rd(),sum1[i]+=sum1[i-1];
        rep(i,1,n) sum2[i]=b[i]=rd(),sum2[i]+=sum2[i-1];
        memset(dp,0,sizeof dp);
        drep(l1,n+1,1) rep(r1,l1-1,n) drep(l2,n+1,1) rep(r2,l2-1,n) {
            if(l1>r1&&l2>r2) continue;
            int s=1e9;
            if(l1<=r1) s=min(s,min(dp[l1+1][r1][l2][r2],dp[l1][r1-1][l2][r2]));
            if(l2<=r2) s=min(s,min(dp[l1][r1][l2+1][r2],dp[l1][r1][l2][r2-1]));
            dp[l1][r1][l2][r2]=sum1[r1]-sum1[l1-1]+sum2[r2]-sum2[l2-1]-s;
        }
        printf("%d\n",dp[1][n][1][n]);
    }
}

\[ \ \]

\[ \ \]

6.Two Rabbits

这个鬼题是真的惊了

首先肯定要接两倍数组断成链

然后我们分析题目性质,对于任意路径,互为重合逆子序列时最优

即跳跃路径为原序列的一个回文子序列时一定更优

若两只兔子的路径错开,那么一定可以向两边继续延伸

细节:一个点时回文长度为1,当跳跃总长小于n时答案要加一

int n,m;
int a[N];
int dp[N][N];

int main(){
    srand(618);
    while(~scanf("%d",&n)&&n){
        rep(i,1,n) a[i]=a[i+n]=rd();
        int ans=0;
        memset(dp,0,sizeof dp);
        rep(l,1,n) drep(i,n*2-l+1,1) {
            int j=i+l-1;
            if(a[i]==a[j]) dp[i][j]=dp[i+1][j-1]+2-(i==j);
            else dp[i][j]=max(dp[i+1][j],dp[i][j-1]);
            ans=max(ans,dp[i][j]+(l

\[ \ \]

\[ \ \]

7.Dire Wolf

这个题长得还是比较区间dp的

表示自己的转移错得离谱都过掉了。。。

正确的转移方法和T2有点类似,即枚举最后一个杀死的狼,加上前后直接转移

int n,m;
int a[N],b[N];
int dp[N][N];

int main(){
    rep(kase,1,rd()){
        rep(i,1,n=rd()) a[i]=rd();
        rep(i,1,n) b[i]=rd();b[n+1]=a[n+1]=0;
        //memset(dp,10,sizeof dp);
        drep(i,n,1) rep(j,i,n) {
            dp[i][j]=1e9;
            if(i==j) dp[i][j]=a[i]+b[i-1]+b[i+1];
            else rep(k,i,j) dp[i][j]=min(dp[i][j],dp[i][k-1]+dp[k+1][j]+a[k]+b[i-1]+b[j+1]);
          //第k只狼最后杀死,贡献分开算
        }
        printf("Case #%d: %d\n",kase,dp[1][n]);
    }
}

\[ \ \]

\[ \ \]

8.Dylans loves sequence

这个题令我不想说话


int n,m;
int a[N],b[N],cnt;
int s[N],ans[N][N];
void Add(int p,int x){
    while(p<=n) s[p]+=x,p+=p&-p;
}
int Que(int p){
    int res=0;
    while(p) res+=s[p],p-=p&-p;
    return res;
}

int main(){
    n=rd(),m=rd();
    rep(i,1,n) a[i]=b[i]=rd();
    sort(b+1,b+n+1),cnt=unique(b+1,b+n+1)-b-1;
    rep(i,1,n) a[i]=lower_bound(b+1,b+cnt+1,a[i])-b;
    rep(l,1,n) {
        memset(s,0,sizeof s);
        rep(r,l,n) {
            ans[l][r]=ans[l][r-1]+r-l-Que(a[r]);
            Add(a[r],1);
        }
    }
    rep(i,1,m) {
        int x=rd(),y=rd();
        printf("%d\n",ans[x][y]);
    }
}

9.Expression

首先要理解题意

所以符号是任意安排顺序,每个符号相当于合并两个数

所以总方案数应该是\((n-1)!\)

转移是也要注意,应该乘上左右两个区间符号交叉出现的价值

将区间\([i,k];[k+1,j]\)合并至\([i,j]\)时要乘上的贡献是

\[\frac{(j-i-1)!}{(k-i)!*(j-k-1)!}\]

(因为固定了第\(k\)个符号最后,所以是\(j-i-1\))

int n,m;
int a[N],opt[N];
ll g[N][N];
ll fac[N]={1},Inv[N]={1,1};

int main(){
    rep(i,1,N-1) fac[i]=fac[i-1]*i%P;
    rep(i,2,N-1) Inv[i]=1ll*(P-P/i)*Inv[P%i]%P;
    rep(i,1,N-1) Inv[i]=Inv[i]*Inv[i-1]%P;
    while(~scanf("%d",&n)) {
        rep(i,1,n) a[i]=rd();
        rep(i,1,n-1) {
            char ch;
            do ch=getchar();
            while(ch!='+'&&ch!='-'&&ch!='*');
            if(ch=='+') opt[i]=1;
            else if(ch=='-') opt[i]=2;
            else opt[i]=3;
        }
        memset(g,0,sizeof g);
        drep(i,n,1) rep(j,i,n) {
            if(i==j) g[i][i]=a[i];//边界条件
            rep(k,i,j-1) {
                ll t=Inv[k-i]%P*Inv[j-k-1]%P*fac[j-i-1]%P;
                if(opt[k]==1) {
                    g[i][j]=(g[i][j]+(fac[k-i]*g[k+1][j]%P+fac[j-k-1]*g[i][k]%P)*t)%P;
                } else if(opt[k]==2) {
                    g[i][j]=(g[i][j]+(-1ll*fac[k-i]*g[k+1][j]%P+1ll*fac[j-k-1]*g[i][k]%P)*t)%P;
                } else {
                    g[i][j]=(g[i][j]+g[i][k]*g[k+1][j]%P*t)%P;
                }
            }
        }
        printf("%lld\n",(g[1][n]%P+P)%P);//可能有负数
    }
}

\[ \ \]

\[ \ \]

10.QSC and Master

众所周知我是一个擅长乱搞的人,所以这个奇怪的题我也是乱搞搞过去的

它长得并不是一个标准的区间dp

两种决策:

直接从左右端点向两边扩展 ;

将这段区间切开

注意由于求得是整个序列的答案所以最后还要合并一下

int n,m;
int a[N],b[N];

ll gcd(ll a,ll b){ return b==0?a:gcd(b,a%b); }
ll dp[N][N];

int main(){
    rep(kase,1,rd()){
        n=rd();
        rep(i,1,n) a[i]=rd();
        rep(i,1,n) b[i]=rd();
        memset(dp,-63,sizeof dp);
        drep(i,n,1) rep(j,i+1,n) {
            if(j-i==1) {
                if(gcd(a[i],a[j])!=1) dp[i][j]=b[i]+b[j];
                continue;
            }
            if(gcd(a[i],a[j])!=1) dp[i][j]=max(dp[i][j],dp[i+1][j-1]+b[i]+b[j]);//决策一
            rep(k,i,j) dp[i][j]=max(dp[i][j],dp[i][k]+dp[k+1][j]);//决策二
        }
        ll ans=0;
        rep(i,1,n) {
            dp[1][i]=max(dp[1][i],0ll);
            rep(j,1,i) dp[1][i]=max(dp[1][i],max(0ll,dp[1][j])+max(0ll,dp[j+1][i]));
        }//合并答案
        rep(i,1,n) rep(j,i,n) ans=max(ans,dp[i][j]);
        printf("%lld\n",ans);
    }
}

\[ \ \]

\[ \ \]

11.Kirinriki

跑错的尺取

const int N=5100,P=1e9+7;


int n,m;
char s[N];
int dp[N];

int main(){
    rep(kase,1,rd()){
        m=rd();
        scanf("%s",s+1);n=strlen(s+1);
        int ans=0;
        rep(i,3,n*2-1) {
            reg int cnt=0,p=1;
            drep(j,i/2-(!(i&1)),1) {
                if(i-j>n) break;
                cnt++;
                dp[cnt]=dp[cnt-1]+abs(s[j]-s[i-j]);
                while(dp[cnt]-dp[p-1]>m) p++;
                ans=max(ans,cnt-p+1);
            }
        }
        printf("%d\n",ans);
    }
}

\[ \ \]

\[ \ \]

12.Zuma

这个题没有素质!它卡常!

\(dp[i][j][0...4]\)表示

0:消完了

1:还剩1个0

2:还剩2个0

3:还剩1个1

4:还剩2个1

转移极其繁琐

卡常技巧:相邻相同的可以压成一个块

(希望我没有想错吧)

const int N=410;
#define chk(a,b) (a>b&&(a=b))//卡常
int n,m;
char s[N];
int a[N],b[N],c[N],dp[N][N][5];
//0 --> 0
//1 --> one 0
//2 --> two 0
//3 --> one 1
//4 --> two 1
int main(){
    int T; scanf("%d",&T);
    rep(kase,1,T){
        scanf("%s",s+1);
        n=strlen(s+1);
        rep(i,1,n) a[i]=s[i]^'0';
        int _n=0;
        rep(i,1,n) if(i==1||a[i]!=a[i-1]) {
            _n++;
            b[_n]=1; c[_n]=a[i];
        } else b[_n]++,b[_n]=b[_n];//块合并
        n=_n;
        rep(i,1,n) rep(j,1,n) rep(o,0,4) dp[i][j][o]=1e9;
        rep(i,1,n) {
            dp[i][i][c[i]*2+b[i]]=0;
            dp[i][i][0]=3-b[i];
        }//边界条件初始化
        drep(i,n,1) rep(j,i+1,n) {
            rep(k,i,j-1){
                reg int *x=dp[i][k],*y=dp[k+1][j];//指针卡常
                chk(dp[i][j][0],x[0]+y[0]);
                chk(dp[i][j][0],x[1]+y[2]);
                chk(dp[i][j][0],x[2]+y[2]);
                chk(dp[i][j][0],x[2]+y[1]);
                chk(dp[i][j][0],x[3]+y[4]);
                chk(dp[i][j][0],x[4]+y[3]);
                chk(dp[i][j][0],x[4]+y[4]);

                chk(dp[i][j][1],x[0]+y[1]);
                chk(dp[i][j][1],x[1]+y[0]);
                chk(dp[i][j][2],x[1]+y[1]);
                chk(dp[i][j][2],x[0]+y[2]);
                chk(dp[i][j][2],x[2]+y[0]);

                chk(dp[i][j][3],x[0]+y[3]);
                chk(dp[i][j][3],x[3]+y[0]);
                chk(dp[i][j][4],x[3]+y[3]);
                chk(dp[i][j][4],x[0]+y[4]);
                chk(dp[i][j][4],x[4]+y[0]);
            }
            chk(dp[i][j][0],dp[i][j][1]+2);
            chk(dp[i][j][0],dp[i][j][2]+1);
            chk(dp[i][j][0],dp[i][j][3]+2);
            chk(dp[i][j][0],dp[i][j][4]+1);
        }
        printf("Case #%d: %d\n",kase,dp[1][n][0]);
    }
}

(题解写到一半突然dalao来质问我,发现这种做法好像是错的。。。,但还不清楚是不是)

如果发现有错误请写评论

转载于:https://www.cnblogs.com/chasedeath/p/11331120.html

你可能感兴趣的:(区间dp专题练习)