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

 



 

 

posted @ 2017-06-09 19:09  Fornic  阅读(468)  评论(0编辑  收藏  举报