dp的练习题;
老刘在网上放了dp的比赛,全™是dp;
菜如狗的lsj发现自己什么都不会,于是开了这篇随笔来巩固(重学)一下dp;
B - 最长等差数列
N个不同的正整数,找出由这些数组成的最长的等差数列。
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
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 }
C - 最大M子段和
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 }
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 }
E - 回文串划分
abbaabaaSample 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 }
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 }