Codeforces #356E: Xenia and String Problem 题解

首先很重要的一点是,gray串的长度种类很有限,必须是二的若干次幂-1,所以所有可能是gray串的子串个数是nlogn级别的

所以可以用isgray[i][clen]表示从i开始,长度为2^clen-1的串是否是gray串

这样我们可以先把原串的beauty值算出来

然后我们枚举每一位改成哪个字符,然后算更新的beauty贡献

首先,对于所有包含第i位且不以i为中心的gray串,修改i必定会使这个串不是gray串,对于这些减少的beauty(区间加值单点求值)可以用一个差分解决(在实际编程的过程中可以把以i为中心的也算进去,在增加的过程中再加回去)

然后考虑每一个从i开始,长度为2^clen-1的子串

用一个score[i][j]表示第i位改为j的新增贡献

如果修改中间字符,那么如果原串是gray串且这个字符没有在左边出现过,则可以增加贡献

如果修改左边或右边的字符,则左边和右边必须有一个是gray串且左边和右边只有一个字符不一样且中间字符没有在那个gray串中出现过,这样的话可以增加贡献

PS: 滚动哈希如果写取模的版本会TLE,写自然溢出的双哈希和单哈希都可以过

#include <cstdio>
#include <iostream>
#include <cstring>
#include <string>
#include <cstdlib>
#include <utility>
#include <cctype>
#include <algorithm>
#include <bitset>
#include <set>
#include <map>
#include <vector>
#include <queue>
#include <deque>
#include <stack>
#include <cmath>
#define LL long long
#define LB long double
#define x first
#define y second
#define Pair pair<int,int>
#define pb push_back
#define pf push_front
#define mp make_pair
#define LOWBIT(x) x & (-x)
using namespace std;

const int MOD=1e9+7;
const LL LINF=2e16;
const int INF=2e9;
const int magic=348;
const double eps=1e-10;
const double pi=3.14159265;

inline int getint()
{
    char ch;int res;bool f;
    while (!isdigit(ch=getchar()) && ch!='-') {}
    if (ch=='-') f=false,res=0; else f=true,res=ch-'0';
    while (isdigit(ch=getchar())) res=res*10+ch-'0';
    return f?res:-res;
}

char s[100048];
int n;

int cnt[100048][27];
bool isgray[100048][21];
LL cost[100048],score[100048][27];
LL ans=0;

const int p=19260817;
const int MOD1=998244353,MOD2=1e9+7;
LL hsh1[100048],hsh2[100048],fp1[100048],fp2[100048];
int len[20];

void init_hash()
{
	int i;
	fp1[0]=1;
	for (i=1;i<=n;i++) 
	{
		hsh1[i]=hsh1[i-1]*p+s[i];
		fp1[i]=fp1[i-1]*p;
	}
}

LL gethash(int left,int right)
{
	return hsh1[right]-hsh1[left-1]*fp1[right-left+1];
}

bool issame(int s1,int s2,int len)
{
	if (s1>s2) swap(s1,s2);
	if (s2+len-1>n) return false;
	return gethash(s1,s1+len-1)==gethash(s2,s2+len-1);
}

int common(int s1,int s2)
{
	int i,res=0;
	for (i=16;i>=0;i--)
		if (issame(s1,s2,res+(1<<i))) res+=(1<<i);
	return res;
}

void calc_cnt()
{
	int i,j;
	for (i=1;i<=n;i++)
		for (j=1;j<=26;j++)
			cnt[i][j]=cnt[i-1][j]+(s[i]-'a'+1==j?1:0);
}

void calc_gray()
{
	int i,clen,center;LL tmp;
	for (clen=1;clen<=16;clen++)
		for (i=1;i<=n;i++)
			if (i+len[clen]-1<=n)
			{
				center=i+len[clen-1];
				if (clen==1 || (isgray[i][clen-1] && isgray[center+1][clen-1] && issame(i,center+1,len[clen-1]) && cnt[i+len[clen]-1][s[center]-'a'+1]-cnt[center][s[center]-'a'+1]==0))
				{
					tmp=(long long)len[clen]*(long long)len[clen];
					isgray[i][clen]=true;
					cost[i]+=tmp;cost[i+len[clen]]-=tmp;
					ans+=tmp;
				}
			}
	for (i=2;i<=n;i++) cost[i]+=cost[i-1];
}

void calc_score(int starter,int clen)
{
	if (clen==1)
	{
		for (int i=1;i<=26;i++) score[starter][i]++;
		return;
	}
	int center=starter+len[clen-1];
	int s1=starter,s2=center+1;
	LL tmp=(long long)len[clen]*(long long)len[clen];
	//change center
	if (isgray[s1][clen-1] && isgray[s2][clen-1] && issame(s1,s2,len[clen-1]))
	{
		for (int i=1;i<=26;i++)
			if (cnt[starter+len[clen]-1][i]-cnt[center][i]==0)
				score[center][i]+=tmp;
	}
	int ll=common(s1,s2);
	if (s1+ll>=center) return;
	int d1=s1+ll,d2=s2+ll;
	int ll2=common(d1+1,d2+1);
	if (d1+1+ll2<center) return;
	//change left
	if (isgray[s2][clen-1] && cnt[starter+len[clen]-1][s[center]-'a'+1]-cnt[center][s[center]-'a'+1]==0)
		score[d1][s[d2]-'a'+1]+=tmp;
	//change right
	if (isgray[s1][clen-1] && cnt[s1+len[clen-1]-1][s[center]-'a'+1]-cnt[starter-1][s[center]-'a'+1]==0)
		score[d2][s[d1]-'a'+1]+=tmp;
}

int main ()
{
	int i,j,clen;
	scanf("%s",s+1);n=strlen(s+1);
	init_hash();
	len[1]=1;for (i=2;i<=20;i++) len[i]=len[i-1]*2+1;
	calc_cnt();
	calc_gray();
	for (i=1;i<=n;i++)
		for (clen=1;clen<=16;clen++)
			if (i+len[clen]-1<=n) calc_score(i,clen);
	LL delta=0;
	for (i=1;i<=n;i++)
		for (j=1;j<=26;j++)
			delta=max(delta,score[i][j]-cost[i]);
	ans+=delta;
	cout<<ans<<endl;
	return 0;
}


评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值