9.11~9.16训练----重要!动规dp

来源:互联网 发布:淘宝买的鞋子味道很大 编辑:程序博客网 时间:2024/06/10 22:27

前言(~废话)

动态规划,指将大问题分成小问题解决,并解决每个小问题将问题答案存储下来,当再次用时直接调用无需重复计算,能大大缩短程序时间.
这周刚步入dp殿(xie)堂(jiao)大门,还不是特别会,大部分没有直接写dp,用的是记忆化搜索,基本与dp相同,只不过dp直接在循环中较为难写但更简洁时间复杂度常数也较小,而记忆化搜索好写,适合我这种蒟蒻,等多练练之后我也开始直接写dp了.
下面是这个礼拜水掉的dp题..

A - Greenhouse Effect

险些吐血的a题…
Emuskald is an avid horticulturist and owns the world’s longest greenhouse — it is effectively infinite in length.

Over the years Emuskald has cultivated n plants in his greenhouse, of m different plant species numbered from 1 to m. His greenhouse is very narrow and can be viewed as an infinite line, with each plant occupying a single point on that line.

Emuskald has discovered that each species thrives at a different temperature, so he wants to arrange m - 1 borders that would divide the greenhouse into m sections numbered from 1 to m from left to right with each section housing a single species. He is free to place the borders, but in the end all of the i-th species plants must reside in i-th section from the left.

Of course, it is not always possible to place the borders in such way, so Emuskald needs to replant some of his plants. He can remove each plant from its position and place it anywhere in the greenhouse (at any real coordinate) with no plant already in it. Since replanting is a lot of stress for the plants, help Emuskald find the minimum number of plants he has to replant to be able to place the borders.
大意是可以移动一些数到任意位置,问最少的移动次数,使得第一个数字的排列非递减,第二个数字其实没什么用.直接入手写了我一个多小时发现dp方程还是错的,后来才发现其实只要找到最长非递减子序列写了一会会就ok了,dp题真是特别要思维啊!!
代码用记忆化搜索写的..

#include<bits/stdc++.h>using namespace std;int n,m,a[5010],ans=0,dp[5010][5010];int f(int x,int y) {     if(dp[x][y]!=-1)return dp[x][y];    if(y==n) {        if(x<=a[y])dp[x][y]=1;        else dp[x][y]=0;        return dp[x][y];    } else if(a[y]<x) {        dp[x][y]=f(x,y+1);    } else if(a[y]==x) {        dp[x][y]=f(x,y+1)+1;    } else {        dp[x][y]=max(f(x,y+1),f(a[y],y+1)+1);    }    return dp[x][y];}int main() {    double p;    int i;    memset(dp,-1,sizeof(dp));    cin>>n>>m;    for(i=1; i<=n; i++)        scanf("%d%lf",&a[i],&p);    for(i=1; i<=n-1; i++) {        int pp=0;        ans=max(ans,f(a[i],i+1)+1);    }    if(n==1) {        cout<<0;        return 0;    }    cout<<n-ans;}

B - Boredom

Alex doesn’t like boredom. That’s why whenever he gets bored, he comes up with games. One long winter evening he came up with a game and decided to play it.

Given a sequence a consisting of n integers. The player can make several steps. In a single step he can choose an element of the sequence (let’s denote it ak) and delete it, at that all elements equal to ak + 1 and ak - 1 also must be deleted from the sequence. That step brings ak points to the player.

Alex is a perfectionist, so he decided to get as many points as possible. Help him.
比较简单,就是一个积分赛制有1~n的数,选了ak,那么值为ak+1与ak-1均不能选(就是相当于是删除),但是总分会加上ak,问你最大能够获得多少分
仍然是记忆化搜索….

#include<bits/stdc++.h>using namespace std;long long int a[100100]= {0},dp[100100],maxn=-9999;long long int f(int n) {    if(dp[n]!=-1)return dp[n];    if(n==maxn) {        dp[maxn]=a[maxn]*maxn;        return dp[maxn];    } else if(a[n]==0) {        int i;        for(i=n; i<=maxn; i++)            if(a[i]!=0)break;        dp[n]=f(i);        return dp[n];    } else {        if(n<=maxn-2)        dp[n]=max(f(n+1),f(n+2)+a[n]*n);        else if(n<=maxn-1)        dp[n]=max(a[n]*n,f(n+1));    }    return dp[n];}int main() {long long int m,i,cnt,ans=0,p=0,n=0;    bool b[100010]= {0};    memset(dp,-1,sizeof(dp));    cin>>m;    for(i=0; i<=m-1; i++) {        scanf("%lld",&cnt);        if(a[cnt]==0)p++;        a[cnt]++;        maxn=max(maxn,cnt);    }    cout<<f(1);}

C-Flowers

We saw the little game Marmot made for Mole’s lunch. Now it’s Marmot’s dinner time and, as we all know, Marmot eats flowers. At every dinner he eats some red and white flowers. Therefore a dinner can be represented as a sequence of several flowers, some of them white and some of them red.

But, for a dinner to be tasty, there is a rule: Marmot wants to eat white flowers only in groups of size k.

Now Marmot wonders in how many ways he can eat between a and b flowers. As the number of ways could be very large, print it modulo 1000000007 (109 + 7).
类似于小学奥数中走台阶问题,一次可以走一步或k步,求方法数…可以想象已走到终点,现在能往后退k步或1步…然后重复这个步骤,将每次答案记录避免重复算(如一次退k步之后的方法是与k次退1步一样的)
还是记忆化搜索哈

#include<bits/stdc++.h>using namespace std;int mod=1000000007,dp[100010]= {0},k,ret[100010]={0};int sol(int a) {    if(dp[a]!=0)return dp[a];    else {        dp[a]=(sol(a-1)+sol(a-k))%mod;ret[a]=(ret[a-1]+dp[a])%mod;    }    return dp[a];}int main() {    int t,i,j,a,b;    cin>>t>>k;    for(i=1; i<k; i++)        {dp[i]=1;ret[i]=i;        }    dp[k]=2;ret[k]=k+1;    for(i=k;i<=100000;i++)     {sol(i);     }    for(i=1; i<=t; i++) {        int ans=0;        scanf("%d%d",&a,&b);        if(a==1)         ans=ret[b];         else        ans=ret[b]-ret[a-1];        ans%=mod;        printf("%d\n",(ans+mod)%mod);    }    return 0;}

D-Mashmokh and ACM

Mashmokh’s boss, Bimokh, didn’t like Mashmokh. So he fired him. Mashmokh decided to go to university and participate in ACM instead of finding a new job. He wants to become a member of Bamokh’s team. In order to join he was given some programming tasks and one week to solve them. Mashmokh is not a very experienced programmer. Actually he is not a programmer at all. So he wasn’t able to solve them. That’s why he asked you to help him with these tasks. One of these tasks is the following.

A sequence of l integers b1, b2, …, bl (1 ≤ b1 ≤ b2 ≤ … ≤ bl ≤ n) is called good if each number divides (without a remainder) by the next number in the sequence. More formally for all i (1 ≤ i ≤ l - 1).

Given n and k find the number of good sequences of length k. As the answer can be rather large print it modulo 1000000007 (109 + 7).
有点难度,就是给你一个n和k,让你用1 – n这n个数,选出k个组成一个序列,这个序列满足任意后面一个都能够整除前面一个。求这个序列的个数。
不用说也知道蒟蒻我用的是记忆化搜索了把

#include<bits/stdc++.h>using namespace std;int mod=1000000007;int dp[2001][2001]= {0},n,m;int f(int x,int ci) {    if(dp[x][ci]!=-1)return dp[x][ci];    else if(ci==n) {        dp[x][ci]=1;        return dp[x][ci];    } else {        int p=1;        dp[x][ci]=0;        while(x*p<=m) {            dp[x][ci]+=f(x*p,ci+1);            dp[x][ci]%=mod;            p++;        }    }    return dp[x][ci];}int main() {    long long int ans=0;    memset(dp,-1,sizeof(dp));    cin>>m>>n;    for(int i=1; i<=m; i++) {        ans+=f(i,1);        ans%=mod;    }    cout<<ans;}

E-Hard problem

Vasiliy is fond of solving different tasks. Today he found one he wasn’t able to solve himself, so he asks you to help.

Vasiliy is given n strings consisting of lowercase English letters. He wants them to be sorted in lexicographical order (as in the dictionary), but he is not allowed to swap any of them. The only operation he is allowed to do is to reverse any of them (first character becomes last, second becomes one before last and so on).

To reverse the i-th string Vasiliy has to spent ci units of energy. He is interested in the minimum amount of energy he has to spent in order to have strings sorted in lexicographical order.

String A is lexicographically smaller than string B if it is shorter than B (|A| < |B|) and is its prefix, or if none of them is a prefix of the other and at the first position where they differ character in A is smaller than the character in B.

For the purpose of this problem, two equal strings nearby do not break the condition of sequence being sorted lexicographically.
确实有点hard.给出n个字符串与翻转它所需的代价(注:不可改变字符串的顺序),问要将所有字符串按字典序排列所需的做小代价(如果不存在,输出-1).
方法先读入每个字符串,并记录它反转的字符串,之后从第一个开始dp,每个与前面的比较,每次四种情况讨论就行..
这次终于尝试直接在循环里写dp啦!!真的很简洁(假的).

#include<bits/stdc++.h>#define ll long long#define N 100010using namespace std;ll inf=1e17,dp[N][2];int a[N];int main(){int m,i;ll ans; for(i=2;i<=N;i++) {dp[i][0]=inf;dp[i][1]=inf;//注意此处memset会溢出,特别坑 } string s[N],f[N]; cin>>m; for(i=1;i<=m;i++) scanf("%d",&a[i]); for(i=1;i<=m;i++)  {cin>>s[i];   f[i]=s[i];   reverse(f[i].begin(),f[i].end());  }  dp[1][0]=0;  dp[1][1]=a[1];  for(i=2;i<=m;i++)   {    if(f[i]>=f[i-1])       dp[i][1]=dp[i-1][1]+a[i];    if(s[i]>=f[i-1])       dp[i][0]=dp[i-1][1];      if(s[i]>=s[i-1])       dp[i][0]=min(dp[i-1][0],dp[i][0]);    if(f[i]>=s[i-1])       dp[i][1]=min(dp[i-1][0]+a[i],dp[i][1]);          }   ans=min(dp[m][0],dp[m][1]);  if(ans==inf)    cout<<-1;    else     cout<<ans;}

F-George and Job

The new ITone 6 has been released recently and George got really keen to buy it. Unfortunately, he didn’t have enough money, so George was going to work as a programmer. Now he faced the following problem at the work.

Given a sequence of n integers p1, p2, …, pn. You are to choose k pairs of integers:

[l1, r1], [l2, r2], …, [lk, rk] (1 ≤ l1 ≤ r1 < l2 ≤ r2 < … < lk ≤ rk ≤ n; ri - li + 1 = m), 
in such a way that the value of sum is maximal possible. Help George to cope with the task.
给出一个有n项的数列Pi,求k个长度为m的完全互不重叠(边界也不能重叠)的闭区间,使每个区间内的值和最大。
又回归到用记忆化搜索了,其实这道直接写dp超级快…

#include<bits/stdc++.h>#define N 5010using namespace std;long long int dp[N][N]={0},n,m,k,ans,a[N],b[N]={0};long long int f(long long int x,long long int y){if(y==0)return 0; else if(dp[x][y]!=-1)return dp[x][y]; else if(x==n-m)    {return b[n-m];} else if(x>n-m)return 0;     else     dp[x][y]=max(f(x+m,y-1)+b[x],f(x+1,y));  //cout<<x<<" "<<dp[x][y]<<" "<<y<<endl;  return dp[x][y];}int main() {    int i;    memset(dp,-1,sizeof(dp));    //freopen("in.txt","r",stdin);    cin>>n>>m>>k;    for(i=0;i<n;i++)     cin>>a[i];    for(i=0;i<=n-m;i++)     {if(i==0)       {for(int j=0;j<=m-1;j++)         b[0]+=a[j];       }      else       b[i]=b[i-1]+a[i+m-1]-a[i-1];        //cout<<i<<" "<<b[i]<<endl;     }     cout<<f(0,k);}

H-The least round way

There is a square matrix n × n, consisting of non-negative integer numbers. You should find such a way on it that

starts in the upper left cell of the matrix;
each following cell is to the right or down from the current cell;
the way ends in the bottom right cell.
Moreover, if we multiply together all the numbers along the way, the result should be the least “round”. In other words, it should end in the least possible number of zeros.
算难题了,有n*n的矩阵,从左上角走到右下角(只能向下或向右),乘积末尾最少有几个零。输出零的个数和路径。开始我只讨论了每个数含5因数的个数的情况,没想到只能水到20来个点(其实已经是数据太弱了,不然更早就gg),接着我补上了含2因数的个数情况后和5的比,结果又gg了,发现数据范围里还有个坑爹的0……补上终于过了..
代码一如既往地记忆化搜索

#include<bits/stdc++.h>#define ll long long#define N 1001using namespace std;int dp[N][N];int m[N][N],m2[N][N];char road[N][N],road1[N][N];bool flag=0;int find_road(int a,int b) {    if(dp[a][b]!=-1)return dp[a][b];    else  {        if(a==1) {            dp[a][b]=find_road(a,b-1)+m[a][b];            road[a][b]='R';        } else if(b==1) {            dp[a][b]=find_road(a-1,b)+m[a][b];            road[a][b]='D';        }        else {            dp[a][b]=min(find_road(a-1,b),find_road(a,b-1))+m[a][b];            if(dp[a][b]==find_road(a-1,b)+m[a][b])                road[a][b]='D';            else   road[a][b]='R';        }    }    return dp[a][b];}int find_otroad(int a,int b) {    if(dp[a][b]!=-1)return dp[a][b];    else  {        if(a==1) {            dp[a][b]=find_otroad(a,b-1)+m2[a][b];            road1[a][b]='R';        } else if(b==1) {            dp[a][b]=find_otroad(a-1,b)+m2[a][b];            road1[a][b]='D';        }        else {            dp[a][b]=min(find_otroad(a-1,b),find_otroad(a,b-1))+m2[a][b];            if(dp[a][b]==find_otroad(a-1,b)+m2[a][b])                road1[a][b]='D';            else   road1[a][b]='R';        }    }    return dp[a][b];}int cnt_0(int a) {    int cnt=0;    while(a%5==0&&a>=5) {        a/=5;        cnt++;    }//cout<<cnt<<endl;    return cnt;}int cnt_2(int a){    int cnt=0;    while(a%2==0&&a>=2)    {a/=2;     cnt++;    }    return cnt;}int main() {    int n,i,j,x,ret1,ret2,ret,aa,bb;    memset(dp,-1,sizeof(dp));    stack<char> ans;//freopen("in.txt","r",stdin);    cin>>n;    for(i=1; i<=n; i++)        for(j=1; j<=n; j++) {            int p;            scanf("%d",&p);            if(p==0)            {flag=1;aa=i;bb=j;            }            m[i][j]=cnt_0(p);            m2[i][j]=cnt_2(p);        }    dp[1][1]=m[1][1];    find_road(n,n);    ret1=dp[n][n];    memset(dp,-1,sizeof(dp));    dp[1][1]=m2[1][1];    find_otroad(n,n);    ret2=dp[n][n];    ret=min(ret1,ret2);    //cout<<ret;    if(ret>1&&flag==1)    {cout<<1<<endl;      for(i=1;i<=aa-1;i++)       cout<<"D";      for(i=1;i<=bb-1;i++)       cout<<"R";      for(i=aa;i<=n-1;i++)       cout<<"D";      for(i=bb;i<=n-1;i++)       cout<<"R";          return 0;    }    else if(ret==ret1)    for(i=n,j=n;;)    {if(i==1&&j==1)break;     ans.push(road[i][j]);     if(road[i][j]=='R')j--;     else i--;    }    else if(ret==ret2)    for(i=n,j=n;;)    {if(i==1&&j==1)break;     ans.push(road1[i][j]);     if(road1[i][j]=='R')j--;     else i--;    }    cout<<ret<<endl;    for(i=1;i<=2*(n-1);i++)    {printf("%c",ans.top());     ans.pop();    }}

总结

dp很重要,首先是写法,我得多试试直接写尽量少用记忆化搜索,更重要的是思维,吴老师说dp思维真的弄透彻了,啥子难题都不在话下.

原创粉丝点击