Codeforces 367D Sereja and Sets 题解&代码

题意:有1-n共n个正整数被分成了m个非空集合(m个集合的并是n个正整数,交总是空集),从m个集合中选最少的集合数,使得对于给定的d,选出的集合中的所有数从小到大排列后满足对于[1, n]这个区间中任意长度为d的连续子区间都至少有一个数在选出的集合中 (1<=d<=n<=100000,1<=m<=20)

这个题意是看网上的转化= =原题意比这个抽象很多,这样转化之后整个问题都简化了
思路:
如果要对于每个区间[i,i+d-1](长度为d的子区间)都存在有一组集合使得集合组中至少存在一个元素存在于区间中;那么如果对于某个区间,某个集合组的数全部不存在于该区间,这个集合组以及这个集合组的子集一定都不满足条件。
然后二进制枚举集合组,同时向下递推false集合组的子集
对于false数组没有标记的所有情况,统计集合组中包含集合的个数,找到包含集合数最少的值。

#include<iostream>
#include<stdio.h>
using namespace std;
const int maxn=100005;
int n,m,d,cnt,num,ans,sta,a[1<<21],k[maxn],fal[1<<21];
int main(void)
{
    scanf("%d%d%d",&n,&m,&d);
    for(int i=0;i<m;i++)
    {
        scanf("%d",&cnt);
        while(cnt--)
        {
            scanf("%d",&num);
            k[num]=i;
        }
    }
    ans=m+1;
    for(int i=1;i<d;i++)
        a[k[i]]++;
    for(int i=d;i<=n;i++)
    {
        if(i>d)a[k[i-d]]--;
        a[k[i]]++;sta=0;
        for(int j=0;j<m;j++)
            if(!a[j])sta|=(1<<j);
        fal[sta]=1;
    }
    for(int i=(1<<m)-1;i>=0;i--)
    {
        if(fal[i])
        {
            for(int j=0;j<m;j++)
            if(i & (1<<j))fal[i^(1<<j)]=1;
        }
        else
        {
            cnt=0;
            for(int j=0;j<m;j++)
                if(i & (1<<j))cnt++;
            ans=min(ans,cnt);
        }
    }
    printf("%d\n",ans);
    return 0;
}

你可能感兴趣的:(codeforces)