ACM第七周总结

这周开了区间DP,多亏了线性DP的学习,为新的一类问题的学习打上了基础,感觉难度还行了,不过还需要多做点题来区分问题的种类,感觉要不是专项训练真的想不出要用区间DP。
套路
memset(dp, 0x3f, sizeof(dp));
for (int i = 1; i <= n; i++) //区间长度为1的初始化
dp[i][i] = 0;
for (int len = 2; len <= n; len++) //枚举区间长度
{
for (int i = 1, j = len; j <= n; i++, j++) //区间[i,j]
{
//DP方程实现
}
}
难得有个套路,这得好好记,大概思路就是把大区间分成一个个小的,分别去求,然后找最优解。
例题
先说下作业的题。
1.一条直线上的石子问题
思路:1)定义一个n*n的数组A来存储合并石子的最小合并方式,由一开始的只有两堆石子要合并慢慢向上递归得到n堆石子合并的最小得分。
2)定义另一个于A同秩的矩阵B来存储各个合并中间的剖分。
分成的子问题,用一个数组a[i,j]表示【i,j】区间里的最优解。
从某一位置K把总区间拆成两个部分,
【i,k】和【k+1,j】。
关键代码:A[i,j]=max{a[i,k]+a[k+1,j]+sumij}}.
注意:当i=j时,让a[i,j]=0

int dpmax[1024][1024]={0};
int main(){
    int n,a[1024],sum[1024]={0};
    sum[0]=0;
    cin>>n;
    for(int i=1;i<=n;i++){
         cin>>a[i];
         sum[i]=sum[i-1]+a[i];
    }
    for(int length=2;length<=n;++length){
        for(int begin=1;begin<=n-length+1;++begin){
            int end=begin+length-1;
            dpmin[begin][end]=200000;
            dpmax[begin][end]=-1;
            int temp=sum[end]-sum[begin-1];
            for(int k=begin;k<end;++k){
                
                dpmax[begin][end]=max (dpmax[begin][end],dpmax[begin][k]+dpmax[k+1][end]+temp);
            }
        }
0

不算太难,基本题。
2.粮仓问题。
有一些食物,放在一个两端开口的仓库里,每天只能从两端选择一端取出一件食物,并且食物的价值是随着天数逐天递增,第i天的价值 本来价值i,求n天取出食物所取得的最大价值。
比较基础的区间DP问题。
思路:每次判断取左还是取右,感觉和那个回文有点类似。
关键代码:dp[i][j]=max(dp[i+1][j]+t[i]
(n-j+i), dp[i][j-1]+t[j]*(n-j+i));

#include<stdio.h>
#include<string.h>
int dp[2005][2005];
int a[2005]; 
int Max(int a,int b)
{
    if(a>b)
        return a;
    return b;
}
 
int main()
{
    int n;
    int i,j,k;
    int max;
    while(scanf("%d",&n)!=EOF)
    {
        memset(dp,0,sizeof(dp));
        memset(a,0,sizeof(a));
        for(i=1;i<=n;i++)
            {scanf("%d",&a[i]);
		 dp[i][i]=a[i]*n;}
 	   for(int k=1;k<n;k++)
          for(int i=1;i+k<=n;i++)
          {
		j=i+k;
            dp[i][j]=max(dp[i+1][j]+(n-k)*a[i],dp[i][j-1]+(n-k)*a[j]);
          } 
		printf("%d\n",dp[1][n]);
     }
    return 0;
}


以上两个题思路上没有太大问题,主要是属于入门的问题。多看多练才是重点,帮助打牢基础。
3.回文字符串。
题意:n个字符组成长度为m的字符串,给出增删字符的花费,可在字符串任意位置增删字符,求把字符串修改成回文串的最小花费。
规定dp[i][j]为将[i,j]区间改成回文串的最小花费,可以看成有回文串 。
思路:相等不处理,不相等比较增删两边中的一边。
有点要说的是,要是两个子区间大小一样会重复计数,要特别标出来最后减去,不然答案会多。

const int MAXN = 2005;
char a[MAXN], ch;
int dp[MAXN][MAXN];
int add[30],sub[30];
int main()
{
    int n, m;
    scanf("%d %d", &n, &m);
    scanf("%s", a+1);
    for (int i = 1; i <= n; i++)
    {
        scanf(" %c", &ch);
        scanf("%d %d", &add[ch-'a'], &sub[ch-'a']);
    }
    memset(dp, 0x3f, sizeof(dp));
    for (int i = 1; i <= m; i++) 
        dp[i][i] = 0;
    for (int len = 2; len <= m; len++)
        for(int i = 1, j = len; j <= m; i++, j++)
        {
            dp[i][j] = min(dp[i][j], min(add[a[i]-'a'],sub[a[i]-'a']) + dp[i+1][j]);
            dp[i][j] = min(dp[i][j], dp[i][j-1] + min(add[a[j]-'a'],sub[a[j]-'a']));
            if (a[i] == a[j])
            {
                if (len==2) 
                    dp[i][j] = 0;
                else
                    dp[i][j] = min(dp[i][j], dp[i+1][j-1]);
            }

4.博弈。
题意:俩小孩玩游戏, 轮流从左边或者右边选择连续若干个数( 除非全选, 否则只能选一边 ), 它们的和就是这个小孩这一次的得分, 然后删去选择的数, 问先手的小孩最多可以比后手的多多少分.
思路:首先先手小孩默认会赢,dp[ i ][ j ]表示在[ l , r ]这一段区间内, 先手可以得到的最大分数。每次只能选左边或者右边的若干个数, 那么假设现在剩下的是[ l , r ]这一段的数, 先手选择了[ l , k ], 剩下的就是[ k+1 , r ]; 先手选择[ k , r ], 剩下的就是[ l , k-1 ].而剩下的又是一个类似的子问题了. 所以必定是从dp[ l ][ k ]和dp[ k ][ r ]转移。
考虑到状态是先手的最大分数, 而这一轮先手选择之后, 后手变先手, 那么原后手也是按照最优策略选择, 所以也是枚举了子区间转移.由于存在先后手转换, 而且二者都是按照最优策略, 所以当前先手肯定枚举k, 在两种方向( 选右边或者选左边 )中选择子区间最优策略下自己剩的分最多的方案.
关键代码:dp[ i ][ j ]=max( sum[ l ][ k ]-dp[ l ][ k ]+sum[ k+1 ][ r ] , sum[ k ][ r ]-dp[ k ][ r ]+sum[ l ][ k-1 ] ).
这题代码自己写出来测了一遍忘保存了,就记得个思路了,和老师的稍有不同,这里就不贴全部的代码了。
总结
学的经典DP和区间DP思路大致相同,都属于大化小的问题,做起题来的感觉其实差不多,就是状态转移方程稍微有点复杂,状态转移方程写起来老是容易出点小错误,算是一个新的考验。
ps:为啥军训还要上早晚自习,我还以为会有更多的时间写代码,现在感觉和平时没啥区别/(ㄒoㄒ)/~~

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值