codeforces 589C Polycarp's Masterpiece(分治 折半搜索)

【2015-2016 ACM-ICPC, NEERC, Southern Subregional Contest (Online Mirror, ACM-ICPC Rules, Teams Preferred)】

给你一个字符串s, n次操作,每次把最后一个字母放到最前面放k次, 得到一个新的字符串,把新的字符串添加到原来的字符串的后面

m次询问,问你在l~r之间字母c出现的次数是多少

第i次变换得到的字符串长度是len[ i ] = len(s)*2^i,它的 前一半是i-1次的字符串,后一半是变换后的字符串

如果querylen < len[ i-1 ], 就直接递归到下一层

如果querylen>len[ i-1 ], 前一半是有基础串改变得到的,整体的字符个数不变,后一半是字符串的某一个后缀+递归到下一层计算

先预处理出每次变换后的字符串前缀,用cnt[64][110][30]记录每个字符出现的次数

#include <iostream>
#include <algorithm>
#include <cstdio>
#include <cstring>
#include <string>
using namespace std;
#define ll __int64
char C[220000], P[105];
int num[30], o, n, m;
int K[100005], cnt[64][110][30];//长度最多是10^18,所以cnt的第一维开64就足够了 
ll find(int n, ll p, int c)
{
	if(!n)
	{
		int ans = 0;
		for(int i = 1; i <= p; i++)
		{
			ans += (c==(P[i]-'a'));
		}
		return ans;
	}
	ll len = o*(1ll<<n-1);
	if(p<len) return find(n-1, p, c);
	return num[c]*(1ll<<n-1)+cnt[n][min((ll)K[n], p-len)][c]+find(n-1, max(0ll,p-len-K[n]), c);
}
int main()
{
	scanf("%s", C+1);
	int l = strlen(C+1);
	for(int i = 1; i < l+1; i++)
	{
		P[i] = C[i];
		num[C[i]-'a']++;
	}
	o = l;
	scanf("%d%d", &n, &m);
	for(int i = 1; i <= n; i++)//预处理前缀 
	{
		scanf("%d", &K[i]);
		if(i <= 63)
		{
			K[i] %= l;
			int res = 0;
			for(int j = K[i]-1; j >= 0; j--)
			{
				res++;
				for(int k = 0; k < 26; k++)
				{
					cnt[i][res][k] = cnt[i][res-1][k];
				}
				cnt[i][res][C[l-j]-'a']++;
			}
			int len = l;
			for(int j = K[i]-1; j >= 0; j--)
			{
				C[++len] = C[l-j];
			}
			for(int j = 1; j <= l-K[i]; j++)
			{
				C[++len] = C[j];
			}
			if(l > 10000)//后期测试过,只要l>2300,这个代码就能ac
			{
				for(int j = 1; j <= l; j++) C[j] = C[j+l];
			}
			else l = len;
		}
	}
	for(int i = 1; i <= n; i++)
	{
		if(o*(1ll<<i)>2e18)
		{
			n=i;
			break;
		}
	}
	for(int i = 1; i <= m; i++)
	{
		ll L, R;
		char tmp[10];
		scanf("%I64d%I64d%s", &L, &R, tmp);
		printf("%I64d\n", find(n, R, tmp[0]-'a')-find(n, L-1, tmp[0]-'a'));
	}
	return 0;
}


介绍另一种方法

基本思路是一致的,不同点就是求前缀时,第二个方法更容易懂

#include <iostream>
#include <algorithm>
#include <cstdio>
#include <cstring>
#include <string>
using namespace std;
#define ll __int64
#define inf 100005
const ll INF=1e18+10;
char s[110];
int k[inf], n, m;
ll lenth[70], pre[70][110][30], num[70][30];
int find(int x, ll len)
{
	if(!x) return s[len]-'a';
	if(len <= lenth[x-1]) return find(x-1, len);
	len-=lenth[x-1];
	if(k[x] >= len) return find(x-1, lenth[x-1]-k[x]+len);
	return find(x-1, len-k[x]);
}
void init()
{
	lenth[0] = strlen(s+1);
	for(int i=1; i<=lenth[0]; i++)
	{
		for(int j=0; j<26; j++) pre[0][i][j] = pre[0][i-1][j];
		pre[0][i][s[i]-'a']++;
	} 
	for(int i=0; i<26; i++) num[0][i] = pre[0][lenth[0]][i];
	for(int i=1; i<=n; i++)
	{
		lenth[i] = lenth[i-1]*2;
		k[i]%=lenth[i-1];
		for(int j=0; j<26; j++) num[i][j] = num[i-1][j]*2;
		if(lenth[i] >= INF) 
		{
			n = i;
			break;
		}
	}
	for(int i=1; i<=n; i++)
	{
		for(int j=1; j<=k[i]; j++)
		{
			for(int r=0; r<26; r++)
			{
				pre[i][j][r] = pre[i][j-1][r];
			}
			int c = find(i-1, lenth[i-1]-k[i]+j);
			pre[i][j][c]++;
		}
	}
} 
ll solve(int x, ll len, int c)
{
	if(!len) return 0;
	if(!x) return pre[0][len][c];
	if(len <= lenth[x-1]) return solve(x-1, len, c);
	len-=lenth[x-1];
	if(len <= k[x]) return num[x-1][c]+pre[x][len][c];
	return num[x-1][c]+pre[x][k[x]][c]+solve(x-1, len-k[x], c);
}
int main()
{
	scanf("%s", s+1);
	scanf("%d%d", &n, &m);
	for(int i=1; i<=n; i++)
	{
		scanf("%d", &k[i]);
	}
	init();
	while(m--)
	{
		ll l, r;
		char c[10];
		scanf("%I64d%I64d%s", &l, &r, c);
		printf("%I64d\n", solve(n,r,c[0]-'a') - solve(n,l-1,c[0]-'a'));
	}
	return 0;
}




评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值