题意:有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;
}