程序设计实习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 程序设计实习之C++部分作业题汇总 - A:位运算

A01:编程填空:第i位替换题目来源:2017 程序设计实习之C++部分作业题汇总总时间限制: 1000ms 内存限制: 1024kB 描述 写出函数中缺失的部分,使得函数返回值为一个整数,该整数...

Java位运算在程序设计中的使用:位掩码(BitMask)

在Java中,位运算符有很多,例如与(&)、非(~)、或(|)、异或(^)、移位(>)等。这些运算符在日常编码中很少会用到。 在下面的一个例子中,会用到位掩码(BitMask),其中包含大量的位...

Java位运算在程序设计中的使用:位掩码(BitMask)

在Java中,位运算符有很多,例如与(&)、非(~)、或(|)、异或(^)、移位(>)等。这些运算符在日常编码中很少会用到。 在下面的一个例子中,会用到位掩码(BitMask),其中包含大量的位运算...
  • Qsir
  • Qsir
  • 2017年05月17日 14:40
  • 219

Java位运算在程序设计中的使用:位掩码(BitMask)

在Java中,位运算符有很多,例如与(&)、非(~)、或(|)、异或(^)、移位(>)等。这些运算符在日常编码中很少会用到。 在下面的一个例子中,会用到位掩码(BitMask),其中包含大量的位...

"尚学堂杯"哈尔滨理工大学第七届程序设计竞赛 G.Great Atm(二进制,位运算)

题目: G.Great Atm Time Limit: 1000 MS Memory Limit: 32768 K Total Submit: 615 (191 u...

面向对象程序设计第一次实验课——位运算封装

题目的大意是用一个类封装位运算,并通过位运算实现四则运算。我一下子就想起了bitset,但是bitset并不支持四则运算。后来想想也许叫大整数类比较合适吧。当然还是有很多问题的,有些问题是写完了才发现...

【数学】CSU 1810 Reverse (2016湖南省第十二届大学生计算机程序设计竞赛)

题目链接:   http://acm.csu.edu.cn/OnlineJudge/problem.php?id=1810 题目大意:   一个长度为N的十进制数,R(i,j)表...

贪心+数学——2016 (湖南省第十二届大学生计算机程序设计竞赛 A)

题目链接: http://acm.csu.edu.cn/OnlineJudge/problem.php?id=1803 分析: 给出连个数n,m(数据范围十亿),求1-n中的数a乘以1-m中的数b...
  • FeBr2
  • FeBr2
  • 2016年09月03日 22:00
  • 664

【最短路】【数学】CSU 1806 Toll (2016湖南省第十二届大学生计算机程序设计竞赛)

题目链接:   http://acm.csu.edu.cn/OnlineJudge/problem.php?id=1806 题目大意:   N个点M条有向边,给一个时间T(2≤n...

CSU 1803 2016 (数学)【2016年湖南省第十二届大学生计算机程序设计竞赛 - A】

题目点我点我点我 湖南省第十二届大学生计算机程序设计竞赛 1803: 2016 Time Limit: 5 Sec  Memory Limit: 128 MB Submit: 289 ...
内容举报
返回顶部
收藏助手
不良信息举报
您举报文章:程序设计实习2016推荐练习3 硬币(dp+数学/位运算压位)
举报原因:
原因补充:

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