【状态压缩】【动态规划】坑爹题

【问题描述】
呵呵,欧教又出了一套题,准备用来考察13 级众基友的OI 水平:“代码都是NOIP 难度,多琢磨一下。”
虽然明知要被坑,但是众基友还是决定齐心合力来解决这套坑爹题。
这套题一共有n 道,而停课的基友却只有8 个。
对于一道题,它会完全占用某基友[a,b]区间的时间。当然,不是所有的基友会做这些题,比如小兽做BFS 不加边框,小蛋基本不会做题……
作为基房的女王,神母牛小希想要知道是否存在一种方案使得众基友能否完成这套题——若存在则回答“yes”,反之则回答“hehe”。
【输入数据】
首先一个整数sum,表示一共有sum 组测试数据。
对于每一组数据:
第一行一个整数n,表示一共有n 道题。
接下来n 行每行若干个整数形如a,b,m,h1,h2,…,hm。第i 行表示工作i 会占用[a,b]的时间且只有m 个基友会做,他们是h1,h2,…,hm。
【输出数据】
一共sum 行,每行为“yes”或“hehe”。
【样例输入】
2
2
2 2 2 1 3
2 2 2 1 3
2
2 2 2 1 3
2 2 2 1 3
【样例输出】
yes
yes
【数据范围及约定】
1≤sum≤10,0≤m≤8,1≤n≤12。
所有输入数据均在longint 范围内。
此题考察状态压缩的应用。
普通的状压显然行不通,因为有一个矛盾不能解决——一个人在不同时间段可能完成不同的任务。
那么一种思想就是将人和任务的关系倒过来,让人去选任务,而不是让任务去选人。
Accode:

#include <cstdio>
#include <cstdlib>
#include <algorithm>
#include <string>

const int maxN = 20;
struct Seg {int L, R, sta;} seg[maxN];
bool f[2][1 << 13];
int L[maxN], R[maxN], can[maxN];
int cnt[2], status[2][1 << 13];
int n, m, pst, ths;

inline int getint()
{
    int res = 0; char tmp;
    while (!isdigit(tmp = getchar()));
    do res = (res << 3) + (res << 1) + tmp - '0';
    while (isdigit(tmp = getchar()));
    return res;
}

void Dfs(int can, int S, int i, int R)
//can为这个人能做的任务,S为已经做过的任务,
//i为待枚举的任务,R为上一个任务的右界(保证不能冲突)。
{
    if (i >= m)
    {
        if (!f[ths][S])
            f[ths][status[ths][cnt[ths]++] = S] = 1;
        return;
    }
    Dfs(can, S, i + 1, R);
//这里一定不能把之前的人做的任务的时间
//冲突也一并算进去,否则后果很严重。
    if ((can & (1 << i)) &&
        !(S & (1 << i))
        && seg[i].L > R)
        Dfs(can, S | (1 << i), i + 1, seg[i].R);
//零壹枚举所有无冲突的任务。
    return;
}

int cmp(const void *a, const void *b)
{
    if (((Seg *)a) -> R - ((Seg *)b) -> R)
        return ((Seg *)a) -> R - ((Seg *)b) -> R;
    return ((Seg *)a) -> L - ((Seg *)b) -> L;
}
//按照区间的右界排序(左界无所谓),
//方便后面判断冲突。

int main()
{
    freopen("problem.in", "r", stdin);
    freopen("problem.out", "w", stdout);
    for (int T = getint(); T; --T)
    {
        memset(f, 0, sizeof f);
        memset(can, 0, sizeof can);
	//多组数据注意清零。
        m = getint(); n = 0;
        for (int i = 0; i < m; ++i)
        {
            seg[i].L = getint();
            seg[i].R = getint();
            seg[i].sta = 0;
            for (int cnt = getint(); cnt; --cnt)
            {
                int tmp = getint() - 1;
                seg[i].sta |= 1 << tmp;
                n = std::max(m, tmp + 1);
            }
        }
        qsort(seg, m, sizeof seg[0], cmp);
        for (int i = 0; i < m; ++i)
        for (int j = 0; j < n; ++j)
        if (seg[i].sta & (1 << j))
            can[j] |= 1 << i;
        pst = 0, ths = 1;
        status[ths][(cnt[ths] = 0)++] = 0;
        f[ths][0] = 1;
        for (int i = 0; i < n; ++i)
        {
            std::swap(pst, ths); cnt[ths] = 0;
            memset(f[ths], 0, sizeof f[ths]);
	//此数组一定要清零,相当于判断状态是否相同的Hash表。
            for (int k = 0; k < cnt[pst]; ++k)
                Dfs(can[i], status[pst][k], 0, 0);
            if (f[ths][(1 << m) - 1]) break;
        }
        printf("%s\n", f[ths][(1 << m) - 1]
               ? "yes" : "hehe");
    }
    return 0;
}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值