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

原创 2016年05月31日 18:28:03

程序设计实习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;
} 
版权声明:本文为博主原创文章,未经博主允许不得转载。

位运算(3)-- 高级运用

注:此文内容来自于对【数据结构与算法之位运算】课程所做的笔记一、二进制中1的个数问题:给定一个无符号整型变量,求其二进制表示中“1”的个数。 相似问题:判断整数A转换成整数B需要的次数。(A ^ B...
  • xushao_Movens
  • xushao_Movens
  • 2016年08月14日 08:53
  • 555

按位运算&数学等价式

按位运算符C++共有6个> , ~ , & , | , ^移位运算符//左移运算符
  • qq_15015129
  • qq_15015129
  • 2016年12月06日 17:13
  • 183

位运算的常见操作和题目

一、位运算基本操作 &          与            1 & 1 = 1;1 & 0 = 0;0 & 0 = 0  只有当两个位都为1时,结果为1 |   ...
  • u013074465
  • u013074465
  • 2015年06月29日 21:04
  • 2586

高精度 压四位原理及实现

高精度 压四位原理及实现
  • q873040807
  • q873040807
  • 2016年01月14日 19:04
  • 1032

高精度计算(三)压位

压位高精
  • sssSSSay
  • sssSSSay
  • 2016年08月03日 11:47
  • 2802

数据结构-位运算的使用

位运算:直接对整数在内存中的二进位进行操作的运算位运算包括与,或,非,异或,同或,移位等,位运算是最接近机器码的运算,在算法当中使用位运算会带来很大的便利注:java十进制转二进制:Integer.t...
  • qq_26971803
  • qq_26971803
  • 2016年04月19日 13:06
  • 1751

位运算总结(按位与,或,异或)

按位与运算符(&) 参加运算的两个数据,按二进制位进行“与”运算。 运算规则:0&0=0;  0&1=0;   1&0=0;    1&1=1;       即:两位同时为“1”,结果才为“1”...
  • sinat_35121480
  • sinat_35121480
  • 2016年12月07日 23:40
  • 3862

编程中位运算用法总结

位运算应用口诀 清零取反要用与,某位置一可用或 若要取反和交换,轻轻松松用异或 移位运算 要点 1 它们都是双目运算符,两个运算分量都是整形,结果也是整形。          2 "  ...
  • y990041769
  • y990041769
  • 2013年10月23日 19:45
  • 17750

位运算之位操作符

4.2.1.1 位与& (1)注意:位与符号是一个&,两个&&是逻辑与。 (2)真值表:1&0=0 1&1=1 0&0=0 0&1=0 (3)从真值表可以看出位与操作的特点是,只有1和1位与结果为1,...
  • weicao1990
  • weicao1990
  • 2016年02月19日 22:32
  • 1225

位运算的应用与技巧:

位运算的应用: 程序中的所有数在计算机内存中都是以二进制的形式储存的。所谓位运算,就是直接对整数在内存中的二进制位进行操作,一般解题时都用一个十进制整数来代表某个集合。 基本的位运算操作: &(按...
  • consciousman
  • consciousman
  • 2016年10月15日 21:24
  • 869
内容举报
返回顶部
收藏助手
不良信息举报
您举报文章:程序设计实习2016推荐练习3 硬币(dp+数学/位运算压位)
举报原因:
原因补充:

(最多只允许输入30个字)