在分析题目前,一定要完全读懂题目意思,否则一些都是白搭。这道理谁都懂,但是能每次都做到的人不多。比如本人,这次又粗心地在这里疯狂地踩坑了。

(1)本题有这么一句话:But Gena's friends won't agree to help Gena for nothing:
the 
i-th
friend asks Gena 
xi rubles
for his help 
in solving all the problems he
can当时就没看清,然后,根据自己的一知半解去套样例,结果刚好符合我的理解(这也从侧面说明了一点,样例通常都是误导粗心大意的、naive的人):每个朋友,AC一题,交一次钱,如果当前已经买了的显示器足够这个朋友需要的,不用再额外支付显示器的钱,否则,还差多少个显示器,就要再付多少个显示器的钱。于是乎,就有了下面这份代码:

#include<iostream>
#include<cstdio>
#include<cstring>
#include<algorithm>
#include<set>
#include<vector>
using namespace std;

typedef __int64 I64;
I64 n, m, b;
typedef struct {
    I64 x, k, m;
    set<I64> pl;
} Friend;
set<I64> st;
Friend fds[105];
I64 dp[22][105];

void init() {
    st.clear();
    for(I64 i = 0;i < 105;i++){
        fds[i].x = 0, fds[i].k = 0, fds[i].m = 0;
        fds[i].pl.clear();
    }
}

void solve() {
    memset(dp, 0x5f, sizeof(dp));
    I64 t;
    for(I64 i = 1; i <= n; i++)dp[0][i] = 0;
    for(I64 i = 1; i <= n; i++){
        if(!fds[i].pl.count(1))continue;
        dp[1][i] = fds[i].x + fds[i].k * b;
        //printf("dp[1][%I64d] = %I64d\n", i, dp[1][i]);
    }
    for(I64 p = 2; p <= m; p++) {
        for(I64 j = 1; j <= n; j++) {
            if(!fds[j].pl.count(p))continue;
            for(I64 r = 1; r <= n; r++) {
                //if(!fds[r].pl.count(p))continue;
                if(dp[p - 1][r] == 0x5f5f5f5f5f5f5f5f)continue;
                t = (fds[j].k > fds[r].k ? (fds[j].k - fds[r].k) * b : 0);
                dp[p][j] = min(dp[p][j], dp[p - 1][r] + fds[j].x + t);
                //printf("dp[%I64d][%I64d] = %I64d\n", p, j, dp[p][j]);
            }
        }
    }
    I64 res = dp[m][1];
    for(I64 i = 1;i <= n;i++)res = min(res, dp[m][i]);
    printf("%I64d\n", res);
}

int main() {
#ifndef ONLINE_JUDGE
     freopen("Einput.txt", "r", stdin);
#endif // ONLINE_JUDGE
    I64 p;
    while(~scanf("%I64d%I64d%I64d", &n, &m, &b)) {
        init();
        for(I64 i = 1; i <= n; i++) {
            scanf("%I64d%I64d%I64d", &fds[i].x, &fds[i].k, &fds[i].m);
            for(I64 j = 0; j < fds[i].m;j++) {
                scanf("%I64d", &p);
                fds[i].pl.insert(p);
                st.insert(p);
            }
        }
        if(st.size() < m)cout << -1 << endl;
        else solve();
    }
    return 0;
}

简要解释下:dp[p][j]表示解决前p题,需要数量最多的显示器的朋友是第i个,所需花费的最小代价。

于是,连续修修补补、添添改改交了N发,WA了N发。直到测试结束也没有A出来。

(2)然而,本题的真正原意是:对于每个朋友,如果他需要的显示器足够,那么,只要支付一次他需要的AC题目的费用,他就会把他会做的题目全部做掉。当然,AC了的题目再做一遍也是可以的。所以,本题应该要用状态压缩DP来做。

dp[s]表示当做了题的状态为s时,所需花费的最小代价。然后,不难想到递推式:dp[s | 第i个朋友会做的题号使用二进制法编码成的整数] = min(dp[s
| 第i个朋友会做的题号使用二进制法编码成的整数],  dp[s] + xi)。咦?不是还要付显示器的钱吗?怎么搞?dp数组弄成结构体,成员为v和moni。dp[s].moni记录做了题的状态为s时,最多买了多少个显示器。

(3)还有一点就是,对状态的循环要放在对朋友的循环的里面,这个也是我没想明白的地方。暂时先搁着吧。再过段时间水平更高了,自然就明白了。

然后,就有了如下代码:

#include<iostream>
#include<cstdio>
#include<cstring>
#include<algorithm>
#include<set>
using namespace std;

typedef __int64 I64;
const I64 INF = ((I64)1) << 62;
I64 n, m, b;
typedef struct {
    I64 x, k, m;
    set<I64> pl;
    I64 ac;
} Friend;
set<I64> st;
Friend fds[105];
typedef struct {
    I64 v, moni;
} DP;
DP dp[1050000];

void init() {
    st.clear();
    for(I64 i = 0; i < 105; i++) {
        fds[i].x = 0, fds[i].k = 0, fds[i].m = 0;
        fds[i].pl.clear();
    }
}

void solve() {
    int ak = (1 << m) - 1;
    I64 t;
    for(int i = 0; i < (1 << 20); ++i) {
        dp[i].v = INF;
        dp[i].moni = 0;
    }
    dp[0].v = 0, dp[0].moni = 0;
    for(int i = 1; i <= n; i++) {
        for(int s = 0; s <= ak; ++s) {
            if((s & fds[i].ac) == fds[i].ac)continue;
            t = (fds[i].k > dp[s].moni ? fds[i].k - dp[s].moni : 0);
            if(dp[s | fds[i].ac].v > dp[s].v + fds[i].x + t * b) {
                dp[s | fds[i].ac].v = dp[s].v + fds[i].x + t * b;
                dp[s | fds[i].ac].moni = dp[s].moni + t;
            }
        }
    }
    printf("%I64d\n", dp[ak].v);
}

int main() {
#ifndef ONLINE_JUDGE
    freopen("Einput.txt", "r", stdin);
#endif // ONLINE_JUDGE
    I64 p;
    while(~scanf("%I64d%I64d%I64d", &n, &m, &b)) {
        init();
        for(I64 i = 1; i <= n; i++) {
            scanf("%I64d%I64d%I64d", &fds[i].x, &fds[i].k, &fds[i].m);
            for(I64 j = 0; j < fds[i].m; j++) {
                scanf("%I64d", &p);
                fds[i].pl.insert(p);
                st.insert(p);
            }
        }
        for(I64 i = 1; i <= n; ++i) {
            I64 t = 0;
            for(set<I64>::iterator it = fds[i].pl.begin(); it != fds[i].pl.end(); ++it)
                t |= (((I64)1) << (*it - 1));
            fds[i].ac = t;
        }
        if(st.size() < m)cout << -1 << endl;
        else solve();
    }
    return 0;
}

然后,交上去在第24个测试样例挂了,结果如下所示。

Test: #24, time: 0 ms., memory: 18488 KB, exit code: 0, checker exit code: 1, verdict: WRONG_ANSWER
Input
59 11 830067068
735007990 107101946 6
1 2 4 5 6 10
603600908 880756312 8
1 2 4 7 8 9 10 11
659716960 174388278 5
2 5 7 9 10
899506226 611183471 8
2 4 5 7 8 9 10 11
571272365 68726728 6
4 5 7 8 10 11
330538629 560221018 6
3 4 5 8 10 11
686021635 919974878 6
1 4 5 6 8 9
2687375 998065071 7
1 3 4 5 8 9 11
141387697 606296659 5
2 3 4 7 9
287220643 466057605 6
2 4 6 7 8 11
822768448 695179109 4
2 3 4 10
696850261 919174199 7
1 4 6 7 8 9 10
71824861 156690160 6
1 3 6 7 10 11
875086545 ...
Output
83528377136277204
Answer
83528376862632587
Checker Log
wrong answer expected 83528376862632587, found 83528377136277204

对比一下可以发现,错误的结果前7个数字还是对的上的,就是后面的对不上了。错的我不明不白。想不明白。

然后,到网上看了别人的题解后,理解了下,普遍的做法是:dp还是简单数据类型数组,只是在dp到达目标状态时,使用min函数更新我们需要的答案,最大需要的显示器数量为当前朋友需要的显示器数量。还有就是,都说要对Friend结构体按需要的显示器个数k自然排序,我就想:为什么要排序呢?每种状态不是都能遍历到吗,为何还要排下序?因为,如果不排序,按照这种写法,就会出现这么一种情况:

2 1 b
1 3 1
1
2 2 1
1

对于该测试样例,得到这么一个结果,ans = 1 + 2 * b。即选第一个的费用x,然后由于题目已经刷完,达到了目标状态,对ans更新,ans = min(1 + 3 * b, 1 + 2 * b)。即用前面做题花的费用再加上当前朋友所需要的显示器*单个显示器价格,即:1 + 2 * b。显然,3 * b < 2 * b,所以,ans被更新为1 + 2 * b。然而,这种情况实际上是不合法的。所以,我们需要把这种情况干掉,就是通过排序。

(4)接下来,就是第4个坑了。排序的时候,因为是按照朋友所需要的显示器的数量从小大自然排序,于是我写了个这样的比较函数:

bool cmp(Friend f1, Friend f2) {
    return f1.k <= f2.k;
}

样例一跑,发现都没错,果断交一发,嘣,我擦,RE。点开Codeforces提供的错误样例,发现输入数据全是最上界的值,然后我开始找数据越界的地方,找了半天还是没找到,最终一点一点注释,定位到了sort函数这里。按理来说,sort函数限定了上下界指针,在里面自由活动应该不会出问题的。然后我把cmp函数里面的“=”去掉,跑一下,竟然没事了。真坑。。。。。。这个问题,我也没想明白,为什么会这样。

于是最终的AC代码就是这样了。

#include<iostream>
#include<cstdio>
#include<cstring>
#include<algorithm>
#include<set>
using namespace std;

typedef __int64 I64;
const I64 INF = 1LL << 62;
I64 n, m, b;
typedef struct {
    I64 x, k, m, ac;
} Friend;
set<I64> st;
Friend fds[105];
I64 dp[1050000];

bool cmp(Friend f1, Friend f2) {
    return f1.k < f2.k;
}

void solve() {
    I64 ak = (I64)(1 << m) - 1;
    I64 t, ans = INF;
    sort(fds + 1, fds + n + 1, cmp);
    for(int i = 0; i < (1 << 20); i++)dp[i] = INF;
    dp[0] = 0;
    for(I64 i = 1; i <= n; i++) {
        for(I64 s = 0; s <= ak; ++s) {
            if((s & fds[i].ac) == fds[i].ac)continue;
            dp[s | fds[i].ac] = min(dp[s | fds[i].ac], dp[s] + fds[i].x);
        }
        if(dp[ak] != INF)ans = min(ans, dp[ak] + fds[i].k * b);
    }
    printf("%I64d\n", ans == INF ? -1 : ans);
}

int main() {
#ifndef ONLINE_JUDGE
    freopen("Einput.txt", "r", stdin);
#endif // ONLINE_JUDGE
    I64 p, t;
    while(~scanf("%I64d%I64d%I64d", &n, &m, &b)) {
        st.clear();
        for(I64 i = 1; i <= n; i++) {
            scanf("%I64d%I64d%I64d", &fds[i].x, &fds[i].k, &fds[i].m);
            t = 0;
            for(I64 j = 0; j < fds[i].m; j++) {
                scanf("%I64d", &p);
                st.insert(p);
                t |= 1LL << (p - 1);
            }
            fds[i].ac = t;
        }
        if(st.size() < m)cout << -1 << endl;
        else solve();
    }
    return 0;
}

(5)最后再做一次总结:

  • 看题一定要仔细,否则做再多也是白搭。这个道理很多地方都是相通的,如果不理解人家,没搞明白人家的意思,做再多又有什么用?谁会买你的苦劳?别人只看结果的。
  • 使用sort函数时,需要注意cmp函数的写法。一定不能再吃这样的哑亏了。
  • 对于不明白的,不必急着刻意去想懂它,先搁一搁,平时走路、洗澡的时候可以想想,有了一段时间就能彻底明白了。现在还不懂就记住这些坑,下次写代码的时候注意好。明白了以后就自然不会再放错了。

Codeforces-417D总结&题解的更多相关文章

  1. Codeforces 417D Cunning Gena(状态压缩dp)

    题目链接:Codeforces 417D Cunning Gena 题目大意:n个小伙伴.m道题目,每一个监视器b花费,给出n个小伙伴的佣金,所须要的监视器数,以及能够完毕的题目序号. 注意,这里仅仅 ...

  2. Codeforces Round #556 题解

    Codeforces Round #556 题解 Div.2 A Stock Arbitraging 傻逼题 Div.2 B Tiling Challenge 傻逼题 Div.1 A Prefix S ...

  3. Cunning Gena CodeForces - 417D

    Cunning Gena CodeForces - 417D 题意 先将小伙伴按需要的监视器数量排序.然后ans[i][j]表示前i个小伙伴完成j集合内题目所需最少钱.那么按顺序枚举小伙伴,用ans[ ...

  4. Codeforces Round #569 题解

    Codeforces Round #569 题解 CF1179A Valeriy and Deque 有一个双端队列,每次取队首两个值,将较小值移动到队尾,较大值位置不变.多组询问求第\(m\)次操作 ...

  5. Codeforces Round #557 题解【更完了】

    Codeforces Round #557 题解 掉分快乐 CF1161A Hide and Seek Alice和Bob在玩捉♂迷♂藏,有\(n\)个格子,Bob会检查\(k\)次,第\(i\)次检 ...

  6. CFEducational Codeforces Round 66题解报告

    CFEducational Codeforces Round 66题解报告 感觉丧失了唯一一次能在CF上超过wqy的机会QAQ A 不管 B 不能直接累计乘法打\(tag\),要直接跳 C 考虑二分第 ...

  7. codeforces CF475 ABC 题解

    Bayan 2015 Contest Warm Up http://codeforces.com/contest/475 A - Bayan Bus B - Strongly Connected Ci ...

  8. Codeforces Round #542 题解

    Codeforces Round #542 abstract I决策中的独立性, II联通块染色板子 IIIVoronoi diagram O(N^2 logN) VI环上距离分类讨论加取模,最值中的 ...

  9. 【codeforces 417D】Cunning Gena

    [题目链接]:http://codeforces.com/problemset/problem/417/D [题意] 有n个人共同完成m个任务; 每个人有可以完成的任务集(不一定所有任务都能完成); ...

  10. Codeforces Choosing Laptop 题解

    这题实在是太水了,具体看注释 蒟蒻的方法是一边找过时的电脑一边比大小 蒟蒻不才,只会C++ 其实还会free basic,但它已经过时了 附: 本题洛谷网址 Codeforces网址 希望蒟蒻的题解能 ...

随机推荐

  1. Ubuntu12.04 中文输入法设置

    1.ibus输入法 Ubuntu系统安装后已经自带了ibus输入法,在英语环境下默认不启动. 配置ibus自动启动可 以在ubuntu系统菜单上选择System(系统)--- Preferences( ...

  2. linux:系统启动流程

    系统启动流程 本文基于CentOS6 版本 黑色部分为主流程分支,蓝色部分为详细流程分支,绿色部分是注释部分 第一步--加载BIOS打开计算机电源,计算机会首先加载BIOS信息,主要负责检测系统外围关 ...

  3. 关于block和inline元素的float

    CSS float 浮动属性 本篇主要介绍float属性:定义元素朝哪个方向浮动. 目录 1. 页面布局方式:介绍文档流.浮动层以及float属性. 2. float:left :介绍float为 l ...

  4. tensorflow 初学习

    tenseroflow 拟合 y = ax*x+b构建神经网络主要分为 4 个步骤:构造数据.构建网络.训练模型.评估及预测模型.此外,还介绍了一些超参数设定的经验和技巧 #coding=utf-8 ...

  5. Qt 4.8.5 jsoncpp lib

    Qt jsoncpp lib 一.参考文档: . QtCreator动态编译jsoncpp完美支持x86和arm平台 http://www.linuxidc.com/Linux/2012-02/536 ...

  6. erl_0020 《面对软件错误构建可靠的分布式系统》读书笔记001 “面向并发COPL”

    在现实世界中,顺序化的(sequential)活动非常罕见.当我们走在大街上的时候,如果只看到一件事情发生的话我们一定会感到不可思议,我们期望碰到许多同时进行的活动. 如果我们不能对同时发生的众多事件 ...

  7. 考研系列 HDU2241之早起看书 三分

    考研并不是说说就可以了,要付诸于行动. 对于Lele来说,最痛苦的事莫过于早起看书了,不过为了考研,也就豁出去了.由于早起看书会对看书效率产生影响,所以对于要什么时候起床看书,还是有必要考虑的. 经过 ...

  8. 《DSP using MATLAB》示例Example 6.26

    代码: % r = 0.9; theta = (pi/180)*[-55:5:-35, 35:5:55]'; p = r*exp(j*theta); a = poly(p); b = 1; % Dir ...

  9. 船长带你看书——《selenium2 python 自动化测试实战》(2)浏览器操作

    浏览器操作 # coding: utf-8 from selenium import webdriver from time import sleep driver = webdriver.Firef ...

  10. C#飞行棋游戏

    using System; using System.Collections.Generic; using System.Linq; using System.Text; using System.T ...