前言
通过钻研 ( t color{black}jcolor{red}{iangly}) 的代码,这篇博客诞生了。
至于怎么想到这个做法的呢?那你得去请教 ( t color{black}jcolor{red}{iangly}) 了。
题目
题目大意:
有 (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) 出来的做法。