您的位置:首页 > 其它

程序设计实习2016推荐练习3 硬币(dp+数学/位运算压位)

2016-05-31 18:28 330 查看
程序设计实习2016推荐练习3 硬币(dp+数学/位运算压位)

总时间限制: 1000ms 内存限制: 262144kB

描述

宇航员Bob有一天来到火星上,他有收集硬币的习惯。于是他将火星上所有面值的硬币都收集起来了,一共有n种,每种只有一个:面值分别为a1,a2… an。 Bob在机场看到了一个特别喜欢的礼物,想买来送给朋友Alice,这个礼物的价格是X元。Bob很想知道为了买这个礼物他的哪些硬币是必须被使用的,即Bob必须放弃收集好的哪些硬币种类。飞机场不提供找零,只接受恰好X元。

输入

第一行包含两个正整数n和x。(1 <= n <= 200, 1 <= x <= 10000)

第二行从小到大为n个正整数a1, a2, a3 … an (1 <= ai <= x)

输出

第一行是一个整数,即有多少种硬币是必须被使用的。

第二行是这些必须使用的硬币的面值(从小到大排列)。

样例输入

5 18

1 2 3 5 10

样例输出

2

5 10

提示

输入数据将保证给定面值的硬币中至少有一种组合能恰好能够支付X元。

如果不存在必须被使用的硬币,则第一行输出0,第二行输出空行。

这是一个脱胎于基础题而又不落俗套,趣味性技术性极高的dp题,做完不禁拍案叫绝。

首先题意就不是很好读懂,对着样例多看几遍外加WA了一回才意识到,不是要输出一组解而是要输出所有解中必须用到的硬币。

那么如果只是输出一组解就有很朴素的做法:除了正常dp之外,不断地复制状态,记录下状态来找到一组解。那么要找到所有解的交集,不妨每一次生成新解后与旧解再取一回交集即可。这个方法很朴素,复杂度O(n2v)O(n^2v)看看题目要求n<=200,v<=10000n<=200,v<=10000感觉也许可行,就算TLE,也只是因为差一个常数。这就是朴素的version 1,TLE。

那么看到version 1只TLE了一点点,为了拯救version 1,考虑一下version 1的瓶颈,在于状态复制的重复与每一次更新是空白区的浪费。加入两个优化,为了空白区不扫描,记录下上一回非空白区最大值下一次从这里开始扫描,这一个优化极其容易想到。至于对于bool数组记录状态的话,复制起来为了复制1 bit的信息,就是一个点的状态,复制了一个1 字节=8 bit的bool类型!而且取交集时候也与取按位与比浪费了很多,所以用位运算加速,这在version 1上可以改过来,附上version 2,(注意一些位操作函数一定要内联,否则开销令优化得不偿失)。结果AC了,早闻二进制加速黑科技,今日一用方知其之厉害。以后遇到大量复制bool数组可以用二进制加速,当然加速比一般在3-4,数量级差太多就无力回天了。

当然,本题是有数学方法的,不只计算可不可行,计算出可行解个数f(x)f(x),那么可以证明价值为v[i]v[i]的物品必须使用当且仅当

∑k>=0,k∗v[i]<=x(−1)kf(x−k∗v[i])=0\sum_{k>=0,k*v[i]<=x}(-1)^kf(x-k*v[i])=0

这是因为 f(y)=含v[i]拼出y的种数+不含v[i]拼出y的种数f(y)=含v[i]拼出y的种数+不含v[i]拼出y的种数

而 含v[i]拼出y的种数=不含v[i]拼出y−v[i]的种数含v[i]拼出y的种数=不含v[i]拼出y-v[i]的种数

从这个关系得到灵感写出上式,进而证明这个关系,这就是version 3。

version 1

Time Limit Exceeded 2100kB  1160ms  1004 B


#include<stdio.h>
#include<algorithm>

using namespace std;

int n,x,num=0,max_j=0;
int v[200];
bool must[10001][200],legal[10001];
bool first=true;

int main()
{
scanf("%d%d",&n,&x);
for (int i=0;i<n;i++)
scanf("%d",&v[i]);
sort(v,v+n);
legal[0]=true;
for (int i=0;i<n;i++)
for (int j=max_j+v[i]>x?x:max_j+v[i];j>=v[i];j--)
if (legal[j-v[i]])
{
if (legal[j])
{
for (int k=0;k<i;k++)
must[j][k]&=must[j-v[i]][k];
}
else
{
legal[j]=true;
for (int k=0;k<i;k++)
must[j][k]=must[j-v[i]][k];
must[j][i]=true;
}
max_j+=v[i];
}
/*
for (int j=0;j<=x;j++)
{
printf("%d:",j);
for (int i=0;i<n;i++)
if (must[j][i])
printf("%d ",v[i]);
printf("\n");
}
*/
num=0;
for (int i=0;i<n;i++)
if (must[x][i])
num++;
printf("%d\n",num);
for (int i=0;i<n;i++)
if (must[x][i])
if (first)
{
printf("%d",v[i]);
first=false;
}
else
printf(" %d",v[i]);
printf("\n");
return 0;
}


version 2

Accepted    7904kB  430ms   1063 B


#define L sizeof(int)

#include<stdio.h>
#include<algorithm>

using namespace std;

int n,x,num=0,max_j=0;
int v[200];
int must[10000][200],legal[10000];
bool first=true;

inline bool l(int k)
{
return legal[k/L]&(1<<(k%L));
}

inline bool m(int j,int k)
{
return must[j][k/L]&(1<<(k%L));
}

inline void mset(int j,int k,int value)
{
must[j][k/L]|=(value<<(k%L));
}

int main()
{
scanf("%d%d",&n,&x);
for (int i=0;i<n;i++)
scanf("%d",&v[i]);
sort(v,v+n);
legal[0]=1;
for (int i=0;i<n;i++)
for (int j=max_j+v[i]>x?x:max_j+v[i];j>=v[i];j--)
if (l(j-v[i]))
{
if (l(j))
{
for (int k=0;k<=i/L+1;k++)
must[j][k]&=must[j-v[i]][k];
}
else
{
legal[j/L]|=(1<<(j%L));
for (int k=0;k<=i/L+1;k++)
must[j][k]=must[j-v[i]][k];
mset(j,i,1);
}
max_j+=v[i];
}
num=0;
for (int i=0;i<n;i++)
if (m(x,i))
num++;
printf("%d\n",num);
for (int i=0;i<n;i++)
if (m(x,i))
if (first)
{
printf("%d",v[i]);
first=false;
}
else
printf(" %d",v[i]);
printf("\n");
return 0;
}


version 3

Accepted    256kB   0ms 597 B


#include<stdio.h>
#include<algorithm>

using namespace std;

int n,x,num=0,s,check,v[200],f[10001],must[200];

int main()
{
scanf("%d%d",&n,&x);
for (int i=0;i<n;i++)
scanf("%d",&v[i]);
sort(v,v+n);
f[0]=1;
for (int i=0;i<n;i++)
for (int j=x;j>=v[i];j--)
f[j]+=f[j-v[i]];
num=0;
for (int i=0;i<n;i++)
{
s=1;
check=0;
for (int j=0;j*v[i]<=x;j++)
{
check+=s*f[x-j*v[i]];
s*=-1;
}
if (check==0)
must[num++]=v[i];
}
printf("%d\n",num);
for (int i=0;i<num-1;i++)
printf("%d ",must[i]);
if (num)
printf("%d\n",must[num-1]);
else
printf("\n");
return 0;
}
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: