【kuangbin带你飞基础DP专题】 简要题解

biubiubiu第一次尝试一天rush一套专题(由于这套简单一些),14个小时完成了,现在来补一下当时卡住的题或者比较有意思的题的题解。
【kuangbin带你飞基础DP专题】 简要题解_第1张图片


HDU1029
http://acm.hdu.edu.cn/showproblem.php?pid=1029
这个题是一个非常经典的裁判官问题,有o(n)时间o(1)空间的解法,原理是根据如果这个数列中存在一个出现次数超过n/2的数字,只要每次拿出两个不同的数,最后剩下的一定是那个出现次数最多的数字
代码

#include
#include
#include
using namespace std;
int main()
{
    int n;
    while(scanf("%d",&n)!=EOF)
    {
        int flag=-1;
        int sum=0;
        while(n--)
        {
            int x;
            scanf("%d",&x);
            if(flag==x) sum++;
            else if(sum>=1) sum--;
            else
            {
                flag=x;
                sum=1;
            }
        }
        printf("%d\n",flag);
    }
    return 0;
}

HDU1257 POJ2533
DP[i]长度为i的以DP[i]为最后一个数字的上升子序列
经典的LIS问题,这里就不讲做法了,百度一下应该有很多。
HDU1257 POJ2533代码

#include
#include
#include
using namespace std;
const int maxn = 1e5+5;
int dp[maxn];
int a[maxn];
int lis[maxn];
int main()
{
    int n;
    while(scanf("%d",&n)!=EOF)
    {
        for(int i=1;i<=n;i++) scanf("%d",&a[i]);
        int cnt=0;
        lis[cnt++]=a[1];
        for(int i=2;i<=n;i++)
        {
            if(a[i]>=lis[cnt-1])
            {
                lis[cnt++]=a[i];
            }
            else
            {
                int pos=lower_bound(lis,lis+cnt,a[i])-lis;
                lis[pos]=a[i];
            }
        }
        printf("%d\n",cnt);
    }
    return 0;
}

POJ1458
DP[i][j] 表示到A串i位置到B串j位置的最长公共子序列
经典的LCS问题,也不在这里讲解了。
POJ1458代码

#include
#include
#include
#include
using namespace std;
const int maxn = 1e3+5;
char str1[maxn];
char str2[maxn];
int dp[maxn][maxn];
int main()
{
    while(scanf("%s%s",str1+1,str2+1)!=EOF)
    {
        int len1=strlen(str1+1);
        int len2=strlen(str2+1);
        for(int i=0;i<=len1;i++)
        {
            for(int j=0;j<=len2;j++)
            {
                dp[i][j]=0;
            }
        }
        for(int i=1;i<=len1;i++)
        {
            for(int j=1;j<=len2;j++)
            {
                if(str1[i]==str2[j])
                {
                    dp[i][j]=max(dp[i][j],dp[i-1][j-1]+1);
                }
                else
                {
                    dp[i][j]=max(dp[i-1][j],dp[i][j-1]);
                }
            }
        }
        printf("%d\n",dp[len1][len2]);
    }
    return 0;
}

HDU1024 最大M子段和
不太明白这个问题这么难竟然这么多的AC,这个题卡了很久最后去找了题解。
首先说一下题意,是给一个长度为n的序列,要求从序列出m个不相交的子段,使他们的和最大
n<=1000000
我们首先想一下最暴力的DP方案
dp[i][j]表示选取第j个数字的情况下,将前j个数字分成i组的最大子段和
所以可能的情况有两种
x1y1,x2y2...xiyinum[j] ① ( x 1 , y 1 ) , ( x 2 , y 2 ) . . . ( x i , y i , n u m [ j ] )

x1y1,x2y2...xi1yi1...num[j] ② ( x 1 , y 1 ) , ( x 2 , y 2 ) . . . ( x i − 1 , y i − 1 ) , . . . , ( n u m [ j ] ) ,其中yi-1是第k个数字
dp[i][j]=max(dp[i][j1],dp[i1][k])+num[j]k=[i1,j1] d p [ i ] [ j ] = m a x ( d p [ i ] [ j − 1 ] , d p [ i − 1 ] [ k ] ) + n u m [ j ] 其 中 k = [ i − 1 , j − 1 ]
首先发现dp[i][]只和dp[i-1][],dp[i]有关,所以这里可以滚动数组优化一下
dp[t][j]=max(dp[t][j1],dp[1t][k])+num[j]k=[i1,j1] d p [ t ] [ j ] = m a x ( d p [ t ] [ j − 1 ] , d p [ 1 − t ] [ k ] ) + n u m [ j ] 其 中 k = [ i − 1 , j − 1 ]
t=0||t=1 t = 0 | | t = 1 分 别 表 示 当 前 状 态 和 上 一 状 态
我们发现最终我们只想要 max(dp[1t][k]) m a x ( d p [ 1 − t ] [ k ] ) ,也就是上一个状态中的最大值,
所以我们用一个数组保存pre[j]就可以了,pre[j]表示不包括j的j之前的最大和
DP方程就变成了
dp[t][j]=max(dp[t][j1],pre[j1])+num[j] d p [ t ] [ j ] = m a x ( d p [ t ] [ j − 1 ] , p r e [ j − 1 ] ) + n u m [ j ]
所以这个时候t也是无用的了。
最终的状态转移方程就变为:
dp[j]=max(dp[j1],pre[j1])+num[j] d p [ j ] = m a x ( d p [ j − 1 ] , p r e [ j − 1 ] ) + n u m [ j ]
这道题充分考察了降维和滚动数组优化的巧妙性,要常回来品味一下
HDU1024代码

#include
#include
#include
using namespace std;
const int INF=0x3f3f3f3f;
const int maxn = 1e6+5;
int pre[maxn];
int dp[maxn];
int a[maxn];
int main()
{
    int n,m;
    while(scanf("%d%d",&m,&n)!=EOF)
    {
        for(int i=1;i<=n;i++) scanf("%d",&a[i]);
        for(int i=1;i<=n;i++) pre[i]=0;
        for(int i=1;i<=n;i++) dp[i]=0;
        int ans=-INF;
        for(int i=1;i<=m;i++)
        {
            ans=-INF;
            for(int j=i;j<=n;j++)
            {
                dp[j]=max(dp[j-1],pre[j-1])+a[j];
                pre[j-1]=ans;
                ans=max(ans,dp[j]);
            }
        }
        printf("%d\n",ans);
    }
    return 0;
}

HDU1087
http://acm.hdu.edu.cn/showproblem.php?pid=1087
基础DP
转移方程为 dp[i]=max(dp[i],dp[j]+a[i])(a[j]<a[i]) d p [ i ] = m a x ( d p [ i ] , d p [ j ] + a [ i ] ) − − ( a [ j ] < a [ i ] )
HDU1087代码

#include
#include
#include
using namespace std;
typedef long long ll;
const int maxn = 1e4+5;
ll dp[maxn];
ll a[maxn];
int main()
{
    int n;
    while(scanf("%d",&n)!=EOF)
    {
        if(n==0) break;
        ll ans=0;
        for(int i=0;i<=n;i++)   dp[i]=0;
        for(int i=1;i<=n;i++)   scanf("%lld",&a[i]);
        for(int i=1;i<=n;i++)
        {
            for(int j=i-1;j>=0;j--)
            {
                if(a[i]>a[j]) dp[i]=max(dp[i],dp[j]+a[i]);
            }
            ans=max(ans,dp[i]);
        }
        printf("%lld\n",ans);
    }
    return 0;
}

HDU1069
题意就是一个小猴子有n种不同的长方体,每种有无数个,小猴子想把长方体垒到最高,要求上面的长方体的下表面严格小于下面的长方体的上表面,
根据题意我们知道一个长方体最多有六种可利用状态,我们按照长度为第一关键字,宽度为第二关键字排序,这样就保证了合法的拜访状态一定是按照这个顺序进行的,我们就可以n^2枚举转移了。
这一类DP有一个特点,先排序保证最优状态一定是按照这个顺序进行的,再在这个排序之后的序列上DP。
DP转移方程为:
dp[i]=max(dp[i],dp[j]+v[i].c) d p [ i ] = m a x ( d p [ i ] , d p [ j ] + v [ i ] . c ) − − v[i].a>v[j].a v [ i ] . a > v [ j ] . a & v[i].b>v[j].b v [ i ] . b > v [ j ] . b
HDU1069代码

#include
#include
#include
using namespace std;
#define dbg(x1,x2,x3) cout<<#x1<<" = "<const int maxn = 1e3+5;
int dp[maxn];
struct data
{
    int a,b,c;
}v[maxn];
bool cmp(const data &a,const data &b)
{
    if(a.a==b.a)
    {
        if(a.b==b.b)
        {
            return a.celse
        {
            return a.belse
    {
        return a.aint main()
{
    int n;
    int T=1;
    while(scanf("%d",&n)!=EOF)
    {
        if(n==0) break;
        int cnt=0;
        for(int i=1;i<=n*6;i++) dp[i]=0;
        for(int i=1;i<=n;i++)
        {
            int a[3];
            scanf("%d%d%d",&a[0],&a[1],&a[2]);
            sort(a,a+3);
            do
             {
                 v[++cnt].a=a[0];
                 v[cnt].b=a[1];
                 v[cnt].c=a[2];
             }while (next_permutation(a,a+3));
        }
        v[0].a=0;
        v[0].b=0;
        v[0].c=0;
        sort(v+1,v+1+cnt,cmp);
        for(int i=1;i<=cnt;i++)
        {
            for(int j=i-1;j>=0;j--)
            {
                if(v[i].a>v[j].a&&v[i].b>v[j].b)
                {
                    dp[i]=max(dp[i],dp[j]+v[i].c);
                }
            }
        }
        int ans=0;
        for(int i=1;i<=cnt;i++) ans=max(ans,dp[i]);
        printf("Case %d: maximum height = %d\n",T++,ans);
    }
    return 0;
}

HDU1176
http://acm.hdu.edu.cn/showproblem.php?pid=1176
题意就是在x轴上一共有0-10是一个位置,最开始你站在5位置,每秒可以选择向左或者向右或者不动,给出n组馅饼掉落情况x,y,表示时间y在x位置掉落一个馅饼,我们可以用dp[i][j]表示时间i站在j的最大收获,很明显状态只能由上一秒的左中右三个位置转移,所以我们最初只将0秒时的5位置设置为可达,o(n)*11枚举一下就可以了
HDU1176代码

#include
#include
#include
using namespace std;
const int maxn = 1e5+5;
int dp[maxn][12];
int con[maxn][12];
int main()
{
    int n;
    while(scanf("%d",&n)!=EOF)
    {
        if(n==0) break;
         for(int i=0;i<=100000;i++)
           for(int j=0;j<12;j++)
             {
                 dp[i][j]=-1;
                 con[i][j]=0;
             }
        while(n--)
        {
            int a,b;
            scanf("%d%d",&a,&b);
            con[b][a]++;
        }
        dp[0][5]=0;
        for(int i=1;i<=100000;i++)
        {
            for(int j=0;j<11;j++)
            {
                if(j==0)
                {
                    dp[i][j]=max(dp[i-1][j],dp[i-1][j+1])+con[i][j];
                }
                else if(j<11)
                {
                    dp[i][j]=max(max(dp[i-1][j-1],dp[i-1][j+1]),dp[i-1][j])+con[i][j];
                }
                else
                {
                    dp[i][j]=max(dp[i-1][j-1],dp[i-1][j])+con[i][j];
                }
            }
        }
        int ans=0;
        for(int i=0;i<11;i++)   ans=max(ans,dp[100000][i]);
        printf("%d\n",ans);
    }
    return 0;
}

HDU1114
HDU1114
经典的背包问题,从低容量向高容量转移就可以了。
HDU1114代码

#include
#include
#include
using namespace std;
const int maxn = 1e4+5;
const int INF = 0x3f3f3f3f;
int p[maxn];
int w[maxn];
int dp[maxn];
int main()
{
    int T;
    scanf("%d",&T);
    while(T--)
    {
        int x,y;
        scanf("%d%d",&x,&y);
        int tmp=y-x;
        int n;
        scanf("%d",&n);
        for(int i=1;i<=n;i++) scanf("%d%d",&p[i],&w[i]);
        for(int i=0;i<=tmp;i++) dp[i]=INF;
        dp[0]=0;
        for(int i=1;i<=tmp;i++)
        {
            for(int j=1;j<=n;j++)
            {
                if(i>=w[j]) dp[i]=min(dp[i],dp[i-w[j]]+p[j]);
            }
        }
        if(dp[tmp]==INF) printf("This is impossible.\n");
        else printf("The minimum amount of money in the piggy-bank is %d.\n",dp[tmp]);
    }
    return 0;
}

HDU1074
题意就是有n个科目要掌握,每种科目有一个开始时间和持续时间,求最少需要的时间
由于这个题n比较小,就是经典的状压DP,我们可以从小到大枚举所有状态,然后对每种状态保留能达到当前状态的最优解,再从小状态向大状态转移。由于这道题要输出保证字典序而且原题就是按照字典序给出的,所以我们可以倒着遍历,这样就保证了字典序。
HDU1074代码

#include
#include
#include
#include
using namespace std;
const int INF=0x3f3f3f3f;
const int maxn = 17;
struct data
{
    char str[105];
    int d;
    int w;
}x[15];
struct data2
{
    int time,pre,cost,en;
}dp[1<int ans[maxn];
int main()
{
    int T;
    scanf("%d",&T);
    while(T--)
    {
        int n;
        scanf("%d",&n);
        for(int i=0;iscanf("%s%d%d",x[i].str,&x[i].d,&x[i].w);
        }
        int all=(1<1;
        for(int i=0;i<=all;i++)
        {
            dp[i].time=0;
            dp[i].pre=0;
            dp[i].en=0;
            dp[i].cost=INF;
        }
        dp[0].cost=0;
        for(int i=1;i<=all;i++)
        {
            for(int j=n-1;j>=0;j--)//为了字典序。
            {
                if(i&(1<int tmp=i-(1<int pp=max(0,dp[tmp].time+x[j].w-x[j].d);
                    if(pp+dp[tmp].costint cnt=0;
        int tmp=all;
        while(tmp)
        {
            ans[cnt++]=dp[tmp].en;
            tmp=dp[tmp].pre;
        }
        printf("%d\n",dp[all].cost);
        for(int i=cnt-1;i>=0;i--)
        {
            printf("%s\n",x[ans[i]].str);
        }
    }
    return 0;
}

HDU1160
HDU1160

经典的按照某个维度排序保证最优解一定是在这个顺序上的,然后再进行DP
HDU1160代码

#include
#include
#include
using namespace std;
const int maxn = 1e3+5;
struct data
{
    int a,b;
    int id;
}m[maxn];
int dp[maxn];
int pre[maxn];
int anss[maxn];
bool cmp(const data &a,const data &b)
{
    if(a.a==b.a)
    {
        return a.b>b.b;
    }
    return a.aint main()
{
    int cnt=1;
    while(scanf("%d%d",&m[cnt].a,&m[cnt].b)!=EOF)
    {
        m[cnt].id=cnt;
        cnt++;
    }
    cnt--;
    sort(m+1,m+1+cnt,cmp);
    dp[0]=0;
    int en=0;
    int ans=0;
    for(int i=1;i<=cnt;i++)
    {
        for(int j=i-1;j>=1;j--)
        {
            if(m[i].a>m[j].a&&m[i].bif(dp[j]+1>dp[i])
                {
                    pre[i]=j;
                    dp[i]=dp[j]+1;
                    if(dp[i]>ans)
                    {
                        ans=dp[i];
                        en=i;
                    }
                }
            }
        }
    }
    cnt=0;
    while(en!=0)
    {
        anss[cnt++]=m[en].id;
        en=pre[en];
    }
    printf("%d\n",cnt);
    for(int i=cnt-1;i>=0;i--) printf("%d\n",anss[i]);
    return 0;
}

HDU1260
题意就是一个人单独买票有一个花费,和前一个人一起买票有另一种花费,求最少花费
经典的绑定DP,对于第i个人,可以选择和前一个人一起买,不和前一个人一起买
dp[i]=max(dp[i1]+a[i],dp[i2]+b[i]) d p [ i ] = m a x ( d p [ i − 1 ] + a [ i ] , d p [ i − 2 ] + b [ i ] )
后面注意中午12点是PM

HDU1260代码

#include
#include
#include
using namespace std;
const int maxn = 2e3+5;
int dp[maxn];
int one[maxn];
int db[maxn];
int a[maxn];
int b[maxn];
int main()
{
    int t;
    scanf("%d",&t);
    while(t--)
    {
        int n;
        scanf("%d",&n);
        for(int i=1;i<=n;i++) scanf("%d",&a[i]);
        for(int i=2;i<=n;i++) scanf("%d",&b[i]);
        dp[1]=a[1];
        for(int i=2;i<=n;i++)   dp[i]=min(dp[i-1]+a[i],dp[i-2]+b[i]);
        int tmp=dp[n];
        int h=tmp/3600;
        int m=(tmp%3600)/60;
        int s=tmp%60;
        int ansh=8+h;

        if(ansh>12) printf("0%d:",ansh-12);
        else if(ansh==12) printf("12:");
        else if(ansh<10) printf("0%d:",ansh);
        else printf("%d:",ansh);

        if(m<10) printf("0%d:",m);
        else printf("%d:",m);

        if(s<10) printf("0%d",s);
        else printf("%d",s);

        if(ansh>=12) printf(" pm\n");
        else printf(" am\n");
    }
    return 0;
}

POJ3616
POJ3616
题意就是给你n个时间段,和一个时间总量,问你最多完成多少个时间段的任务,两个任务之间要休息R分钟
注意到M只有1000,所以M^2暴力转移一下就可以了。

dp[i]=max(dp[i],dp[j]+x[i].val)x[i].l>=x[j].r+R d p [ i ] = m a x ( d p [ i ] , d p [ j ] + x [ i ] . v a l ) − − x [ i ] . l >= x [ j ] . r + R

POJ3616代码

#include
#include
#include
using namespace std;
typedef long long ll;
const int maxn = 1e6+5;
ll dp[maxn];
struct data
{
    int l,r;
    ll val;
}x[1005];
bool cmp(const data &a,const data &b)
{
    if(a.l==b.l) return a.rreturn a.lint main()
{
    int n,m,r;
    scanf("%d%d%d",&n,&m,&r);
    for(int i=1;i<=m;i++)
    {
        scanf("%d%d%lld",&x[i].l,&x[i].r,&x[i].val);
    }
    sort(x+1,x+1+m,cmp);
    ll ans=0;
    for(int i=1;i<=m;i++) dp[i]=x[i].val;
    for(int i=1;i<=m;i++)
    {
        for(int j=1;jif(x[i].l>=x[j].r+r) dp[i]=max(dp[i],dp[j]+x[i].val);
        }
        ans=max(ans,dp[i]);
    }
    printf("%lld\n",ans);
    return 0;
}

POJ1661
POJ1661题目链接
题意就是一个小人在最高的平台上选择向左或者向右走,每秒的行走速度和下降速度都是1,有一个最长的下降的距离,超过这个距离就会GG,求最小时间
由于小人只能选择从左侧下降或者从右侧下降,我们只要设置两个下降状态,然后按照高度排序从下向上一下就可以了
设置 dp[0][i]i d p [ 0 ] [ i ] 为 小 人 站 在 i 块 选 择 从 左 侧 下 降 时 的 到 达 地 面 的 最 短 时 间
设置 dp[1][i]i d p [ 1 ] [ i ] 为 小 人 站 在 i 块 选 择 从 右 侧 下 降 时 的 到 达 地 面 的 最 短 时 间
然后从低到高进行更新就可以了。具体实现看代码。
POJ1661代码

#include
#include
#include
#include
#include
#include
using namespace std;
typedef long long ll;
const int maxn = 1005;
const int INF = 0x3f3f3f3f;
struct data
{
    int l,r,h;
}x[maxn];
int dp[2][maxn];
bool cmp(const data &a,const data &b)
{
    if(a.h==b.h) return a.l<=b.l;
    return a.hint main()
{
    int t;
    scanf("%d",&t);
    while(t--)
    {
        int n,X,Y,maxx;
        scanf("%d%d%d%d",&n,&X,&Y,&maxx);
        for(int i=1;i<=n;i++)
        {
            scanf("%d%d%d",&x[i].l,&x[i].r,&x[i].h);
        }
        for(int i=0;i<=n+1;i++)
        {
            dp[0][i]=INF;
            dp[1][i]=INF;
        }
        sort(x+1,x+1+n,cmp);
        x[n+1].l=X;
        x[n+1].r=X;
        x[n+1].h=Y;
        for(int i=1;i<=n+1;i++)
        {
            for(int j=i-1;j>=1;j--)
            {
                if(x[i].h-x[j].h<=maxx&&x[i].h>x[j].h)
                {
                    if(x[j].l<=x[i].l&&x[i].l<=x[j].r)
                    {
                        dp[0][i]=min(dp[0][i],x[i].h-x[j].h+min(dp[0][j]+x[i].l-x[j].l,dp[1][j]+x[j].r-x[i].l));
                        break;
                    }
                }
            }
            if(dp[0][i]==INF)
            {
                if(x[i].h>maxx) dp[0][i]=INF;
                else dp[0][i]=x[i].h;
            }
            for(int j=i-1;j>=1;j--)
            {
                if(x[i].h-x[j].h<=maxx&&x[i].h>x[j].h)
                {
                    if(x[j].l<=x[i].r&&x[i].r<=x[j].r)
                    {
                        dp[1][i]=min(dp[1][i],x[i].h-x[j].h+min(dp[0][j]+x[i].r-x[j].l,dp[1][j]+x[j].r-x[i].r));
                        break;
                    }
                }
            }
            if(dp[1][i]==INF)
            {
                if(x[i].h>maxx) dp[1][i]=INF;
                else dp[1][i]=x[i].h;
            }
        }
        printf("%d\n",min(dp[0][n+1],dp[1][n+1]));
    }
    return 0;
}

POJ3186
POJ3186
题意就是给一个数组v,每次可以取前面的或者后面的,第k次取的v[i]价值为v[i]*k,问总价值最大是多少。·
dp[i][j] d p [ i ] [ j ] 为 取i到j后 的最大值,可能由 d[i+1][j]d[i][j1] d [ i + 1 ] [ j ] 或 者 d [ i ] [ j − 1 ] 转移而来。
转移方程: dp[i][j]=max(dp[i+1][j]+p[i](n+ij),dp[i][j1]+p[j](n+ij)); d p [ i ] [ j ] = m a x ( d p [ i + 1 ] [ j ] + p [ i ] ∗ ( n + i − j ) , d p [ i ] [ j − 1 ] + p [ j ] ∗ ( n + i − j ) ) ; 其中 n(ji) n − ( j − i ) 是第几次取

POJ3186代码

#include
#include
#include
#include
using namespace std;
const int maxn = 2005;
int a[maxn];
int dp[maxn][maxn];
int main()
{
    int n;
    while(scanf("%d",&n)!=EOF)
    {
        memset(dp,0,sizeof(dp));
        for(int i=1;i<=n;i++)
        {
            scanf("%d",&a[i]);
            dp[i][i]=a[i];
        }
        for(int i=n;i>=1;i--)
        for(int j=i;j<=n;j++)
        {
           dp[i][j]=max(dp[i+1][j]+a[i]*(n+i-j),dp[i][j-1]+a[j]*(n+i-j));
        }
        printf("%d\n",dp[1][n]);
    }
}

HDU1078
HDU1078
这个题我们可以看出每个点不管之前怎么走,之后能走的最大路径都是固定的,因为之前不管怎么走,只要走到了这个点,就说明没有走过比当前点权值大的点,所以我们可以记忆化搜索一下,就可以了。
HDU1078代码

#include
#include
#include
using namespace std;
const int maxn = 105;
int dp[maxn][maxn];
int v[maxn][maxn];
int dis[4][2]={1,0,-1,0,0,1,0,-1};
int n,k;
int dfs(int x,int y)
{
    if(dp[x][y]!=-1) return dp[x][y];
    dp[x][y]=v[x][y];
    for(int i=1;i<=k;i++)
    {
        for(int j=0;j<4;j++)
        {
            int tx=x+dis[j][0]*i;
            int ty=y+dis[j][1]*i;
            if(tx<1||tx>n||ty<1||ty>n) continue;
            if(v[tx][ty]<=v[x][y]) continue;
            dp[x][y]=max(dp[x][y],v[x][y]+dfs(tx,ty));
        }
    }
    return dp[x][y];
}
int main()
{
    while(scanf("%d%d",&n,&k)!=EOF)
    {
        if(n==-1&&k==-1) break;
        for(int i=1;i<=n;i++)
        {
            for(int j=1;j<=n;j++)
            {
                scanf("%d",&v[i][j]);
                dp[i][j]=-1;
            }
        }
        printf("%d\n",dfs(1,1));
    }
    return 0;
}

HDU2859
HDU2859
求最大对称子矩阵,对称是延对角线对称,由于本题给出的对角线不方便操作,我们将所有字符串逆置一下,就变成了好操作的对角线,然后我们对每一行每一列进行hash,dp的时候只要从左上角dp值一直减小到0,判断是否有len满足向上和向左的hash值相等。
HDU2859代码

#include
#include
#include
#include
using namespace std;
typedef unsigned long long ull;
const int maxn = 1e3+5;
ull hash_1[maxn][maxn],hash_2[maxn][maxn],xp[maxn];
char str[maxn][maxn];
void init()
{
    xp[0]=1;
    for(int i=1;i1]*13331;//这里13331玄学数字,大概可以随意换
    return ;
}
void make_hash(int n)//处理出str的hash值
{
    for(int i=0;i0;
        for(int j=n-1;j>=0;j--)
        {
            hash_1[i][j]=hash_1[i][j+1]*13331+(str[i][j]-'A')+1;
        }
    }
    for(int i=0;i0;
        for(int j=n-1;j>=0;j--)
        {
            hash_2[i][j]=hash_2[i][j+1]*13331+str[j][i]-'A'+1;
        }
    }
    return ;
}
ull Get_hash1(int x,int i,int L)//得到起点为i,长度为L的子串的hash值
{
    return hash_1[x][i]-hash_1[x][i+L]*xp[L];
}
ull Get_hash2(int x,int i,int L)//得到起点为i,长度为L的子串的hash值
{
    return hash_2[x][i]-hash_2[x][i+L]*xp[L];
}
int dp[maxn][maxn];
int main()
{
    int n;
    init();
    while(scanf("%d",&n)!=EOF)
    {
        if(n==0) break;
        for(int i=0;iscanf("%s",str[i]);
        for(int i=0;ifor(int i=0;ifor(int j=0;j1;
        int ans=1;
        for(int i=1;ifor(int j=1;jint tmp=dp[i-1][j-1];
                 for(int len=tmp;len>=0;len--)
                 {
                    ull tmp1=Get_hash1(i,j-len,len+1);
                    ull tmp2=Get_hash2(j,i-len,len+1);
                    if(tmp1==tmp2)
                    {
                        dp[i][j]=len+1;
                        break;
                    }
                 }
                 ans=max(ans,dp[i][j]);
            }
        }
        printf("%d\n",ans);
    }
    return 0;
}

POJ3666
本题题意就是用最小代价将原序列变为单调不增或者单调不减序列,
由于单调不增和单调不减是对称的,我们先想一下单调不减的情况
这个题首先我们想一下比较暴力的转移方程
dp[i][j]=abs(jw[j])+min(dp[i1][k])(k<=j) d p [ i ] [ j ] = a b s ( j − w [ j ] ) + m i n ( d p [ i − 1 ] [ k ] ) − − ( k <= j )
很明显 dp[i1][k] d p [ i − 1 ] [ k ] 这个状态就是上一状态中最小值,所以我们就可以把dp方程变为
dp[i][j]=abs(jw[j])+minnminn=min(dp[i1][k]) d p [ i ] [ j ] = a b s ( j − w [ j ] ) + m i n n − − m i n n = m i n ( d p [ i − 1 ] [ k ] )
我们发现题目中A[i]达到1e9,而且改变之后的序列每个值肯定等于原序列中的某个值,所以我们只要对原序列排序之后把j变为扫原序列就可以了。

POJ3666代码

#include
#include
#include
#include
using namespace std;
const int maxn = 2005;
const long long INF = 0x3f3f3f3f3f3f3f3fLL;
int n;
int a[maxn],b[maxn];
long long dp[maxn][maxn];
int main()
{
    scanf("%d",&n);
    for(int i=1;i<=n;i++)
    {
        scanf("%d",&a[i]);
        b[i]=a[i];
    }
    sort(b+1,b+1+n);
    for(int i=1;i<=n;i++)
    {
        long long minn=dp[i-1][1];
        for(int j=1;j<=n;j++)
        {
            minn=min(minn,dp[i-1][j]);
            dp[i][j]=abs(a[i]-b[j])+minn;
        }
    }
    long long ans=dp[n][1];
    for(int i=1;i<=n;i++)
        ans=min(ans,dp[n][i]);
    printf("%lld\n",ans);
    return 0;
}

POJ1015
POJ1015
能力优先表述不清引用的kuangbin大神的题解
题意:在Frobnia,一个遥远的国家,法庭审判的判决是由普通市民组成的陪审团决定的。每次审判开始前,都要挑选出一个陪审团。首先,随机从公众中抽取几个人。控辩双方指定一个0到20的分数表示对这些人的偏好。0意味着非常不喜欢,20意味着非常适合进入陪审团。法官根据双方给出的分数,决定陪审团的组成。为了确保一个公正的审判,陪审团的偏袒倾向应尽可能的被平衡。因此,陪审团应在控辩双方都满意的方式下挑选出来。现在我们要将这个挑选行为更精确化:给出n个候选陪审员,对于任意候选陪审员i,给出两个值di(辩方给出的分数)和pi(控方给出的分数),你要挑选出一个m人的陪审团。如果J是一个具有m个元素的子集{1…..n},那么D(J)=sum(dk),k属于J,还有P(J)=sum(pk),k属于J,分别表似控辩双方给出的总分。对于一个最佳的陪审团J,|D(J)-P(J)|必须是最小的。如果有多个方案满足|D(J)-P(J)|最小,那么应该选择D(J)+P(J)值最大的方案,因为陪审团应尽可能的符合控辩双方理想中的选择。要求你写一个程序实现陪审团的挑选过程并从给出的人选中选择出最佳的陪审团。
我们设 f[j][k] f [ j ] [ k ] 为有j个候选人差值为k的最大和,我们想象一下状态该如何转移,我们可以从 f[i1][n] f [ i − 1 ] [ n ] 转移到 f[i][j] f [ i ] [ j ]
问题的关键是建立递推关系。需要从哪些已知条件出发,才能求出 f(j,k) f ( j , k ) 呢?显然,方案f(j, k)是由某个可行的方案 f(j1,x) f ( j − 1 , x ) (20×mx20×m) ( − 20 × m ≤ x ≤ 20 × m ) 演化而来的。可行方案 f(j1,x) f ( j − 1 , x ) 能演化成方案 f(j,k) f ( j , k ) 的必要条件是:存在某个候选人i,i 在方案 f(j1,x) f ( j − 1 , x ) 中没有被选上,且 x+V(i)=k x + V ( i ) = k 。在所有满足该必要条件的f(j-1, x)中,选出 f(j1,x)+S(i) f ( j − 1 , x ) + S ( i ) 的值最大的那个,那么方案 f(j1,x) f ( j − 1 , x ) 再加上候选人i,就演变成了方案 f(j,k) f ( j , k ) 。这中间需要将一个方案都选了哪些人都记录下来。不妨将方案 f(j,k) f ( j , k ) 中最后选的那个候选人的编号,记在二维数组的元素 path[j][k] p a t h [ j ] [ k ] 中。那么方案 f(j,k) f ( j , k ) 的倒数第二个人选的编号,就是 path[j1][kV[path[j][k]] p a t h [ j − 1 ] [ k − V [ p a t h [ j ] [ k ] ] 。假定最后算出了解方案的辩控差是k,那么从 path[m][k] p a t h [ m ] [ k ] 出发,就能顺藤摸瓜一步步求出所有被选中的候选人。初始条件,只能确定 f(0,0)=0 f ( 0 , 0 ) = 0 。由此出发,一步步自底向上递推,就能求出所有的可行方案 f(m,k)(20×mk20×m) f ( m , k ) ( − 20 × m ≤ k ≤ 20 × m ) 。实际解题的时候,会用一个二维数组f 来存放f(j, k)的值。而且,由于题目中辩控差的值k 可以为负数,而程序中数租下标不能为负数,所以,在程序中不妨将辩控差的值都加上 400 400 ,以免下标为负数导致出错,即题目描述中,如果辩控差为 0 0 ,则在程序中辩控差为 400 400
POJ1015代码

#include
#include
#include
#include
#include
using namespace std;
const int maxn = 205;
const int maxm = 25;
int sum[maxn];
int sub[maxn];
int dp[maxn][805];
vector<int> path[maxm][805];
int main()
{
    int cnt=1;
    int n,m;
    while(scanf("%d%d",&n,&m)!=EOF)
    {
        if(n==0&&m==0) break;
        for(int i=1;i<=n;i++)
        {
            int x,y;
            scanf("%d%d",&x,&y);
            sum[i]=x+y;
            sub[i]=x-y;
        }
        memset(dp,-1,sizeof(dp));
        for(int i=0;i<=m;i++)
        {
            for(int j=0;j<805;j++)
            {
                path[i][j].clear();
            }
        }
        int BASE = 20*m;
        dp[0][BASE]=0;
        for(int i=1;i<=n;i++)
        {
            for(int j=m;j>=1;j--)
            {
                for(int k=sub[i];k<=BASE*2;k++)
                {
                    if(dp[j-1][k-sub[i]]==-1) continue;
                    if(k<0||k-sub[i]>2*BASE) continue;
                    if(dp[j][k]1][k-sub[i]]+sum[i])
                    {
                        dp[j][k]=dp[j-1][k-sub[i]]+sum[i];
                        path[j][k]=path[j-1][k-sub[i]];
                        path[j][k].push_back(i);
                    }
                }
            }
        }
        int ans=0;
        while(dp[m][BASE-ans]==-1&&dp[m][BASE+ans]==-1) ans++;

        if(dp[m][BASE-ans]>dp[m][BASE+ans]) ans=BASE-ans;
        else ans=BASE+ans;

        int ans1=(dp[m][ans]+ans-BASE)/2;
        int ans2=(dp[m][ans]-ans+BASE)/2;
        printf("Jury #%d\n", cnt++);
        printf("Best jury has value %d for prosecution and value %d for defence:\n", ans1, ans2);
        for(int i=0;i<m;i++)    printf(" %d",path[m][ans][i]);
        printf("\n\n");
    }
    return 0;
}

你可能感兴趣的:(DP)