[SCOI2013]密码

89 篇文章 0 订阅
38 篇文章 0 订阅

题目

传送门 to DarkBZOJ

思路

一个更通用的思路:将回文视作标记 ( l , r , k ) (l,r,k) (l,r,k) 表示 [ l , l + k ) [l,l+k) [l,l+k) [ r , r + k ) [r,r+k) [r,r+k) 这两段子串在翻转后相同。用带权并查集维护(为 1 1 1 表示翻转后相等,为 0 0 0 表示直接相等。权值为到根节点的路径异或和),让 k = 2 p k=2^p k=2p 即可转移到 k = 2 p − 1 k=2^{p-1} k=2p1 的情况。

最后处理不相等的字符,这个应当是比较简单的。时间复杂度 O ( α n log ⁡ n ) \mathcal O(\alpha n\log n) O(αnlogn)

但是这个题是字符串题。可以联想到 m a n a c h e r \rm manacher manacher 的过程——对于上面的那种标记传递的问题,其实并不存在。为什么我们要用 S T \rm ST ST 表呢?因为有一个传递闭包的问题。可能 a , b a,b a,b c c c 距离甚远,但是 a = c a=c a=c b = c b=c b=c,所以 a = b a=b a=b,这就需要提前预知才行。

m a n a c h e r \rm manacher manacher 告诉我们,根本没必要。譬如 [ a , b ] [a,b] [a,b] 是回文且 [ c , d ] [c,d] [c,d] 是回文,不妨设 a ⩽ c a\leqslant c ac,如果 b ⩽ d b\leqslant d bd 即两个区间只是相交,那么从左往右依次确定字符并没有什么问题(不需要提前预知信息);如果 d ⩽ b d\leqslant b db 即两个区间相互包含,那么 [ c , d ] [c,d] [c,d] 沿着 a , b a,b a,b 中轴翻转后得到的要求(所谓传递闭包)已然存在。毕竟保证有解。

也就是说:只需要贪心地让当前位置的回文长度是 r i r_i ri 就够了。

类似的道理,判断字符不相等,只需要 存下每一位不能为何字符,当扫描到它的时候再判断。

时间复杂度 O ( n ) \mathcal O(n) O(n)

代码

#include <cstdio>
#include <iostream>
#include <vector>
#include <cstring>
#include <algorithm>
using namespace std;
# define rep(i,a,b) for(int i=(a); i<=(b); ++i)
# define drep(i,a,b) for(int i=(a); i>=(b); --i)
typedef long long int_;
inline int readint(){
	int a = 0, c = getchar(), f = 1;
	for(; '0'>c||c>'9'; c=getchar())
		if(c == '-') f = -f;
	for(; '0'<=c&&c<='9'; c=getchar())
		a = (a<<3)+(a<<1)+(c^48);
	return a*f;
}
void writeUnsigned(const unsigned &x){
	if(x > 9) writeUnsigned(x/10);
	putchar((x-x/10*10)^48);
}
inline void writeint(const int &x){
	if(x >= 0) writeUnsigned(x);
	else putchar('-'), writeUnsigned(-x);
}
inline void getMin(int &a,const int &b){ if(b < a) a = b; }
inline void getMax(int &a,const int &b){ if(a < b) a = b; }

const int MAXN = 200005;
char str[MAXN]; int r[MAXN];
unsigned ok[MAXN];

int main(){
	int n = readint();
	for(int i=2; i<=(n<<1); i+=2)
		r[i] = readint();
	for(int i=3; i<(n<<1); i+=2)
		r[i] = readint(), str[i] = '#';
	str[1] = str[n<<1|1] = '#';
	str[0] = '!', str[(n+1)<<1] = '?';
	for(int i=2,o=1,x=0; i<=(n<<1); ++i,x=0){
		if(o+r[o] > i) // get symmetry
			x = min(o+r[o]-i,r[(o<<1)-i]);
		if(str[i] == 0) // unset
			str[i] = char(__builtin_ffs(~ok[i])-1+'a');
		for(; x<=r[i]; ++x) str[i+x] = str[i-x];
		if(x != i) // not left boundary
			ok[i+x] |= (1u<<(str[i-x]-'a'));
		if(i+r[i] > o+r[o]) o = i;
	}
	rep(i,1,n) putchar(str[i<<1]);
	putchar('\n');
	return 0;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值