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

程序设计实习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)看看题目要求n<=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),那么可以证明价值为v[i]的物品必须使用当且仅当

k>=0kv[i]<=x(1)kfxkv[i]=0

这是因为 f(y)=v[i]y+v[i]y
v[i]y=v[i]yv[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;
} 
阅读更多
版权声明:本文为博主原创文章,未经博主允许不得转载。 https://blog.csdn.net/PKU_ZZY/article/details/51548085
个人分类: Algorithm
想对作者说点什么? 我来说一句

没有更多推荐了,返回首页

不良信息举报

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

最多只允许输入30个字

加入CSDN,享受更精准的内容推荐,与500万程序员共同成长!
关闭
关闭