200 条题解
-
12
PowderHan LV 10 @ 7 年前
-
26 年前@
01背包+还原决策方案
-
14 年前@
dp统计方案数,再判断方案数,若为1,再dfs搜方案即可。
-
16 年前@
01背包来一发
网上看了好些记录方法都不太喜,自己就写了个
解法一
Pascal
var dp,vis:array[0..100004]of longint;
a,put:array[0..104]of longint;
n,i,j,v,sum,flag,tp,ok:longint;
begin
readln(v,n);
for i:=0 to n-1 do
begin
read(a[i]);
sum:=sum+a[i];
end;
v:=sum-v;
fillchar(vis,sizeof(vis),255);
for i:=0 to n-1 do
for j:=v downto a[i] do
begin
if(dp[j-a[i]]+a[i]>dp[j])then begin
dp[j]:=dp[j-a[i]]+a[i];
vis[j]:=i;
end
else if(dp[j-a[i]]+a[i]=dp[j])and(dp[j]=j)and(j=v)then flag:=1;
end;
if dp[v]<>v then writeln(0)
else if(flag=1)then writeln(-1)
else begin
tp:=v;
ok:=0;
while(vis[tp]<>-1)do
begin
put[ok+1]:=vis[tp]+1;
inc(ok);
tp:=tp-a[vis[tp]];
end;
for i:=ok downto 0 do
if i<>0 then write(put[i],' ');
end;
end.
解法二
Pascal
var f:array[0..200000]of longint;
b:array[0..100]of longint;
a:array[0..100]of longint;
c:array[0..200000]of longint;
n,v,i,j:longint;
begin
readln(v);
readln(n);
for i:=1 to n do
readln(a[i]);
f[0]:=1;
for i:=1 to n do
for j:=v downto a[i] do
if f[j-a[i]]>0 then
begin
if f[j]=0 then c[j]:=i;
f[j]:=f[j]+f[j-a[i]];
if (j=v) and (f[j]>1) then begin writeln(-1);halt; end;
end;
if f[v]=0 then
begin
writeln(0);
halt;
end;
if f[v]=1 then
begin
i:=v;
while i<>0 do
begin
inc(b[c[i]]);
dec(i,a[c[i]]);
end;
for i:=1 to n do
if b[i]=0 then
write(i,' ');
end;
end.
最后,P党加油!
-
16 年前@
-
17 年前@
//暴力dp,空间有点大
//f(i, j) 表示前i张牌能否取到j的重量
#include<iostream>
#include<algorithm>
#include<cstdio>
#include<cstring>
#include<stack>
using namespace std;int a[105];
bool f[105][100010];
stack<int> ans;int main()
{
int w, n, sum=0;
cin>>w>>n;
for(int i=1;i<=n;i++){ scanf("%d", &a[i]); sum+=a[i]; }
w=sum-w;
f[0][0]=true;
for(int i=1;i<=n;i++){
for(int j=sum;j>=a[i];j--)
f[i][j]=f[i-1][j-a[i]]|f[i-1][j];
for(int j=0;j<a[i];j++)
f[i][j]=f[i-1][j];
}
if(!f[n][w]) cout<<0<<endl;
else{
int j=w;
for(int i=n;i>=1;i--){
if(j<a[i]) continue;
if(f[i-1][j-a[i]]&&f[i-1][j]){
cout<<-1<<endl;
return 0;
}
if(f[i-1][j-a[i]]){
ans.push(i);
j-=a[i];
}
}
while(!ans.empty()){
cout<<ans.top()<<' ';
ans.pop();
}
cout<<endl;
}
return 0;
} -
03 年前@
pf吧......我毕竟蒟蒻
--------- 分割线 -----------
........唉,别喷我.我 -
04 年前@
就是一道0 - 1背包问题,单数这里需要记录过程,所以用到path数组,最后递归打印。
-
05 年前@
三维暴力+滚动优化;
-
05 年前@
其实这道题可以三维暴力+滚动优化;
-
08 年前@
http://blog.csdn.net/jackypigpig/article/details/69945527
我的方法较为暴力——
- 相当于做一次简单的 01背包,附加操作是记下每个状态下需要用到的牌的编号(用了个bitset),当方案数大于一时就不用再理它了。最终看看方案数分类输出就行了
- 不过这样时间和空间(特别是空间)就十分庞大 > 总耗时59ms 峰值内存2.32MiB
- 其实,你也可以不用bitset记录,可以用个类似于**邻接表**的东西记下到当前状态的**路径**,输出时走一遍,也可以。
- 这样时间和空间都明显好很多 > 总耗时22ms 峰值内存928.0KiB
-
08 年前@
简单背包……
```c++
#include<cstdio>
#include<stack>
#include<vector>
#include<cstring>
#include<algorithm>
using namespace std;
const int maxn = 200;int n,totweith,weith[maxn],num_ans=0,allweith=0;
char info[maxn][1100];
vector<int> ans;
stack<int> s;void dp(int now,int left)
{
if(info[now][left]==1) return;
if(now==n+1)
{
if(left!=0) return;
stack<int> temp=s;
while(!temp.empty())
{
ans.push_back(temp.top());
temp.pop();
}
num_ans++;
return;
}
int k=num_ans;
if(weith[now]<=left)
{
s.push(now);
dp(now+1,left-weith[now]);
s.pop();
}
if(num_ans>=2) return;
dp(now+1,left);
if(num_ans==k) info[now][left]=1;
}int main()
{
/*freopen("bag","r",stdin);*/
memset(info,-1,sizeof(info));
scanf("%d%d",&totweith,&n);
for(int i=1;i<=n;i++)
{
scanf("%d",&weith[i]);
allweith+=weith[i];
}dp(1,allweith-totweith);
if(num_ans==1)
for(int i=(int)ans.size()-1;i>=0;i--)
printf("%d ",ans[i]);
else printf("%d",num_ans==0 ? 0 : -1);
printf("\n");
return 0;
}/* Accepted, time = 45 ms, mem = 776 KiB, score = 100 2016年6月24日星期五 */
``` -
09 年前@
更新记录之前的牌的时候要注意,不然会WA第四个点(#3)
附上程序:
#include<cstdio>
bool b[101];
int f[100001],l[100001],p[100001],m,n,w;
void find(int i)
{
if(!i)
return;
find(l[i]);
b[p[i]]=true;
}
int main()
{
scanf("%d%d",&m,&n);
f[0]=1;
for(int i=1;i<=n;++i)
{
scanf("%d",&w);
for(int j=m;j>=w;--j)
if(f[j-w])
{
f[j]+=f[j-w];
if(f[j]==1)/*这条语句是重点这条语句是重点这条语句是重点,重要的事情说三遍*/
l[j]=j-w,p[j]=i;
}
}
if(f[m]>1)
{puts("-1");return 0;}
else
if(!f[m])
{puts("0");return 0;}
find(m);
for(int i=1;i<=n;++i)
if(!b[i])
printf("%d ",i);
return 0;
} -
09 年前@
简单背包,一个数组 f 记录方案数,另一个数组 g 记录从哪里转移而来,最后递归输出。两个数组都可以省略一维。(当然不省略也可以,只是空间开销比较大)。
f[j] = f[j]+f[j-weight[i]]
其中 i 是当前物品,j 是当前重量。记住 f 和 g 要开到100*1000那么大,我第一次就因为这个错。#include <stdio.h>
#include <string.h>
#define INF 1000000
int weight[105];
int f[100005];
int g[100005];
int print(int end){
int begin, i;
if(end == 0)
return 0;
begin = print(g[end]);
for(i=begin+1; i<=end; i++){
if(end-g[end] == weight[i]){
printf("%d ", i);
return i;
}
}
}
int main(){
int num, totalW;
int i, j;
scanf("%d %d", &totalW, &num);
totalW *= -1;
for(i=1; i<=num; i++){
scanf("%d", &weight[i]);
totalW += weight[i];
}
memset(f, 0, sizeof f);
memset(g, 0, sizeof g);
f[0] = 1;
for(i=1; i<=num; i++){
for(j=totalW; j>=weight[i]; j--){
f[j] += f[j-weight[i]];
if(f[j-weight[i]] > 0)
g[j] = j-weight[i];
}
}
if(f[totalW] > 1)
printf("-1");
else if(f[totalW] == 0)
printf("0");
else
print(totalW);
return 0;
} -
09 年前@
第一次动规过了,之后突然想试一下dfs能不能过,结果a掉6个点,2个点wa,2个tle。。。=_=,动规果然所向披靡
-
09 年前@
大神们帮我看看怎么优化吧,总是超时,能给我过了的话感激不尽!
#include<iostream>
#include<cstdio>
using namespace std;
int v[1000000]={0},n,a[101],b[101],rest,p;
bool f[1000000][101]={0};
void find(int x,int y,int z)
{
//cout<<"x="<<x<<' '<<"y="<<y<<' '<<"z="<<z<<endl;
if(z<0){
return;
}
if(z==0){
for(int i=1;i<y;i++){
printf("%d ",b[i]);
}
return;
}
int i;
for(i=x;i<p;i++){
if(v[z-a[i]]!=0){
b[y]=i;
z-=a[i];
find(i+1,y+1,z);
z+=a[i];
}
}
return;
}
int main()
{
int weight,sum[101]={0},i,j;
scanf("%d%d",&weight,&n);
for(i=1;i<=n;i++){
scanf("%d",&a[i]);
sum[i]=sum[i-1]+a[i];
}
rest=sum[n]-weight;
v[0]=1;
for(j=1;j<=n;j++){
for(i=rest;i>=1;i--){
if(a[j]<=i&&v[i-a[j]]!=0){
if(i==rest){
p=j;
}
v[i]+=v[i-a[j]];
}
}}
//cout<<"p="<<p<<endl;
if(v[rest]==0){
printf("0");
return 0;
}
else if(v[rest]>1){
printf("-1");
return 0;
}
else{
find(1,1,rest-a[p]);
printf("%d",p);
}
} -
09 年前@
格式不大好,大家见谅
-
09 年前@
求大神们帮我看看怎么优化,总是超时。
#include<iostream>
#include<cstdio>
using namespace std;
int v[1000000]={0},n,a[101],b[101],rest;
bool f[1000000][101]={0};
void find(int x,int y,int z)
{
if(z<0){
return;
}
if(x>n&&z==0){
for(int i=1;i<y;i++){
printf("%d ",b[i]);
}
return;
}
int i;
for(i=x;i<=n;i++){
if(v[z-a[i]]!=0){
b[y]=i;
z-=a[i];
find(i+1,y+1,z);
z+=a[i];
}
}
return;
}
int main()
{
int weight,sum[101]={0},i,j;
scanf("%d%d",&weight,&n);
for(i=1;i<=n;i++){
scanf("%d",&a[i]);
sum[i]=sum[i-1]+a[i];
}
rest=sum[n]-weight;
v[0]=1;
for(j=1;j<=n;j++){
for(i=rest;i>=1;i--){
if(a[j]<=i&&v[i-a[j]]!=0){
v[i]+=v[i-a[j]];
}
}}
if(v[rest]==0){
printf("0");
return 0;
}
else if(v[rest]>1){
printf("-1");
return 0;
}
else{
find(1,1,rest);
}
} -
09 年前@
01背包 输出方案有点麻烦
-
010 年前@
比较暴力的两遍搜索+记忆化....
这个数据还是比较弱的......
第一遍DFS,就是求到达最终目标的方案总数..
第二遍DFS,依照到达目标的方案总数做转移.....
假设我们到达了状态(i,v),表示我们正在选择第i个牌是否存在,并且当前选取的总重为v,那么从(i,v)有且仅有一个状态转移出去(因为方案唯一,且存在方案)....
直接判就好....int n,m;
int w[105];
int d[105][100050];
bool used[105][100050];int DFS(int x,int v) //path count to end point.
{
if(v>m) return d[x][v]=0;
if(x==n)
{
if(v==m) return d[x][v]=1;
else return d[x][v]=0;
}
if(used[x][v]) return d[x][v];
used[x][v]=true;
return d[x][v]=min(DFS(x+1,v)+DFS(x+1,v+w[x]),2);
}bool res[105];
void SDFS(int x,int v)
{
if(x==n) return ;
if(d[x+1][v]) res[x]=0,SDFS(x+1,v);
if(d[x+1][v+w[x]]) res[x]=1,SDFS(x+1,v+w[x]);
}int main()
{
m=getint();
n=getint();
for(int i=0;i<n;i++)
w[i]=getint();int cnt=DFS(0,0);
if(cnt>1) printf("-1\n");
else if(cnt==0) printf("0\n");
else
{
SDFS(0,0);
for(int i=0;i<n;i++)
if(!res[i]) printf("%d ",i+1);
printf("\n");
}return 0;
}