• dp的练习题;


    老刘在网上放了dp的比赛,全™是dp;

    菜如狗的lsj发现自己什么都不会,于是开了这篇随笔来巩固(重学)一下dp;

    B - 最长等差数列

    N个不同的正整数,找出由这些数组成的最长的等差数列。

     
     
    例如:1 3 5 6 8 9 10 12 13 14
    等差子数列包括(仅包括两项的不列举)
    1 3 5
    1 5 9 13
    3 6 9 12
    3 8 13
    5 9 13
    6 8 10 12 14
     
    其中6 8 10 12 14最长,长度为5。
     
     

    Input第1行:N,N为正整数的数量(3 <= N <= 10000)。 
    第2 - N+1行:N个正整数。(2<= Aii <= 10^9)Output最长等差数列的长度。Sample Input

    10
    1
    3
    5
    6
    8
    9
    10
    12
    13
    14

    Sample Output

    5
    先用sort拍一下序;dp【i】【u】表示结尾是a[i],倒数第二个是a[u]的等差数列的长度;那么我们就可以列出方程:
    if(a[i]-a[u]==a[u]-a[k])dp[i][u]=dp[u][k]+1;
    看似好像是n^3, 但仔细分析一下的话会发现由于u是递增的,所以a[i]-a[u]是递减的;所以a[u]-a[k]也是递减的,所以k是递增的;
    有了这个想法,就不难写出n^2的算法了;
     1 #include<iostream>
     2 #include<cstring>
     3 #include<cstdio>
     4 #include<cstring>
     5 #include<algorithm>
     6 #define xx getchar()
     7 #define tmp 23
     8 using namespace std;
     9 int a[10001];
    10 short dp[10001][10001];
    11 int n;
    12 short ans=0;
    13 int read()
    14 {
    15     int x=0,f=1;
    16     char ch;
    17     ch=xx;
    18     while(ch<'0'||ch>'9'){if(ch=='-')f=-1;ch=xx;}
    19     while(ch>='0'&&ch<='9'){x=(x<<1)+(x<<3)+ch-'0';ch=xx;}
    20     return x*f;
    21 }
    22 int main()
    23 {
    24     n=read();
    25     for(int i=1;i<=n;i++)
    26         a[i]=read(),dp[i][0]=1;
    27     sort(a+1,a+n+1);
    28     for(int i=2;i<=n;i++)
    29     {
    30         int k=0;
    31         for(int u=1;u<i;u++)
    32         {
    33             int x=a[u]-(a[i]-a[u]);
    34             int f=0;
    35             while(k<=u)
    36             {
    37                 if(a[k]==x){dp[i][u]=dp[u][k]+1;f=1;}
    38                 if(a[k]>x)break;
    39                 k++;
    40             }
    41             if(f==0)dp[i][u]=2;
    42             ans=max(ans,dp[i][u]);
    43         }
    44     }
    45     cout<<ans<<endl;
    46     return 0;
    47 }
    View Code

    C - 最大M子段和

    N个整数组成的序列a1,a2,a3,…,an,将这N个数划分为 互不相交的M个子段,并且这M个子段的和是最大的。 如果M >= N个数中正数的个数,那么输出所有正数的和。
    例如: -2  11  -4  13  -5  6  -2,分为2段,11 -4 13一段,6 一段,和为26。

    Input第1行:2个数N和M,中间用空格分隔。N为整数的个数,M为划分为多少段。(2 <= N , M <= 5000)
    第2 - N+1行:N个整数 (-10^9 <= aii <= 10^9)Output输出这个最大和Sample Input

    7 2
    -2
    11
    -4
    13
    -5
    6
    -2

    Sample Output

    26

    设dp【i】【u】将前u个数分成i段的最大值;
    刚开始想的是这样的;
    do[i][u]=max(dp[i-1][u],dp[i][u-1]);
    dp[i][u]=max(dp[i-1][u-1]+a[u],dp[i][u-1]+a[u],dp[i][u]);
    但是这样是有问题的;
    dp[i-1][u-1]+a[u]并不错但dp【i】【u-1】+a[u]就不一定了;
    因为第i个不一定连续到了u-1;
    那么我们就想一下如何保证dp【i】【u-1】+a【u】是可行的;
    经过深思熟虑的思考(查题解);
    我们知道了这样一个方式:保证a[u-1]在i中;
    再开一个f[i][u]记录a[u]必选的最大值;
    那么就有f[i][u]=max(f[i-1][u-1]+a[u],f[i][u-1]+a[u]);
    那么dp[i][u]就可以改成dp[i][u]=max(dp[i-1][u-1],f[i][u]);
    这样就可以A掉此题;
    还值得说的就是对此题的空间优化了,因为f,dp数组都只和上一个f,dp有关,所以我们可以吧i给省略掉;
     1 #include<iostream>
     2 #include<cstring>
     3 #include<cstdio>
     4 #include<cstring>
     5 #include<algorithm>
     6 #define xx getchar()
     7 #define tmp 23
     8 #define intt long long
     9 using namespace std;
    10 int m;
    11 int n;
    12 short ans=0;
    13 int read()
    14 {
    15     int x=0,f=1;
    16     char ch;
    17     ch=xx;
    18     while(ch<'0'||ch>'9'){if(ch=='-')f=-1;ch=xx;}
    19     while(ch>='0'&&ch<='9'){x=(x<<1)+(x<<3)+ch-'0';ch=xx;}
    20     return x*f;
    21 }
    22 intt a[10020];
    23 intt dp[3][5001];
    24 intt f[3][5001];
    25 int b[10000];
    26 intt sum[10000];
    27 intt ansx=0;
    28 int numz=0;
    29 int main()
    30 {
    31     int k=0;
    32     n=read();m=read();
    33     for(int i=1;i<=n;i++)
    34     {a[i]=read();b[i]=k;if(a[i]>0)numz++,ansx+=a[i],k=i;sum[i]=sum[i-1]+a[i];}
    35     if(m>=numz){cout<<ansx<<endl;return 0;}
    36     for(int i=1;i<=m;i++)
    37     {
    38         for(int u=1;u<=n;u++)
    39         {
    40             f[2][u]=max(dp[1][u-1]+a[u],f[2][u-1]+a[u]);
    41             //if(a[u]>0)
    42             //dp[2][u]=max(dp[1][u-1]+a[u],dp[2][b[u]]+sum[u]-sum[b[u]]);
    43             dp[2][u]=max(f[2][u],dp[2][u-1]);
    44         }
    45         for(int u=1;u<=n;u++)
    46         dp[1][u]=dp[2][u],f[1][u]=f[2][u];
    47     }
    48     cout<<dp[1][n]<<endl;
    49     return 0;
    50 }
    View Code

    D - 树的距离之和

    给定一棵无根树,假设它有n个节点,节点编号从1到n, 求任意两点之间的距离(最短路径)之和。Input第一行包含一个正整数n (n <= 100000),表示节点个数。 
    后面(n - 1)行,每行两个整数表示树的边。Output每行一个整数,第i(i = 1,2,...n)行表示所有节点到第i个点的距离之和。Sample Input

    4
    1 2
    3 2
    4 2

    Sample Output

    5
    3
    5
    5

    我们分析一下,对任意点的距离其实=e【i】.v*经过e【i】的次数;所以我们可以以边为单位来解决这个问题;

    那么当根节点顺着边转移的时候,记次边原本被经过了x次,那么现在就变成了n-x次;而别的边是不变的;

    所以我们只需要先去一个点来建一颗书,然后再递推出来答案就好了;

    记得用longlong;

     1 #include<iostream>
     2 #include<cstring>
     3 #define intt long long 
     4 using namespace std;
     5 int read()
     6 {
     7     int x=0,f=1;
     8     char ch;
     9     ch=getchar();
    10     while(ch<'0'||ch>'9'){if(ch=='-')f=-1;ch=getchar();}
    11     while(ch>='0'&&ch<='9'){x=(x<<1)+(x<<3)+ch-'0';ch=getchar();}
    12     return x*f;
    13 }
    14 struct {int v,next,y,xxx;}e[1000000];
    15 int lin[1000000];
    16 int len=0;
    17 intt n;
    18 intt ans=0;
    19 intt ansx[1000000]={};
    20 int init(int x,int y,int v)
    21 {e[++len].y=y;e[len].v=v;e[len].next=lin[x];lin[x]=len;e[len].xxx=0;}
    22 struct sss{intt vis,v;};
    23 int    dfs(int x,int f,int vv)
    24 {    
    25     int viss=0;
    26     for(int i=lin[x];i;i=e[i].next)
    27     {
    28         int y=e[i].y;
    29         if(y==f)continue;
    30         ans+=vv+e[i].v;
    31         e[i].xxx=dfs(y,x,vv+e[i].v);
    32         viss+=e[i].xxx;
    33     }
    34     return viss+1;
    35 }
    36 void dfsx(int x,int f)
    37 {
    38     for(int i=lin[x];i;i=e[i].next)
    39     {
    40         int y=e[i].y;
    41         if(y==f)continue;
    42         int w=n-e[i].xxx;
    43         ansx[y]=ansx[x]+(w-e[i].xxx)*e[i].v;
    44         dfsx(y,x);
    45     }
    46     return ;
    47 }
    48 void initx()
    49 {
    50     n=read();
    51 //cout<<n<<endl;
    52     for(int i=1;i<=n-1;i++)
    53     {
    54         int x=read();
    55         int y=read();
    56         init(x,y,1);init(y,x,1);
    57     }
    58     dfs(1,0,0);
    59     ansx[1]=ans;
    60     dfsx(1,0);
    61 }
    62 int main()
    63 {
    64     initx();
    65     for(int i=1;i<=n;i++)
    66     {
    67         cout<<ansx[i]<<endl;
    68     }
    69     return 0;
    70 }
    View Code
    E - 回文串划分
     
    有一个字符串S,求S最少可以被划分为多少个回文串。
    例如:abbaabaa,有多种划分方式。
     
    a|bb|aabaa - 3 个回文串
    a|bb|a|aba|a - 5 个回文串
    a|b|b|a|a|b|a|a - 8 个回文串
     
    其中第1种划分方式的划分数量最少。
    Input输入字符串S(S的长度<= 5000)。Output输出最少的划分数量。Sample Input
    abbaabaa
    Sample Output3;
     
    很浅显的我们可以这样列出dp方程:if(s[j]-->s[u]是一个回文串)dp【u】=min(【j-1】+1,dp[u]);如果直接这样暴力,时间是肯定会超
    的,其原因在于我们无法在o(1)的时间来算串是否回文,判断一个串是否回文的复杂度是o(n);考虑一下回文串对称的特性,我们可
    以发现,如果我们枚举回文串的中点的话,我们可以o(1)扩展出串来,有了这个想法,此题就可以过了;
     1 #include<iostream>
     2 #include<cstring>
     3 #include<cstdio>
     4 using namespace std;
     5 char s[5001];
     6 int dp[10000];
     7 int main()
     8 {
     9     memset(dp,10,sizeof(dp));
    10     dp[0]=1;
    11     scanf("%s",s);
    12     int ww=strlen(s);
    13     for(int i=1;i<=ww-1;i++)
    14     {
    15         for(int j=i,k=i;j>=0,k<ww;j--,k++)
    16         {
    17             if(s[j]==s[k]){dp[k]=min(dp[j-1]+1,dp[k]);}
    18             else break;
    19         }
    20         for(int j=i-1,k=i;j>=0,k<=ww;j--,k++)
    21         {
    22             if(s[j]==s[k]){dp[k]=min(dp[j-1]+1,dp[k]);}
    23             else break;
    24         }
    25     }
    26     cout<<dp[ww-1]<<endl;
    27     return 0;
    28 }
    View Code

    J - 最长递增子序列的数量

     数组A包含N个整数(可能包含相同的值)。设S为A的子序列且S中的元素是递增的,则S为A的递增子序列。如果S的长度是所有递增子序列中最长的,则称S为A的最长递增子序列(LIS)。A的LIS可能有很多个。例如A为:{1 3 2 0 4},1 3 4,1 2 4均为A的LIS。给出数组A,求A的LIS有多少个。由于数量很大,输出Mod 1000000007的结果即可。相同的数字在不同的位置,算作不同的,例如 {1 1 2} 答案为2。 
    Input第1行:1个数N,表示数组的长度。(1 <= N <= 50000) 
    第2 - N + 1行:每行1个数Aii,表示数组的元素(0 <= Aii <= 10^9)Output输出最长递增子序列的数量Mod 1000000007。Sample Input

    5
    1
    3
    2
    0
    4

    Sample Output

    2

    我们观察一下数据量,再观察一下时限(1s)就可以知道,此题用的应该是nlogn的算法,lis告诉我们,我们应该先二分求一下lis;
    求个lis之后,我们可以记录以每一个点为结尾的最长的lis;
    我们记录以一个点为结尾并且长度最大的递增串的个数,设这个长度为len,那么很明显,这个答案ans=sum(ans【在他之前&&len-1&&还要比ans对应的数要小;】)
    乍一看好像需要二维的线段树(树状数组)来解决,但空间上是不允许的;
    但仔细思考一下的话,我们可以用一些奇技淫巧来解决空间这个问题;
    我们可以开一个一维的数组,然后将空间进行划分;
    但到底该怎么做呢,
    我们先来分析一下lis的特点:
    对于lis来说,又一组数x∈() 满足f[x]=same; 那么我们可以知道这些数是递减的;

    那么我们随便想一想就知道可以用二分来做了;那么复杂度是n*logn*logn(树状数组);

    在码题的过程中,我深入思考了一下发现其实换一种方法我们可以在(n+n)*logn的时间复杂度内算出答案,
    比方说:len=3的集合和len=4的集合中的数都是递减的;
    那么我们在len=3的集合中找于len=4中的某个数相等的时候这个下标肯定是递增的;
    这样一来,我们就可以在(n+n)logn的时间复杂度内解决问题了;
    总的时间复杂度也就是(n*logn+2*n*logn======3*n*logn)左右了;
      1 #include<iostream>
      2 #include<cstring>
      3 #include<cstdio>
      4 #include<algorithm>
      5 #define MAXX 500005
      6 using namespace std;
      7 int kk[500001];
      8 int order=1000000000+50000;
      9 long long c[1000000];
     10 int lowbit(int x){return x&(-x);}
     11 void add(int x,long long  v)
     12 {
     13     //if(x==0)return ;
     14     for(int i=x;i<=MAXX;i+=lowbit(i))
     15     {
     16         c[i]+=v;
     17     }
     18 }
     19 long long sum(int x)
     20 {
     21     if(x==0)return 0;
     22     long long sum=0;
     23     for(int i=x;i>0;i-=lowbit(i))
     24     {
     25         sum+=c[i];        
     26     }
     27     return sum;
     28 }
     29 void build(int x,long long v)
     30 {add(x,v);}
     31 long long getsum(int z,int y)
     32 {return (sum(y)-sum(z-1))%1000000007;}
     33 int lis[500006];
     34 int mt[500006];
     35 int my[500006];
     36 int top[500006];
     37 int ansx[500006];
     38 struct
     39 {
     40     int z,y;
     41 }mark[500006];
     42 int a[500006];
     43 int n;
     44 int read()
     45 {
     46     char ch;
     47     int x=0,f=1;
     48     ch=getchar();
     49     while(ch<'0'||ch>'9'){if(ch=='-')f=-1;ch=getchar();}
     50     while(ch>='0'&&ch<='9'){x=(x<<1)+(x<<3)+ch-'0';ch=getchar();}
     51     return x*f;
     52 }
     53 int new_lis(int k)
     54 {
     55     int v=a[k];
     56     int z=0,r=k;
     57     while(z+1<r)
     58     {
     59         int mid=(z+r)/2;
     60         if(lis[mid]<v)z=mid;
     61         else r=mid;
     62     }
     63     if(lis[z]<v)return z;
     64     else return r;
     65 }
     66 long long  find(int k,int v)
     67 {
     68     while(ansx[kk[k]]>=v)kk[k]++;
     69     return getsum(kk[k],top[k]);
     70 }
     71 struct s{int v,id;}b[50001];
     72 bool ma(s a,s b)
     73 {return a.v<b.v;}
     74 void add(int k,int v,int ans)
     75 {ansx[++top[k]]=ans;build(top[k],v);}
     76 int tot=0;
     77 int main()
     78 {
     79     freopen("a.in","r",stdin);
     80     n=read();
     81     for(int i=1;i<=n;i++)b[i].v=read(),b[i].id=i;
     82     sort(b+1,b+1+n,ma);
     83     a[b[1].id]=++tot;
     84     for(int i=2;i<=n;i++)
     85     {
     86         if(b[i].v!=b[i-1].v)a[b[i].id]=++tot;
     87         else a[b[i].id]=tot;
     88     }
     89     for(int i=1;i<=n;i++){lis[i]=order,ansx[i]=order;}
     90     lis[1]=a[1];
     91     lis[0]=0;
     92     mt[1]=1;
     93     int ans=0;
     94     for(int i=2;i<=n;i++)
     95     {
     96         int k=new_lis(i)+1;
     97         mt[i]=k;
     98         lis[k]=min(lis[k],a[i]);
     99     }
    100     for(int i=1;i<=n;i++)
    101     {my[mt[i]]++;}
    102     mark[0].y=0;
    103     for(int i=1;i<=n;i++)
    104     {
    105         if(my[i]==0)break;
    106         mark[i].z=mark[i-1].y+1;
    107         mark[i].y=mark[i].z+my[i]-1;
    108         top[i]=mark[i].z-1;
    109         kk[i]=top[i]+1;
    110     }
    111     int maxx=0;
    112     for(int i=1;i<=n;i++)
    113     {
    114         int k=mt[i];
    115         maxx=max(maxx,k);
    116         if(k==1)add(1,1,a[i]);
    117         else
    118         {
    119             long long v=find(k-1,a[i]);
    120             add(k,v,a[i]);
    121         }
    122     }
    123     cout<<getsum(mark[maxx].z,mark[maxx].y)%1000000007<<endl;
    124     return 0;
    125 }
    View Code


     

  • 相关阅读:
    JS实现 div拖拽 限制在屏幕内
    国际化配置simple_form
    simple_form模板templates erb haml
    git rolify
    rails模板生成bootstrap格式的simple_form的erb文件
    rails生成器生成自定义controller模板
    ubuntu 终端常用命令(转)
    Ruby for Rails笔记
    Java基础
    javascript ybmiaov
  • 原文地址:https://www.cnblogs.com/Lazers/p/6973378.html
Copyright © 2020-2023  润新知