uva 10891 Game of Sum
题目大意:两个人轮流从数组里面取数,可以在头或者尾选择连续的任意多个,直到取完。最后统计两个人取走的数之和,两个人都尽量让自己的得分高,求A的得分减去B的得分。
分析:这题的关键是只能在头尾取,把数组看成一个序列(i~j),那么取走k个数后仍是一个连续的序列(i+k,j)或(i,j-k)。
我们用dp[i][j]表示碰到(i,j)这个序列能得到的最高分数。
那么我们考虑如何转移,这里有点博弈的思想,我们要现在这个状态最高即要求对方碰到的状态(即自己取完后的状态)的得分越少越好。
那么状态转移方程就是dp[i][j]=sum[i][j]-min{dp[i+1][j]....dp[j][j],dp[i][j-1]....dp[i][i],0}。

1 /*dp[i][j]=sum[i][j]-min{dp[i+1][j]....dp[j][j],dp[i][j-1]....dp[i][i],0} 2 * 3 */ 4 #include <iostream> 5 #include <cstdio> 6 #include <cstring> 7 #define maxlen 110 8 using namespace std; 9 int dp[maxlen][maxlen]; 10 int sum[maxlen];//the sum of 0~i 11 bool used[maxlen][maxlen]; 12 int min(int a,int b) 13 { 14 return a<b?a:b; 15 } 16 int dfs(int i,int j)//dp search 17 { 18 if(used[i][j]) 19 return dp[i][j]; 20 used[i][j]=true; 21 int ans=0; 22 for(int k=i+1;k<=j;++k) 23 ans=min(ans,dfs(k,j)); 24 for(int k=j-1;k>=i;--k) 25 ans=min(ans,dfs(i,k)); 26 return dp[i][j]=sum[j]-sum[i-1]-ans; 27 } 28 int main () 29 { 30 int n; 31 while(scanf("%d",&n)!=EOF) 32 { 33 if(n==0) 34 break; 35 sum[0]=0; 36 for(int i=1;i<=n;++i) 37 { 38 int x; 39 scanf("%d",&x); 40 sum[i]=sum[i-1]+x; 41 } 42 memset(used,false,sizeof(used)); 43 int ans=2*dfs(1,n)-sum[n]; 44 printf("%d ",ans); 45 } 46 }
uva 11584 Partitioning by Palindromes
题目大意:给你一个字符串,把它划分为回文串,问最小的回文串个数
分析:dp[i]表示字符串(1,i)的最小回文串个数
状态转移就是枚举子集,求一个最小值
方程为:dp[i]=min{dp[j-1]+1,dp},j<=i&&字符串(j,i)为回文串

1 #include <iostream> 2 #include <cstdio> 3 #include <cstring> 4 #include <algorithm> 5 #define maxlen 1010 6 #define INF 0x3fffff 7 using namespace std; 8 int dp[maxlen]; 9 char str[maxlen]; 10 int min(int a,int b) 11 { 12 return a<b?a:b; 13 } 14 bool judge(int i,int j) 15 { 16 while(i<j) 17 { 18 if(str[i]!=str[j]) 19 return false; 20 i++; 21 j--; 22 } 23 return true; 24 } 25 int main() 26 { 27 int t; 28 scanf("%d",&t); 29 gets(str); 30 while(t--) 31 { 32 gets(str+1); 33 int len=strlen(str+1); 34 for(int i=1;i<=len;++i) 35 dp[i]=INF; 36 dp[1]=1; 37 for(int i=1;i<=len;++i) 38 { 39 for(int j=1;j<=i;++j) 40 { 41 if(judge(j,i)) 42 dp[i]=min(dp[i],dp[j-1]+1); 43 } 44 } 45 printf("%d ",dp[len]); 46 } 47 }
uva 10405 Longest Common Subsequence
题目大意:最经典的LCS问题,最长公共子序列。
dp[i][j]表示str匹配到i,str2匹配到j时的最大长度。
状态转移方程为:
d[i][j]=dp[i-1][j-1]+1 (str[i]==str2[j])
dp[i][j]=max(dp[i-1][j],dp[i][j-1])(str[i]!=str2[j])

1 #include <iostream> 2 #include <cstdio> 3 #include <cstring> 4 #define maxlen 1010 5 using namespace std; 6 int dp[maxlen][maxlen]; 7 char str[maxlen]; 8 char str2[maxlen]; 9 int main () 10 { 11 while(gets(str)) 12 { 13 gets(str2); 14 int len=strlen(str); 15 int len2=strlen(str2); 16 memset(dp,0,sizeof(dp)); 17 for(int i=1;i<=len;++i) 18 { 19 for(int j=1;j<=len2;++j) 20 { 21 if(str[i-1]==str2[j-1]) 22 dp[i][j]=dp[i-1][j-1]+1; 23 else 24 dp[i][j]=max(dp[i-1][j],dp[i][j-1]); 25 } 26 } 27 printf("%d ",dp[len][len2]); 28 } 29 }
uva 674 Coin Change
题目大意:有50,25,10,5,1五种硬币,现在给你n问有多少种兑换方式
分析:
dp[i]表示兑换种数
状态方程就是dp[i]=sum{dp[i-num[j]]}(0<=j<5&&i>=num[j])

1 #include <iostream> 2 #include <cstdio> 3 #include <cstring> 4 #define maxlen 7500 5 using namespace std; 6 int dp[maxlen]; 7 int num[]={50,25,10,5,1}; 8 int main () 9 { 10 int n; 11 dp[0]=1; 12 for(int i=0;i<5;++i) 13 { 14 for(int j=0;j<maxlen;++j) 15 { 16 if(j>=num[i]) 17 dp[j]+=dp[j-num[i]]; 18 } 19 } 20 while(scanf("%d",&n)!=EOF) 21 { 22 printf("%d ",dp[n]); 23 } 24 }
uva 10003 Cutting Sticks
题目大意:给你长度n的木棍,以及m个要切割的位置。问如何安排顺序是的花费最小,花费的定义是每次切割时木棍的长度之和。
分析:本题最主要的是状态的划分,一开始不知道如何下手,本来想从起始两端的位置dp,后来发现没用。
后来想到用切割的位置来dp
dp[i][j]表示在已有的切割要求下完成切割所需要的最小代价,区间i, j表示第i个和第j个切割点,初始为0,结束为最后一个节点,之间为输入切割点数
切割点的位置是一定的,但是切割的时候剩余的木块长度不一定,所以有
dp[i,j] = min{ dp[i, k] + dp[k, j] } + num[j] - num[i] (i<k<j)

1 #include <iostream> 2 #include <cstdio> 3 #include <cstring> 4 #define maxlen 60 5 #define INF 0x3fffff 6 using namespace std; 7 int dp[maxlen][maxlen]; 8 int num[maxlen]; 9 int dfs(int i,int j) 10 { 11 if(i==j-1) 12 return dp[i][j]=0; 13 if(dp[i][j]!=-1) 14 return dp[i][j]; 15 dp[i][j]=INF; 16 for(int k=i+1;k<j;++k) 17 dp[i][j]=min(dp[i][j],dfs(i,k)+dfs(k,j)+num[j]-num[i]); 18 return dp[i][j]; 19 } 20 int main() 21 { 22 int n,m; 23 while(scanf("%d",&n)!=EOF) 24 { 25 if(n==0)break; 26 scanf("%d",&m); 27 for(int i=1;i<=m;++i) 28 scanf("%d",&num[i]); 29 memset(dp,-1,sizeof(dp)); 30 num[0]=0; 31 num[m+1]=n; 32 int ans=dfs(0,m+1); 33 printf("The minimum cutting is %d. ",ans); 34 } 35 }
uva 116 Unidirectional TSP
题目大意:给以n*m矩阵,要求求从0列到m-1列的一条路径使得和最小,在行上面是循环的即最后一行向下走一步就回到第一行
方向有三个:
要求输出字典序最小的路径以及最小值
分析:dp[i][j]表示走到(i,j)位置的最小值,那么它是由三个状态dp[(i-1)%n][j+1],dp[i][j+1],dp[(i+1)%n][j]转化过来的,三者取一下最小值然后记录路径即可。
方程为:dp[i][j]=min{dp[(i-1)%n][j+1],dp[i][j+1],dp[(i+1)%n][j]}+map[i][j]

1 #include <iostream> 2 #include <cstdio> 3 #include <cstring> 4 #define maxlen 210 5 #define INF 0x3ffffff 6 #define min(a,b)(a<b?a:b) 7 using namespace std; 8 int dp[maxlen][maxlen],path[maxlen][maxlen]; 9 int maps[maxlen][maxlen]; 10 int n,m; 11 void init() 12 { 13 memset(dp,0,sizeof(dp)); 14 memset(path,0,sizeof(path)); 15 memset(maps,0,sizeof(maps)); 16 } 17 int main () 18 { 19 while(scanf("%d%d",&n,&m)!=EOF) 20 { 21 init(); 22 for(int i=0;i<n;++i) 23 for(int j=0;j<m;++j) 24 scanf("%d",&maps[i][j]); 25 for(int j=m-1;j>=0;--j) 26 { 27 for(int i=0;i<n;++i) 28 { 29 int m=min(dp[(i-1+n)%n][j+1],min(dp[i][j+1],dp[(i+1)%n][j+1])); 30 dp[i][j]=maps[i][j]+m; 31 path[i][j]=INF; 32 if(m==dp[(i-1+n)%n][j+1]) 33 path[i][j]=min(path[i][j],(i-1+n)%n); 34 if(m==dp[i][j+1])//这里不能写成else if 35 path[i][j]=min(path[i][j],i); 36 if(m==dp[(i+1)%n][j+1]) 37 path[i][j]=min(path[i][j],(i+1)%n); 38 } 39 } 40 int ans=INF; 41 int c; 42 for(int i=0;i<n;++i) 43 { 44 if(ans>dp[i][0]) 45 { 46 c=i; 47 ans=dp[i][0]; 48 } 49 } 50 printf("%d",c+1); 51 c=path[c][0]; 52 for(int j=1;j<m;++j) 53 { 54 printf(" %d",c+1); 55 c=path[c][j]; 56 } 57 printf(" "); 58 printf("%d ",ans); 59 } 60 }
uva 10066 The Twin Towers
题目大意:最长公共子序列
方程见上面的,是差不多的(一样的)。

1 #include <iostream> 2 #include <cstdio> 3 #include <cstring> 4 #define maxlen 110 5 #define INF 0x3ffffff 6 #define max(a,b)(a>b?a:b) 7 using namespace std; 8 int dp[maxlen][maxlen]; 9 int num[maxlen]; 10 int num2[maxlen]; 11 int n,m; 12 int main () 13 { 14 int Case=1; 15 while(scanf("%d%d",&n,&m)!=EOF) 16 { 17 if(n==0&&m==0) 18 break; 19 for(int i=1;i<=n;++i) 20 scanf("%d",&num[i]); 21 for(int i=1;i<=m;++i) 22 scanf("%d",&num2[i]); 23 memset(dp,0,sizeof(dp)); 24 for(int i=1;i<=n;++i) 25 { 26 for(int j=1;j<=m;++j) 27 { 28 if(num[i]==num2[j]) 29 dp[i][j]=dp[i-1][j-1]+1; 30 else 31 dp[i][j]=max(dp[i-1][j],dp[i][j-1]); 32 } 33 } 34 printf("Twin Towers #%d ",Case++); 35 printf("Number of Tiles : %d ",dp[n][m]); 36 } 37 }
uva 357 Let Me Count The Ways
跟上面的钱币兑换是一样的,注意要用longlong

1 #include <iostream> 2 #include <cstdio> 3 #include <cstring> 4 #define maxlen 30010 5 using namespace std; 6 long long dp[maxlen]; 7 int num[]={50,25,10,5,1}; 8 int main () 9 { 10 int n; 11 dp[0]=1; 12 for(int i=0;i<5;++i) 13 { 14 for(int j=0;j<maxlen;++j) 15 { 16 if(j>=num[i]) 17 dp[j]+=dp[j-num[i]]; 18 } 19 } 20 while(scanf("%d",&n)!=EOF) 21 { 22 if(dp[n]==1) 23 printf("There is only 1 way to produce %d cents change. ",n); 24 else 25 printf("There are %lld ways to produce %d cents change. ",dp[n],n); 26 } 27 }
uva 562 Dividing coins
题目大意:把一些钱num[]分给两个人,要求尽量使两个人得到的钱只差最小,输出最小的差值、
分析:我们这么考虑这个问题,num[i]只能分给一个人,0表示分给第一个人,1表示分给第二个人,那么可以从0-1背包方向考虑。
假设总钱数为sum且A分得的钱不超过B,dp[i]表示A是否可以分得一些硬币使得其总钱数为i,dp[i]为1表示可以,0表示不可以。
那么就是0-1背包问题的变形了
dp[0]=1。然后分别对每个硬币枚举,判断dp是否为1即可。最后选择离Sum/2最近且dp[i]==1的i即可。

1 #include <iostream> 2 #include <cstdio> 3 #include <cstring> 4 #define maxlen 50010 5 using namespace std; 6 bool dp[maxlen]; 7 int num[110]; 8 int n; 9 int main() 10 { 11 int t; 12 scanf("%d",&t); 13 while(t--) 14 { 15 scanf("%d",&n); 16 int sum=0; 17 for(int i=0;i<n;++i) 18 { 19 scanf("%d",&num[i]); 20 sum+=num[i]; 21 } 22 memset(dp,0,sizeof(dp)); 23 dp[0]=true; 24 for(int i=0;i<n;++i) 25 { 26 for(int j=sum;j>=num[i];--j) 27 { 28 if(!dp[j]) 29 dp[j]=dp[j-num[i]]; 30 } 31 } 32 for(int i=sum/2;i>=0;--i) 33 { 34 if(dp[i]) 35 { 36 printf("%d ",sum-i-i); 37 break; 38 } 39 } 40 } 41 }
uva 10130 SuperSale
题目大意:每种物品有价值与重量,现在有n个人,给出每个人能承受的最大重量,问这些人最多能够拿到的物品最大值是多少。
分析:多人的0-1背包,只是对每个人用0-1背包求最大值然后加起来就可以了。
使用滚动数组可以把二维的优化到一维。dp[i]表示容量i能装下的最大价值
状态转移方程维:
dp[i]=max{dp[i],dp[i-w]+v} if (i>=w)
dp[i]=dp[i] (i<w)初始化为0

1 #include <iostream> 2 #include <cstdio> 3 #include <cstring> 4 #define maxlen 1010 5 using namespace std; 6 int w[maxlen],v[maxlen],dp[maxlen]; 7 int n,g; 8 int main () 9 { 10 int t; 11 scanf("%d",&t); 12 while(t--) 13 { 14 scanf("%d",&n); 15 memset(dp,0,sizeof(dp)); 16 for(int i=1;i<=n;++i) 17 { 18 scanf("%d%d",&v[i],&w[i]); 19 for(int j=30;j>=0;--j) 20 { 21 if(j>=w[i]) 22 dp[j]=max(dp[j],dp[j-w[i]]+v[i]); 23 } 24 } 25 scanf("%d",&g); 26 int ans=0; 27 int x; 28 for(int i=0;i<g;++i) 29 { 30 scanf("%d",&x); 31 ans+=dp[x]; 32 } 33 printf("%d ",ans); 34 } 35 }
uva 624 CD
分析:0-1背包,可以理解为价值与重量是一样的0-1背包,需要记录路径。

1 #include <iostream> 2 #include <cstdio> 3 #include <cstring> 4 #define maxlen 2010 5 using namespace std; 6 int v[30],dp[maxlen]; 7 bool path[30][maxlen]; 8 int V,n; 9 int main () 10 { 11 while(scanf("%d%d",&V,&n)!=EOF) 12 { 13 for(int i=1;i<=n;++i) 14 scanf("%d",&v[i]); 15 memset(dp,0,sizeof(dp)); 16 memset(path,0,sizeof(path)); 17 for(int i=n;i>=1;--i) 18 { 19 for(int j=V;j>=v[i];--j) 20 { 21 if(j>=v[i]&&dp[j]<dp[j-v[i]]+v[i]) 22 { 23 dp[j]=dp[j-v[i]]+v[i]; 24 path[i][j]=true; 25 } 26 } 27 } 28 for(int i=1,j=V;i<=n;++i) 29 { 30 if(path[i][j]) 31 { 32 printf("%d ",v[i]); 33 j-=v[i]; 34 } 35 } 36 printf("sum:%d ",dp[V]); 37 } 38 }
uva 147 Dollars
分析:跟上面的钱币兑换类似,这里有个优化的就是题目说的是5的倍数,那么每个都除以5,会使程序速度更快。

1 #include <iostream> 2 #include <cstdio> 3 #include <cstring> 4 #define maxlen 20010 5 using namespace std; 6 long long dp[maxlen]; 7 int cc[]={1,2,4,10,20,40,100,200,400,1000,2000}; 8 int n; 9 int main() 10 { 11 int i,j,k; 12 double num; 13 while (scanf("%lf",&num),num>0) 14 { 15 n=int((num+0.005)*100); 16 n/=5; 17 memset(dp,0,sizeof(dp)); 18 dp[0]=1; 19 for (int i=0;i<=10;++i) 20 { 21 for (int j=0;j<=n;++j) 22 { 23 if(j>=cc[i]) 24 dp[j]+=dp[j-cc[i]]; 25 } 26 } 27 printf("%6.2lf%17lld ",num,dp[n]); 28 } 29 }