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

相关文章推荐

位运算学习

今天这个位运算搞了我半天,总算弄明白了。虽然感觉用的不多,但对计算机每一位的操作也是一种能力。 /* * 2017年7月21日16:20:38 4.题目:输入一个整数a,再输入两个整数p(p...

位运算 学习

首先介绍逻辑运算中的真值表,如表 A B A与B A或B 非A A异或B   0 0 ...

我是如何成为一名python大咖的?

人生苦短,都说必须python,那么我分享下我是如何从小白成为Python资深开发者的吧。2014年我大学刚毕业..

计数排序+位运算

计数排序 在某些特殊情况下,对数据进行排序,使用计数排序可以实现O(N)的时间复杂度。比如现在有K个数a0,a1,a2........ak,这些数的范围是0 int ctr[MAXN];//计数器...

Java常见运算符整理

本文是在学习中的总结,欢迎转载但请注明出处:http://blog.csdn.net/pistolove/article/details/44724267 本文主要介绍Java中常见的运算符,重...

位运算学习(一)

问题来源我是由一个算法题目想到位运算的。题目是《算法问题实战策略》中的204页的题目,题目如下: 在没有电话的时代,摩尔斯电码是无线电传输领域中的一个常用代码。电码以短信号和长信号的不同组合表示各种...

移位运算符学习

左移运算符就是在二进制的基础上对数字进行平移。按照平移的方向和填充数字的规则分为三种:>(带符号右移)和>>>(无符号右移)。   在移位运算时,byte、short和char类型移位后的结...

学习笔记---位运算

位运算 位运算符 优先级 运算符 功能 结合方式 2 ~ 按位取反 由左向右 5 > 左移   右移 由左向右 8 ...

数位DP学习小结

一、学习心得体会 问题描述: 一般体现为,定义某种性质K,问某区间内具有K性质的数的个数 往往给的区间会很大,对区间内的每个数进行判断显然会超时 于是数位DP登场 数位DP,顾名思义,是对数字的每一位...

JAVA学习(4)位运算

只需要记住16进制的2 、4、8、C 就能记住二进制码 16进制转换成2进制:转换方式有2种 1.先转换成10进制,再转成2进制 2.直接转换成2进制 2进制转16进制:同16进制转2进...

Java位运算学习

模仿课本上敲的,不过还是很有收获,说明太弱了。。。。 import java.awt.BorderLayout; import java.awt.Container; import java.a...
返回顶部
收藏助手
不良信息举报
您举报文章:深度学习:神经网络中的前向传播和反向传播算法推导
举报原因:
原因补充:

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