[CF1450G] Communism

前言

通过钻研 ( t color{black}jcolor{red}{iangly}) 的代码,这篇博客诞生了。

至于怎么想到这个做法的呢?那你得去请教 ( t color{black}jcolor{red}{iangly}) 了。

题目

CF

洛谷

题目大意:

(n) 个人,每个人有一份工作,用小写字母字符串 (s) 表示,(s_i) 表示第 (i) 个人的工作。你需要通过如下操作将所有人的工作变为一样的。

  • 给定一个常数 (k=frac{a}{b}),令一种工作 (x) 的下标的集合为 ({i_1,i_2,...,i_m}(i_1<i_2<...<i_m))。如果满足 (k*(i_m-i_1+1)le m) ,那么你可以将所有的工作 (x) 改为任何一种现在存在的工作 (y)

对于每个出现过的工作,询问是否可能最后所有人都是这个工作。

trygub这6个字母不会出现,因为它是撒旦。

(1le nle 5000;1le a,ble 10^5;)

时空限制:( t 1.5s,32MB.)

讲解

从现在起,我将工作称为字母。

(26-6=20),这引导我们向状压的方面去想。

首先需要预处理每个字母出现的左右端点 (l,r) 以及数量 (cnt)

然后考虑 (dp)。令 (dp_s) 表示字母集合为 (s) 是否满足换字母的条件,并称 (s) 可以换字母的情况为万能态。

我们考虑如何转移。

part1

这类情况是 (s) 中的所有字母先全部变成相同的,然后如果 (s) 可以整体变换,那么 (s) 为万能态。

如果存在一个字母 (iin s),并且 (s) 中的其它字母可以换字母,那么 (s) 就可以被转换成同一种字母 (i)

在这个情况下满足题目所给条件 (k*(i_m-i_1+1)le m),那么 (s) 就为万能态。

part2

这类情况是讲 (s)(s) 分割成几个小块,如果每个小块都是万能态,那么 (s) 就是万能态,也就是分小块转换。

但是如果直接暴力做显然复杂度上天,我们想一些性质。

1.包含

如果两种字母 (i,j) 满足 (l_i<l_j<r_j<r_i),那么显然一起换字母更优,也就是说不用考虑分开换的情况。

2.相交

更进一步,如果两种字母 (i,j) 满足 (l_i<l_j<r_i<r_j),一起换也一定不劣于单独换字母。

简单说明一下:

如果分开转换,需要满足两个条件:(k(r_i-l_i+1)le m_i,k(r_j-l_j+1)le m_j)

而如果一起转换,只需要满足这一个条件:(k(r_j-l_i+1)le m_i+m_j),显然这个条件比上面那两个条件更容易满足。

所以一起换也是更优的,这种情况也不用考虑分开换的情况,个人觉得这个点是个难想到的点。

3.相离

但是如果 (l_i<r_i<l_j<r_j),那就说不准了,有可能我们将 (i,j) 字母变为一样了之后并不是万能态,所以需要字母 (i,j) 分别都是万能态。那么我们只需要把 (s) 分割成独立的几块即可,而且由于子集已经处理过,所以其实只需要分割成独立的两块,而根据这种情况的特殊性,我们将 (s) 当中的字母按照左端点或者右端点排序,然后枚举切割点即可。

如果存在一种分割方法,两个小块都是万能态,那么 (s) 就是万能态。

总时间复杂度 (O(|C| imes 2^{|C|}))(C) 为出现的字符的集合。

代码

//12252024832524
#include <cstdio>
#include <cstring>
#include <algorithm>
#define TT template<typename T>
using namespace std;

typedef long long LL;
const int MAXN = 100005;
int n,m,A,B,anstot;
char a[MAXN],ans[25];
int s[MAXN];

LL Read()
{
	LL x = 0,f = 1;char c = getchar();
	while(c > '9' || c < '0'){if(c == '-')f = -1;c = getchar();}
	while(c >= '0' && c <= '9'){x = (x*10) + (c^48);c = getchar();}
	return x * f;
}
TT void Put1(T x)
{
	if(x > 9) Put1(x/10);
	putchar(x%10^48);
}
TT void Put(T x,char c = -1) 
{
	if(x < 0) putchar('-'),x = -x;
	Put1(x); if(c >= 0) putchar(c);
}
TT T Max(T x,T y){return x > y ? x : y;}
TT T Min(T x,T y){return x < y ? x : y;}
TT T Abs(T x){return x < 0 ? -x : x;}

char dic[30];
bool vis[256],dp[1 << 20];
int l[25],r[25],cnt[25],od[25];

bool cmp(int x,int y){return l[x] < l[y];}//按左端点排序

int main()
{
//	freopen(".in","r",stdin);
//	freopen(".out","w",stdout);
	n = Read(); A = Read(); B = Read();
	scanf("%s",a);
	for(int i = 0;i < n;++ i)
		if(!vis[a[i]])
			dic[m++] = a[i],vis[a[i]] = 1;
	sort(dic,dic+m);
	for(int i = 0;i < n;++ i)
		s[i] = lower_bound(dic,dic+m,a[i]) - dic;
	for(int i = 0;i < m;++ i) l[i] = n,od[i] = i;
	for(int i = 0;i < n;++ i) l[s[i]] = Min(l[s[i]],i),r[s[i]] = Max(r[s[i]],i),cnt[s[i]]++;
	sort(od,od+m,cmp);
	dp[0] = 1;
	for(int S = 1;S < (1 << m);++ S)
	{
        	//part1
		int L = n,R = -1,C = 0,zouni = 0;
		for(int i = 0;i < m;++ i)
			if(S >> i & 1) 
				L = Min(l[i],L),R = Max(r[i],R),C += cnt[i],zouni = zouni || dp[S ^ (1 << i)];
		if(zouni && A * (R-L+1) <= C * B) dp[S] = 1;
        	//part2
		int now = 0; 
		for(int i = 0;i < m;++ i) //枚举切割点
		{
			if(S >> od[i] & 1)
			{
				now |= 1 << od[i];
				dp[S] = dp[S] || (dp[S ^ now] && dp[now]);
			}
		}
	}
	for(int i = 0;i < m;++ i)
		if(dp[(1<<m) - 1 - (1<<i)])
			ans[++anstot] = dic[i];
	Put(anstot); for(int i = 1;i <= anstot;++ i) putchar(' '),putchar(ans[i]);
	return 0;
}

后记

如果我讲的有问题,请速速打我的脸,毕竟这是我 ( t YY) 出来的做法。

原文地址:https://www.cnblogs.com/PPLPPL/p/15002786.html