蓝桥杯省赛[新手向题解] 2019 第十届 C/C++ A组 第九题 DP

蓝桥杯省赛[新手向题解] 2019 第十届 C/C++ A组 第九题 [传送门:暂无]


Problem Description

  \(N\)袋糖果,每袋\(K\)颗糖,一共存在\(M\)种糖果。求最少选多少袋糖果可以吃到所有种类糖果。

Input

  第一行仨整数\(N\),\(M\)\(K\)
  接下来N行每行\(K\)整数,第\(i\)行表示第\(i\)袋糖有哪几种糖果
  (可能两袋内容相同,也可能一袋里有相同种类的糖果,这不重要)
  (数据范围挺小,大概\(N≤200,M\)\(k≤20\)

Output

  输出最小袋数

并不想按格式写:

赛时:

  还剩40分钟的分钟的时候摸到最后两题,感觉这题希望比较大,遂果断最后一题骗分再回来写这题。结果这题从12:35开始想算法到12:50写完……果然还是DP爽。

想网络流的时候先看数据范围是不是能直接莽

(其实是因为不抄板子写不出网络流)

  每种味道存在或不存在,一共\(2^{M}=2^{20}=1024^2\)种状态,直接状压DP

感觉还是直接上?比较好解释:
  比如\(M=6,K=3\),一共有\(2^6=64\)种状态,用二进制表示分别为:
    \(0=(000000)_2\) 表示所有糖果都没有
    \(1=(000001)_2\) 表示只有第1种糖果
    \(2=(000010)_2\) 表示只有第2种糖果
    \(3=(000011)_2\) 表示有第1种和第2种糖果
    ......
    \(63=(111111)_2\) 表示所有糖果都有
    \(dp[i][j]\)表示搜索到第\(i\)袋的时候,达到状态\(j\)最少需要多少袋,最终需要的答案是\(dp[N][2^{M}]\)
    比如当前搜索到第\(4\)袋的内容为\(1,3,4\),对应的状态为\((001101)_2=13\),对于前3袋处理过的任意状态,比如有第3种和第5种糖果的状态\((010100)_2=20\),如果把两者结合,就会转移到有第\(1,3,4,5\)种糖果的状态\((011101)_2=29\),这里的运算是按位或(|),也就是只要两者其中之一包含\(i\)种糖果,合并的状态中就包含第\(i\)种糖果。按这个例子来说就是\(dp[3][20]\)根据输入的001101转移到了\(dp[4][29]=dp[3][20]+1\)
    要注意,\(dp[4][29]\)可能不止由\(dp[3][20]\)转移过来,我们要保留所有转移方案中袋数最小的,所以应该是\(dp[4][29] = min(dp[4][29],dp[3][20]+1)\)
    同时,如果放弃第4袋糖果,就有\(dp[4][20]=dp[3][20]\),即没有产生影响。

  对于第\(i\)袋糖果\(a_{i_1},a_{i_2}......a_{i_K}\),它的状态为\(p[i]=2^{a_{i_1}-1}\ |\ 2^{a_{i_2}-1}\ |\ ......\ |\ 2^{a_{i_K}-1}\),这里涉及二进制的转换,第\(i\)种糖果在二进制的右边第\(i\)位,它的十进制为\(2^{i-1}\),然后把每个糖果贡献加起来就是这袋的总贡献。但是,一袋糖里可能会有相同的,所以用加法就会出错,不过按位或就不会,因为1和1作按位或,结果仍然是1,即重复的糖果不影响结果。
  状态转移方程:
\[dp[i+1][j|p[i]] = min(dp[i+1][j|p[i]],dp[i][j] + 1)\]
  初始状态:
    到达没有糖的状态不需要任何袋数,\(dp[i][0]=0\)
    第0袋的时候,到达任何状态的最小袋数都是无穷大,即\(dp[0][j] = INF\)
  (可以滚动数组)

Codes:


咕咕咕~

(随便写点,仅供参考)
const int MAXN = 1024*1024;
int dp[MAXN];
int N,M,K;

int main()
{
    cin >> N >> M >> K;
    int MAXM = pow(2,M);
    memset(dp,0x3f3f,sizeof(dp));
    dp[0] = 0;
    while(N--)
    {
        int now(0);
        for(int i=0; i<K; ++i)
        {
            int a;
            cin >> a;
            a = pow(2,a-1);
            now = (now | a);
        }
        for(int j=0; j<MAXM; ++j)
            dp[(j | now)] = min(dp[(j | now)], dp[j] + 1);
    }
    cout << dp[MAXM-1] << endl;
    return 0;
}

转载于:https://www.cnblogs.com/MMMMMMing/p/10611950.html

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值