【Codeforces】CF367D Sereja and Sets (数学)

题目大意

  1到n这n个正整数被分成了m个不相交的集合(集合不一定连续),现在从这m个集合中选出最少个数的集合,满足对于[1,n]中任意一个长度为d的区间都至少有一个数字出现在已选集合中。(1 <= d <= n <= 100000, 1 <= m <= 20)

输入输出样例
输入样例1
  3 2 2
  1 2
  2 1 3
输出样例1
  1
输入样例2
  5 1 1
  5 4 5 3 2 1
输出样例2
  1
输入样例3
  7 3 1
  4 1 3 5 7
  2 2 6
  1 4
输出样例3
  3

分析

  本来想贪心做的,手玩半天样例玩不出答案而且还不知道怎么写,最后选择了屈服

  考虑一个长度为d的区间如果符合条件,那么这个区间内包含的数的所属集合中任选一个,这个区间就是合法的,

  暂时称这些集合为每段区间的合法集合,这些集合可以组成一个集合组(集合的集合,姑且叫它合法集合组

  题目转化为选出数量最少集合组成一个集合组,这个集合组满足与所有的区间的合法集合组都有交集(交集的元素是若干个集合

  直接求答案不好求,我们反过来试试

  对于每个区间的合法集合组,它的补集及其子集一定是不合法的,把这些集合组都去掉,剩下的集合组就是合法的了

  然后枚举找出最小的集合组即可

  具体实现的话,从大到小枚举集合组,枚举到一个不合法的集合组就把它的子集都打上标记,枚举到合法的就更新答案

  Code

#include<cstdio>
#include<algorithm>
using namespace std;
int n,m,d,I,ans,ori[100005],mp[1050000],vis[25];
int main()
{
    scanf("%d%d%d",&n,&m,&d);I=(1<<m)-1;ans=m;
    for(int i=1,a,k;i<=m;i++){scanf("%d",&k);while(k--)scanf("%d",&a),ori[a]=i;}
    for(int i=1;i<=d;i++)vis[ori[i]]++;
    for(int l=1,r=d;r<=n;vis[ori[l]]--,l++,r++,vis[ori[r]]++)
    {int st=0;for(int i=1;i<=m;i++)if(vis[i])st|=1<<(i-1);mp[I-st]=1;}
    for(int st=I;st>=0;st--)
    if(mp[st]){for(int j=1;j<=m;j++)if(st&(1<<(j-1)))mp[st^(1<<(j-1))]=1;}
    else ans=min(__builtin_popcount(st),ans);
    printf("%d\n",ans);
}

 

posted @ 2019-10-28 19:34  散樗  阅读(236)  评论(0编辑  收藏  举报