CF1995D-状态压缩

CF1995D-状态压缩

大致题意

你是一名语言学家,正在研究一种神秘的古代语言。你知道

  1. 它的单词只由拉丁字母的前 c 个字母组成。
  2. 每个单词都有一个大小写,可以通过其最后一个字母明确地确定(不同的字母对应不同的大小写)。例如,单词 "ABACABA "和 "ABA"(如果存在的话)在该语言中具有相同的大小写,因为它们都有相同的词尾 "A",而 "ALICE "和 "BOB "则有不同的大小写。如果语言中没有与某个字母相对应的大小写,则表示该单词不能以该字母结尾。
  3. 每个单词的长度为 k 或更少。

您有一个用这种语言写成的文本。不幸的是,由于这种语言非常古老,单词之间没有空格,所有字母都是大写。您想知道这种语言的最少大小写个数是多少。您能找出答案吗?

说人话就是我们将字符串分成多个集合,每个集合的长度可以为\([1,k]\),我们将每个集合的最后一个字符作为标识符,分出的集合标识符总个数越少越好(相同标识符记为同一个)

解题思路

我们考虑对于一个长度为k的集合,其中至少有一个字符作为标识符那么该集合就为合法的集合,那么我们就可以这样考虑,只要我们枚举每一种分割集合的方法,将每个合法的集合算出来求每种方法分割可以得到的最少标识符个数就可以得到答案。

但是对于这种方法复杂度过高,所以我们反过来想去找非法的集合。

我们可以通过状态压缩枚举长度为k的子串中出现过的字符,我们将其命名为集合\(S\),该集合一定为合法集合,那么对于集合\(S\),其补集\(C_uS\)以及该补集的子集一定为非法集合,我们剔除所有的非法集合之后我们就能得到所有的合法集合(此时的合法集合和我们的集合\(S\)并不相同,其结果为每种分割方法所能得到的合法集合)

代码实现

#include<bits/stdc++.h>
using namespace std;
#define int long long
#define endl "\n"
void solve()
{
	int n,c,k;
	cin>>n>>c>>k;
	vector<int> s((1<<c)+1),pos(c+1,-1);
	string st;
	cin>>st;
	for(int i = 0;i < k-1;++i)
	{
		pos[st[i]-'A'] = i;
	}
	for(int i = k-1;i<n;++i)
	{
		pos[st[i]-'A'] = i;
		int l = i-k+1,r = i;
		int mask = 0;
		for(int j = 0;j < c;++j)
		{
			if(pos[j]>=l&&pos[j]<=r)
			{
				mask |= (1<<j);
			}
		}
		s[mask] = 1;
	}

	s[1<<(st[n-1]-'A')] = 1;
	vector<int> bad((1ll<<c)+1);
	for(int i = 0;i < (1ll<<c);++i)
	{
		bad[i^((1ll<<c)-1)] = s[i];
	}
	for(int i = (1ll<<c)-1;i>=0;--i)
	{
		if(bad[i])
		{
			for(int j = 0;j < c;++j)
			{
				if(i&(1ll<<j))
				{
					bad[i-(1ll<<j)] = 1;
				}
			}
		}
	} 
	int ans = c;
	for(int i = 1;i < (1ll<<c);++i)
	{
		if(!bad[i])
		{
			int res = 0;
			int t = i;
			while(t)
			{
				res += (t&1);
				t>>=1; 
			}
			ans = min(ans,res);
		}
	
	}
	cout<<ans<<endl;
}
signed main()
{
	ios::sync_with_stdio(false);
	cin.tie(0);
	int tt;
	cin>>tt;
	while(tt--)solve();
	return 0;
}
posted @ 2024-08-04 11:45  empty_y  阅读(32)  评论(0)    收藏  举报