CF938F Erasing Substrings(乱搞)

Akano 发布于 2023-11-02 3 次阅读


思路

这是一篇乱搞题解,不保证能过所有 hack,能过官方数据

关于乱搞的正确性咱会在最后说。不知道乱搞能不能过审,过不了审就放自己博客了()

首先看到字典序想到贪心,我们可以设状态 $f_{i,j}$ 表示目前位置为 $i$,剩下可以删的长度为 $j$ (事实上,这个可删的长度就是可删线段压缩后的状态,二者是等价的)。

我们贪心地尝试下一个字母,从 $a$ 到 $z$ 一次尝试,遍历队列里的每一个状态(咱的实现是 BFS ),枚举所有可能的当前枚举字符的位置,算出这些位置 $pos$ 和 $i$ 的差,查看这个距离是否在二进制集合意义下属于 $j$,即 $(pos-i) \in j$,如果是,则转移到 $f_{pos+1,j - (pos-i)}$。

理论复杂度复杂度 $O(n^4)$,不过实际跑出来的复杂度在极端数据(泉部为 $a$)的情况下也只有 $O(n^3)$,也许是咱哪里算错了吧。发现这个算法在绝大多数情况下根本跑不满,因为你找到了 $ch$ 处有转移之后就直接退出本回合的转移了(贪心嘛),你的字符种类再多,在绝大多数情况下只有最小的字符会起作用。于是我们交上代码,发现 TLE on #7 ,数据就是泉部为 $a$ 的数据。

特判是没有用的,一是“面对数据编程”本质作弊,二是出题人肯定不傻,在其他数据中随机插入了少量其他字符。对每个回合转移出来的状态数打表,发现状态数在达到某一个值之后很快固定为一个值,这是因为我们每个状态的转移本来就很多(每个状态 $n$ 个),再加上在大多数字母为 $a$ 的情况下转移是极容易成立的,于是剪枝,在下一层的状态数固定很多次以后直接退出。经过细微调参通过本题。

乱搞感想(?)及改良

咱认为一个好的乱搞应该是针对所有数据的,而不好的乱搞则是只针对某一种数据。很惭愧咱的乱搞应该算后者,因为它的确是对着数据调出来的。然而我们通过上文的推理,想要把咱的这种代码在复杂度上卡掉,一定只能是某一种字符重复出现(应该别无可能),那么我们自然可以把这当做一个性质来用。

另外,乱搞是一场不公平的博弈。出题人能够看到选手的程序并加以针对,而选手在赛时无法看到数据。所以我们只要把乱搞代码写死,过不了多久它就真的要死了。那么怎么办呢?我们干脆不把代码写死!让代码“活起来”的方法就是随机化。

“没有随机化的乱搞在 hack 面前是无力的”,为了不被针对性出数据,我们加入随机化因素。为了防止“无效转移固定次数后退出”被针对,我们略微随机这个“固定次数”的数字,并且对转移序列进行 shuffle。咱目前没有想出能够卡掉加了这些随机化的数据,如果真的没有数据能在合理概率下卡掉它的话,这也能算一种随机化正解吧。

不过这些随机化和乱搞调参卡常需要的时间并不比想正解好多少......有能力最好还是写正解。写乱搞主要还是有种和出题人斗智斗勇的快感(

代码

#pragma GCC optimize("Ofast,no-stack-protector,unroll-loops,fast-math")
#pragma GCC target("sse,sse2,sse3,ssse3,sse4.1,sse4.2,avx,avx2,popcnt,tune=native")
#include<bits/stdc++.h>
#define Akano 1
#define pure__Elysia 0
#define loves ^
using namespace std;
using pii = pair<int,int>;
const int MAXW = 14;
const int MAXSTAT = (1<<12) + 10 + 18 + 11 + 8;
const int MAXN = 2006 + 1018 + 1108 + 1000;
string str;
int n,step,maxs,res,pos[28][MAXN],postail[28],vis[MAXN][MAXN],qTail,nxtTail;
pii q[MAXN],nxt[MAXN];
string ans;
mt19937 rng(time(0));
int main(){
	ios::sync_with_stdio(false),cin.tie(0),cout.tie(0);
	cin>>str;
	n = str.length();
	str = "." + str;
	for(int i = 1;i <= n;i++){
		pos[str[i] - 'a'][++postail[str[i] - 'a']] = i;
	}
	step = log2(n);
	maxs = (1<<step) - 1;
	res = n - maxs;
	q[++qTail] = {1,maxs};
	for(int i = 1;i <= res;i++){
		sort(q+1,q+qTail+1);
		for(int ch = 0;ch < 26;ch++){
			nxtTail = 0;
			int st = 1;
			for(int nowi = 1,sameCnt = 0;nowi <= qTail;nowi++){
				int stTail = nxtTail;
				auto u = q[nowi];
				while(st <= postail[ch] && pos[ch][st] < u.first)st++;
				for(int it = st;it <= postail[ch];it++){
					const int j = pos[ch][it];
					if(((u.second & (j - u.first)) == (j - u.first))){
						if(vis[j+1][u.second - (j - u.first)] != i){
							vis[j+1][u.second - (j - u.first)] = i;
							nxt[++nxtTail] = {j+1,u.second - (j - u.first)};
						}
					}
					if(j > u.first + u.second)break;
				}
				if(nxtTail == stTail){
					sameCnt++;
				}else{
					sameCnt = 0;
				}
				if(sameCnt > 60)break;
			}
			if(nxtTail != 0){
				swap(q,nxt),swap(qTail,nxtTail);
				ans += 'a' + ch;
				break;
			}
		}
	}
	cout<<ans<<endl;
	return not(Akano loves pure__Elysia);
}
此作者没有提供个人介绍。
最后更新于 2026-09-10