bzoj3900 交换茸角

3900: 交换茸角

Time Limit: 20 Sec  Memory Limit: 512 MB
Submit: 259  Solved: 119
[Submit][Status][Discuss]

Description

动物园里有 n 头麋鹿。每头麋鹿有两支茸角,每支茸角有一个重量。然而,一旦某头麋鹿上
两支茸角的重量之差过大,这头麋鹿就会失去平衡摔倒。为了不然这种悲剧发生,动物园院长决
定交换某些茸角,使得任意一头麋鹿的两角重量差不超过 c。然而,交换两支茸角十分麻烦,不
仅因为茸角需要多个人来搬运,而且会给麋鹿造成痛苦。因此,你需要计算出最少交换次数,使
得任意一头麋鹿的两角重量差不超过 c。
注意,交换两支茸角只能在两头麋鹿之间进行。因为交换同一头麋鹿的两支角是没有意义的。

Input

第一行为整数 n,c。接下来 n 行,每行两个整数,分别表示一开始每头麋鹿的两角重量。

Output

一个数,即最少交换次数。如果无论如何也不能使每头麋鹿平衡,输出 -1。
 

Sample Input

3 0
3 3
2 5
2 5

Sample Output

1

HINT

对于 100% 的数据,n <= 16, c <= 1000000, 每支茸角重量不超过 1000000。

Source

By 佚名提供

分析:这道题和bzoj2064有异曲同工之妙.

   它们有什么共同的特征呢?求最小答案,n一般非常小,能够状压的级别,爆搜是搜不出来的.

   有什么通用的解法吗?先分析出理论上界,然后利用状压dp减去不必要的操作,就是答案了.

   对于这道题:先要弄清楚在最糟糕的情况下需要操作的次数.将所有的角放在一起排序,然后按照顺序分配给1到n号鹿,这其实就是一个交换的过程,需要n-1次操作,每一次匹配一对鹿角.

   哪些是不必要的操作?对于所有鹿组成的集合的一个子集,如果这个子集中的鹿经过排序后可以分配(任意两项相邻的差值不超过c),那么操作次数就能够减少这个子集大小次.为什么呢?可以这么来理解:对于一个大小为S且经过排序后可以分配的子集,如果在其内部交换,则需要S-1次操作才能匹配,如果按照之前的方法一个一个分配,则需要S次操作,那么每个子集就减少了1次操作.

   现在的任务就是求能将一个集合分成多少个不交叉的满足要求的子集.枚举子集进行状压dp呗.  f[i] = max{f[j] + f[i ^ j]}.

   还有一个疑问:这道题为什么不能像bzoj2064那样枚举一个元素进行转移呢? 因为bzoj2064转移是看总体的和是否相等,也就是看总体上是否符合条件,而这道题两个合并的子集f[j],f[i^j]都必须要满足要求.而且如果每次加进来一个元素,它是可以和其它的子集拼在一起的,而转移是考虑不到这一点的,所以不能只枚举一个集合中出现的元素来进行转移.

   这种看上去毫无头绪,规模可以状压,要求最小操作数的题目就可以用上述的方法来解决.

#include <cstdio>
#include <cstring>
#include <iostream>
#include <algorithm>

using namespace std;

const int maxn = 20;
int n,c,f[1 << 17],maxx,a[40],tot;

struct node
{
    int x,y;
} e[maxn];

int calc(int S)
{
    tot = 0;
    for (int i = 1; i <= n; i++)
        if (S & (1 << (i - 1)))
            a[++tot] = e[i].x,a[++tot] = e[i].y;
    sort(a + 1,a + 1 + tot);
    for (int i = 1; i < tot; i += 2)
        if (a[i + 1] - a[i] > c)
            return -1;
    return 1;
}

int main()
{
    scanf("%d%d",&n,&c);
    for (int i = 1; i <= n; i++)
        scanf("%d%d",&e[i].x,&e[i].y);
    maxx = (1 << n) - 1;
    f[maxx] = calc(maxx);
    if (f[maxx] == -1)
        puts("-1");
    else
    {
        for (int i = 1; i < maxx; i++)
            f[i] = calc(i);
        for (int i = 1; i <= maxx; i++)
            if (f[i] != -1)
            {
                for (int j = i & (i - 1);j;j = (j - 1) & i)
                    if (f[j] != -1 && f[i ^ j] != -1)
                        f[i] = max(f[i],f[j] + f[i ^ j]);
            }
        printf("%d\n",n - f[maxx]);
    }

    return 0;
}

 

posted @ 2018-03-02 23:52  zbtrs  阅读(260)  评论(0编辑  收藏  举报