dp问题:哈哈哈
子集合问题的dp解法:?
subset sum
uva,562
找到离总和1/2最近的硬币总和,dp数组不断逼近,然后减一减,dp的强大啊~~~
1 #include <iostream> 2 #include <cstdio> 3 #include <algorithm> 4 #include <vector> 5 using namespace std; 6 #define maxn 105 7 8 int main() 9 { 10 int a[maxn]; 11 int n,m; 12 scanf("%d",&n); 13 while(n--) 14 { 15 scanf("%d",&m); 16 int cnt=0; 17 for(int i=0;i<m;i++) 18 { 19 scanf("%d",&a[i]); 20 cnt+=a[i]; 21 } 22 23 int aver=cnt/2; 24 vector<int> dp(aver+5,0); 25 26 for(int i=0;i<m;i++) 27 for(int j=aver;j>=a[i];j--) 28 dp[j]=max(dp[j],dp[j-a[i]]+a[i]); 29 printf("%d\n",(cnt-dp[aver])-dp[aver]); 30 } 31 return 0; 32 }
dp[i]=dp[i]||dp[i-a[i]]为1或0 最接近aver的那个为1的就为所求。
1 #include <iostream> 2 #include <cstdio> 3 #include <algorithm> 4 #include <cstring> 5 using namespace std; 6 #define maxn 105 7 8 int main() 9 { 10 int a[maxn]; 11 int n,m; 12 int dp[maxn*500]; 13 scanf("%d",&n); 14 while(n--) 15 { 16 scanf("%d",&m); 17 int cnt=0; 18 for(int i=0;i<m;i++) 19 { 20 scanf("%d",&a[i]); 21 cnt+=a[i]; 22 } 23 memset(dp,0,sizeof(dp)); 24 dp[0]=1,dp[cnt]=1; 25 int aver=cnt/2; 26 27 for(int i=0;i<m;i++) 28 for(int j=aver;j>=a[i];j--) 29 dp[j]=dp[j]||dp[j-a[i]]; 30 int index=0; 31 for(int i=aver;i>=0;i--) 32 if(dp[i]) 33 { 34 index=i; 35 break; 36 } 37 38 printf("%d\n",(cnt-index)-index); 39 } 40 return 0; 41 }
简单的01背包问题:一直放直到放不下,即最大价值。
1 #include <iostream> 2 #include <cstdio> 3 #include <algorithm> 4 #define maxn 105 5 using namespace std; 6 int dp[maxn][maxn*500];//不放全局就越界了。。。 7 int a[maxn]; 8 9 int main() 10 { 11 int n,m; 12 scanf("%d",&n); 13 while(n--) 14 { 15 scanf("%d",&m); 16 int cnt=0; 17 for(int i=1;i<=m;i++) 18 { 19 scanf("%d",&a[i]); 20 cnt+=a[i]; 21 } 22 23 for(int i=0;i<=cnt;i++) 24 dp[0][i]=0; 25 int aver=cnt/2; 26 for(int i=1;i<=m;i++) 27 for(int j=0;j<=aver;j++) 28 { 29 if(j<a[i]) 30 dp[i][j]=dp[i-1][j]; 31 else 32 dp[i][j]=max(dp[i-1][j],dp[i-1][j-a[i]]+a[i]); 33 } 34 printf("%d\n",cnt-2*dp[m][aver]); 35 } 36 return 0; 37 }
uva,624
递归+dp的魅力,我暂时还无法理解。。
1 #include <iostream> 2 #include <cstdio> 3 #include <algorithm> 4 #include <cstring> 5 6 using namespace std; 7 int dp[1005][25]; 8 9 void printCD(int a[],int n,int m) 10 { 11 if(n==0 || m==0) return; 12 if(dp[n][m]==dp[n][m-1]) printCD(a,n,m-1); 13 else 14 { 15 printCD(a,n-a[m],m-1); 16 printf("%d ",a[m]); 17 } 18 } 19 20 int main() 21 { 22 int n,m; 23 int a[25]; 24 while(~scanf("%d%d",&n,&m)) 25 { 26 for(int i=1;i<=m;i++) 27 scanf("%d",&a[i]); 28 29 memset(dp,0,sizeof(dp)); 30 for(int i=1;i<=n;i++) 31 for(int j=1;j<=m;j++) 32 { 33 if(a[j]<=i) 34 dp[i][j]=max(dp[i][j-1],dp[i-a[j]][j-1]+a[j]); 35 else 36 dp[i][j]=dp[i][j-1]; 37 } 38 39 printCD(a,n,m); 40 printf("sum:%d\n",dp[n][m]); 41 42 } 43 return 0; 44 }
反向放物品,确保打印顺利,遇到更小的不会更新。
1 #include <iostream> 2 #include <cstdio> 3 #include <algorithm> 4 #include <cstring> 5 #include <vector> 6 7 using namespace std; 8 9 10 int main() 11 { 12 int n,m; 13 int a[25]; 14 bool vis[25][10010]; 15 while(~scanf("%d%d",&n,&m)) 16 { 17 for(int i=1;i<=m;i++) 18 scanf("%d",&a[i]); 19 20 memset(vis,false,sizeof(vis)); 21 vector<int> dp(n+5,0); 22 23 for(int i=m;i>=1;i--) 24 for(int j=n;j>=a[i];j--) 25 { 26 if(dp[j]<dp[j-a[i]]+a[i]) 27 { 28 dp[j]=dp[j-a[i]]+a[i]; 29 vis[i][j]=true; 30 } 31 } 32 33 for(int i=1,j=n;i<=m;i++) 34 if(vis[i][j]) 35 { 36 printf("%d ",a[i]); 37 j-=a[i]; 38 } 39 printf("sum:%d\n",dp[n]); 40 } 41 return 0; 42 }
如果能更大就一直加,然后逐步更新Ans直到完毕。
1 #include <iostream> 2 #include <cstdio> 3 #include <algorithm> 4 #include <cstring> 5 #include <vector> 6 7 using namespace std; 8 vector<int> Container; 9 vector<int> Ans; 10 int Maxsum; 11 int n,m; 12 int a[21]; 13 14 void combination(int pos,int sum); 15 16 int main() 17 { 18 while(~scanf("%d%d",&n,&m)) 19 { 20 for(int i=0;i<m;i++) 21 scanf("%d",&a[i]); 22 23 Container.clear(); 24 Maxsum=0; 25 combination(0,0); 26 27 int sum=0; 28 for(int n:Ans) 29 { 30 printf("%d ",n); 31 sum+=n; 32 } 33 printf("sum:%d\n",sum); 34 35 } 36 return 0; 37 } 38 39 void combination(int pos,int sum) 40 { 41 if(sum>Maxsum) 42 { 43 Ans=Container; 44 Maxsum=sum; 45 } 46 47 for(int i=pos;i<m;i++) 48 { 49 if(sum+a[i]<=n) 50 { 51 sum+=a[i]; 52 Container.push_back(a[i]); 53 combination(i+1,sum); 54 55 sum-=a[i]; 56 Container.pop_back(); 57 } 58 } 59 }
今天发现:
#include<bits/stdc++.h>自带所有的头文件。
uva,10036
dp[i][j]表示前i个数组合除以k余j,那么dp[i][j]等于在i-1个数组合的基础上,加上或减去第i个数除以k.
1 #include <cstdio> 2 #include <iostream> 3 #include <algorithm> 4 #include <cmath> 5 #include <cstring> 6 7 using namespace std; 8 #define maxn 10005 9 10 int main() 11 { 12 int n,m,k,t; 13 bool dp[maxn][105]; 14 scanf("%d",&m); 15 while(m--) 16 { 17 scanf("%d%d%d",&n,&k,&t); 18 memset(dp,false,sizeof(dp)); 19 dp[0][(t%k+k)%k]=true; 20 for(int i=1;i<n;i++) 21 { 22 scanf("%d",&t); 23 int res=abs(t)%k; 24 for(int j=0;j<k;j++) 25 dp[i][j]=dp[i-1][(j+res)%k]||dp[i-1][(j-res+k)%k]; 26 } 27 if(dp[n-1][0]) 28 printf("Divisible\n"); 29 else 30 printf("Not divisible\n"); 31 } 32 return 0; 33 34 }
这种方法我理解了:
dp[i][j]表示前i个数组合(这里组合的意思:加或减)对k取余数为j
那么如果dp[i-1][j]=1表示前i-1个数组合对k取余数为j是正确的,那么加上或减去第i个数对k取余数即为。dp[i][(j+res)%k]和dp[i][(j-res+k)%k],而且它们是正确的。
所求即为前n个数的组合对k取余数为0 dp[n][0]如果是对的就成立。
1 #include <cstdio> 2 #include <iostream> 3 #include <algorithm> 4 #include <cmath> 5 #include <cstring> 6 7 using namespace std; 8 #define maxn 10005 9 10 int main() 11 { 12 int n,m,k,t; 13 bool dp[maxn][105]; 14 scanf("%d",&m); 15 while(m--) 16 { 17 scanf("%d%d%d",&n,&k,&t); 18 memset(dp,false,sizeof(dp)); 19 dp[0][(t%k+k)%k]=true; 20 for(int i=1;i<n;i++) 21 { 22 scanf("%d",&t); 23 int res=abs(t)%k; 24 for(int j=0;j<k;j++) 25 if(dp[i-1][j]) 26 dp[i][(j+res)%k]=dp[i][(j-res+k)%k]=true; 27 } 28 if(dp[n-1][0]) 29 printf("Divisible\n"); 30 else 31 printf("Not divisible\n"); 32 } 33 return 0; 34 35 }
uva,10664
第一道题目差不多。
1 #include <iostream> 2 #include <cstdio> 3 #include <cstring> 4 #include <algorithm> 5 #include <sstream> 6 #include <vector> 7 #include <string> 8 9 using namespace std; 10 11 12 int main() 13 { 14 int m; 15 int a[25]; 16 scanf("%d",&m); 17 getchar(); 18 while(m--) 19 { 20 string s; 21 int t,index=0,sum=0; 22 getline(cin,s); 23 stringstream ss(s); 24 while(ss>>t) 25 { 26 a[++index]=t; 27 sum+=t; 28 } 29 if(sum%2) 30 { 31 printf("NO\n"); 32 continue; 33 } 34 int cnt=sum/2; 35 vector<int> dp(cnt+5,0); 36 for(int i=1;i<=index;i++) 37 for(int j=cnt;j>=a[i];j--) 38 dp[j]=max(dp[j],dp[j-a[i]]+a[i]); 39 if(dp[cnt]==cnt) 40 printf("YES\n"); 41 else 42 printf("NO\n"); 43 } 44 return 0; 45 }
总结一下子集合问题:
用动态规划的方法,能够以伪多项式时间解决子集合加总问题。我们假定输入序列为:
- x 1, ..., xn
我们需要判断是否存在某个非空子集,使得子集中的数字和为0。我们序列中负数的和为N,正数的和为P。定义函数Q(i, s),它的涵义为:
- 是否存在 x 1, ..., xi的非空子集,使得子集中的数字和为 s
子集合加总问题的答案即为Q(n, 0)。
显然,如果s < N或者s > P,则Q(i,s) = false,因此无需记录这些值。我们把Q(i, s)的值保存在数组中,其中1 ≤ i ≤ n且N ≤ s ≤ P。
接下来使用循环来填充数组。首先,对于N ≤ s ≤ P,设定
- Q(1, s) := ( x 1 = s)
随后,对于i = 2, …, n和N ≤ s ≤ P,设定
- Q( i, s) := Q( i - 1, s) 或 ( xi = s) 或 Q( i - 1, s - xi)
算法运行的总时间为O(n(P - N))。
对算法加以改动,即可返回和为0的子集。
在计算复杂度理论中,这种解法需要的时间并不算多项式时间,这是因为P - N同输入大小并不成线性关系。原因在于输入大小仅仅取决于表达输入所需要的比特数。算法的时间复杂度同N与P的值成线性关系,而它们的值与表达它们所需的比特数成幂关系。
1.即一个集合中负数部分为n 正数部分为p,即为np完全问题所以无法在多项式时间内解决。
要求:1.某两个子集合为s
2.分成的两个子集合差最小,分成的两个子集和相等。
3.正向逆向放物品的能否使得最后所留空间最小,背包特例。
正向:前i中物品的选出重量不超过j的的物品总价值最大。
逆向:从第i个物品开始选出重量不超过j的物品的总价值最大。所以当考虑子集和为某一给定值时,用逆向求出。
4.同余dp,即子集和变幻加减号之后得到的数字能否整除某个数。
思想就是:找到i+1和i项之间的bool关系或者加和关系。
找出i与j的关系。即在i的条件下j代表什么。从题意中挖掘这两层关系然后写出状态转移方程,最后求解。