subset sum

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 }
View Code

 

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 }
View Code

 

简单的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 }
View Code

 

 

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 }
View Code

反向放物品,确保打印顺利,遇到更小的不会更新。

 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 }
View Code

如果能更大就一直加,然后逐步更新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 }
View Code

 

 

今天发现:

#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 }
View Code

 

这种方法我理解了:

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 }
View Code

 

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 }
View Code

 

 

 

总结一下子集合问题:

动态规划的方法,能够以伪多项式时间解决子集合加总问题。我们假定输入序列为:

x 1, ...,  xn

我们需要判断是否存在某个非空子集,使得子集中的数字和为0。我们序列中负数的和为N,正数的和为P。定义函数Q(is),它的涵义为:

是否存在 x 1, ...,  xi的非空子集,使得子集中的数字和为 s

子集合加总问题的答案即为Q(n, 0)。

显然,如果s < N或者s > P,则Q(i,s) = false,因此无需记录这些值。我们把Q(is)的值保存在数组中,其中1 ≤ i ≤ nN ≤ s ≤ P

接下来使用循环来填充数组。首先,对于N ≤ s ≤ P,设定

Q(1,  s) := ( x 1 =  s)

随后,对于i = 2, …, nN ≤ s ≤ P,设定

Q( is) :=  Q( i - 1,  s ( xi =  s  Q( i - 1,  s -  xi)

算法运行的总时间为O(n(P - N))。

对算法加以改动,即可返回和为0的子集。

在计算复杂度理论中,这种解法需要的时间并不算多项式时间,这是因为P - N输入大小并不成线性关系。原因在于输入大小仅仅取决于表达输入所需要的比特数。算法的时间复杂度同NP的值成线性关系,而它们的值与表达它们所需的比特数成幂关系。

1.即一个集合中负数部分为n 正数部分为p,即为np完全问题所以无法在多项式时间内解决。

要求:1.某两个子集合为s

2.分成的两个子集合差最小,分成的两个子集和相等。

3.正向逆向放物品的能否使得最后所留空间最小,背包特例。

正向:前i中物品的选出重量不超过j的的物品总价值最大。

逆向:从第i个物品开始选出重量不超过j的物品的总价值最大。所以当考虑子集和为某一给定值时,用逆向求出。

4.同余dp,即子集和变幻加减号之后得到的数字能否整除某个数。

 

思想就是:找到i+1和i项之间的bool关系或者加和关系。

找出i与j的关系。即在i的条件下j代表什么。从题意中挖掘这两层关系然后写出状态转移方程,最后求解。

 

转载于:https://www.cnblogs.com/do-it-best/p/5471142.html

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值