Codeforces Round 961 (Div. 2)

ABC没什么,除了B2还没补。

主要就是这个D题。

这题我基本上需要想到的都想到了,没想到的部分就是记录不合法答案而非直接记录正确答案。
其实这思路也有点能够被启发的地方,就是只有某个位置前k个位置是又至少一个数字存在与我们的选择集合里的,我们才能够统计答案。也就是说,如果统计的是不合法的答案有哪些,我们只需要关注这个位置\(i\)的前\(k\)个位置,\([i-k,i]\)这个区间内的字符,这个就是滑动窗口,直接计算就可以了。但是如果是求合法答案,则需要关注的是整个区间,包括\([1,i]\)中间的所有字符都需要被考虑。
而除此之外,还有更重要的就是,合法的答案集合,它的子集不一定合法,而不合法的答案的子集则是一定不合法。
事实上很容易发现,一个答案集合不合法,也只能是因为这个答案集合的选择会导致在原本的字符串上,有连续的\(k\)个字符的位置没有出现答案在集合中的字符,这是它唯一不合法的情况。而全集,除去不合法的情况,剩下的就一定都是合法的情况了。

听起来是非常简单的思路,但是其实这种反向的思路,如果不是刻意去想真的是想不到。。。反正我是这样的。
这题的特点就在于,不合法的集合,它的子集也是不合法的,而全集的大小十分的有限,我们统计合法情况和不合法的情况的结果是一样的。这个不合法的集合的包含性是非常重要的,而合法集合无法快速统计的一个原因就是正确集合之间并没有什么关系。

我感觉,当这个题目的集合总数被我发现不是特别大的时候,我就应该出现了统计不合法集合有哪些的思路了。而不合法的集合和合法集合完全互补就是反向的做法的肯定。而状压的题目则都是这样,只不过这题这个思路十分突出的原因就在于这个状压的是答案集合。而答案集合又只有两种状态,一种合法状态,一种不合法状态。对于比较常见的状压dp,状压的集合虽然不会大,但是一个集合并不是简单的合法和不合法的区分,这个状压的更多的是作为状态而非答案。

这题想要做出来最重要的还是想到统计不合法的集合,这个不合法的集合所拥有的性质比合法集合优秀了太多太多。
不过我做题的时候其实尝试过从结尾处的字符必选的情况思考,从而发现了其实合法的答案非常的没有顺序。从此其实能够发现统计合法答案不是一个特别好的思路,但是我没有想到这一层。

#include<bits/stdc++.h>
#define ll long long
using namespace std;
inline int read(){
	int a=0,b=1;char c=getchar();
	for(;c<'0'||c>'9';c=getchar())if(c=='-')b=-1;
	for(;c>='0'&&c<='9';c=getchar())a=a*10+c-'0';
	return a*b;
}
int n,k,c,cnt[19],f[(1<<19)],s[(1<<19)];
inline int Count(int x)
{
	int sum=0;
	for(int i=0;i<c;i++)
	{
		if((x>>i)&1)sum++;
	}
	return sum;
}
int main()
{
//	cout<<Count(2)<<endl;
	int T=read();
	while(T--)
	{
		n=read(),c=read(),k=read();
		for(int i=0;i<(1<<c);i++)
			f[i]=0;
		for(int i=0;i<=c;i++)cnt[i]=0;
		int now=0;
		for(int i=1;i<=k;i++)
		{
			char x;cin>>x;
			int a=x-'A';
			s[i]=a;
			cnt[a]++;
			if(cnt[a]==1)now+=(1<<a);
		}
		f[(1<<c)-1-now]=1;
		for(int i=k+1;i<=n;i++)
		{
			int last=i-k;
			cnt[s[last]]--;
			if(cnt[s[last]]==0)now-=(1<<s[last]);
			char x;
			cin>>x;
			s[i]=x-'A';
			cnt[s[i]]++;
			if(cnt[s[i]]==1)now+=(1<<s[i]);
			f[(1<<c)-1-now]=1;
		}
		f[(1<<c)-1-(1<<s[n])]=1;
		for(int j=(1<<c)-1;j>=0;j--)
		{
			if(f[j]==1)continue;
			for(int i=0;i<c;i++)
			{
				if((j>>i&1)==0)f[j]|=f[j+(1<<i)];
			}
		}
		int Min=0x3f3f3f3f;
		for(int i=0;i<(1<<c);i++)
		{
			if(f[i]==0)
			{
//				cout<<i<<' ';
				Min=min(Min,Count(i));
			}
		}
		cout<<Min<<endl;
	}
	return 0;
}

写题的过程中能够很明显的发现,其实合法答案的分布并没有很明显的规律,而相比之下不合法答案的分布规律有点太明显了。

最终得到的其实就是对于这种状压,假如这个状压的是和答案相关的集合,可以被分为两个互为补集的答案部分,可以两边都试试。。
好蠢的结论。

posted @ 2024-07-25 10:23  Tracer_w  阅读(46)  评论(0)    收藏  举报