猜拳游戏(后缀数组,线段树)

题面

O i d Oid Oid H i d Hid Hid 进行猜拳游戏。现场有两个仅包含小写字母的字符串 A , B A,B A,B ,每次 O i d Oid Oid A A A 中取出一个长为 K K K 的子串, H i d Hid Hid 也从 B B B 中取出一个长为 K K K 的子串,然后比较两个子串的大小,字典序小的赢,相等则平局。

给出串 A , B A,B A,B ,求当 K = 1 , 2 , . . . , min ⁡ ( ∣ A ∣ , ∣ B ∣ ) K=1,2,...,\min(|A|,|B|) K=1,2,...,min(A,B) 时, O i d Oid Oid 赢的概率、平局的概率、 H i d Hid Hid 赢的概率。每次输出三个最简分数。

∣ A ∣ , ∣ B ∣ ≤ 2 × 1 0 5 |A|,|B|\leq2\times10^5 A,B2×105

题解

由于情况总数是可以由 K K K 快速确定的,所以我们要求的其实是这三种情况的数量。

如果想到了后缀数组和线段树的话,这题基本就只剩敲键盘了。

传统操作,把串 A,B 拼成一个大串 A#B ,然后建后缀数组,以及处理 h h h 数组。

h h h 值从小到大加 r a n k rank rank ,模拟出随着 K K K 变大、 r a n k rank rank 数组的变化,在线段树上修改。

线段树维护 s a sa sa 序列的信息,包括:

  • 该段区间内部顺序对 ( A i , B i ) (A_i,B_i) (Ai,Bi) 的个数 s u m a suma suma ,顺序对 ( B i , A i ) (B_i,A_i) (Bi,Ai) 的个数 s u m b sumb sumb 。(其实就是单独考虑这个区间中的子串时, O i d Oid Oid H i d Hid Hid 分别获胜的情况数)
  • 该区间内 A A A 串子串个数 c n t a cnta cnta B B B 串子串个数 c n t b cntb cntb
  • 该区间最左端的 r a n k rank rank m i mi mi ,该区间最右端的 r a n k rank rank m x mx mx(由于 s a sa sa 序列上每个串的 r a n k rank rank 值一定是单调不降的,因此最左边即最小,最右边即最大),为了方便计算 s u m a , s u m b suma,sumb suma,sumb
  • 同时记录左端 r a n k rank rank 值为 m i mi mi A A A 子串个数 c 1 c_1 c1 B B B 子串个数 c 2 c_2 c2,右端 r a n k rank rank 值为 m x mx mx A A A 子串个数 c 3 c_3 c3 B B B 子串个数 c 4 c_4 c4

合并区间信息比较复杂,但还是很好推的,令 C = m e r g e ( A , B ) C=merge(A,B) C=merge(A,B)

  • s u m a C = s u m a A + s u m a B + c n t a A ⋅ c n t b B − [ m x A = m i B ] ⋅ ( c 3 A × c 2 B ) s u m b C   =   s u m b A + s u m b B + c n t b A ⋅ c n t a B − [ m x A = m i B ] ⋅ ( c 4 A × c 1 B ) suma_C=suma_A+suma_B+cnta_A\cdot cntb_B-[mx_A=mi_B]\cdot(c_{3A}\times c_{2B})\\ sumb_C\,=\,sumb_A+sumb_B+cntb_A\cdot cnta_B-[mx_A=mi_B]\cdot(c_{4A}\times c_{1B}) sumaC=sumaA+sumaB+cntaAcntbB[mxA=miB](c3A×c2B)sumbC=sumbA+sumbB+cntbAcntaB[mxA=miB](c4A×c1B)先把左右区间内部的情况数相加,再算上跨区间的情况。跨区间时考虑是否左区间的右端 r a n k rank rank 与右区间的左端 r a n k rank rank 相等,因为这样意味着多算了中间本来是平局的情况。
  • c n t a , c n t b cnta,cntb cnta,cntb 左右加起来就行了。
  • C C C 的最左端信息继承 A A A 的最左端信息, C C C 的最右端信息继承 B B B 的最右端信息。如果 A A A 的左右 r a n k rank rank 值相等,那么考虑是否算上 B B B 左端的信息。 B B B 的左右 r a n k rank rank 值相等时同理。

时间复杂度 O ( ( ∣ A ∣ + ∣ B ∣ ) log ⁡ ) O((|A|+|B|)\log ) O((A+B)log) ,常数较大。

CODE

#include<set>
#include<map>
#include<stack>
#include<cmath>
#include<ctime>
#include<queue>
#include<bitset>
#include<cstdio>
#include<cstring>
#include<iostream>
#include<algorithm>
using namespace std;
#define MAXN 200005
#define LL long long
#define ULL unsigned long long
#define UI unsigned int
#define DB double
#define ENDL putchar('\n')
#define lowbit(x) (-(x) & (x))
#define FI first
#define SE second
#define eps (1e-4)
#define MI map<LL,int>::iterator
#pragma GCC optimize(2)
int xchar() {
	static const int SZ = 1000005;
	static char ss[SZ];
	static int pos = 0,len = 0;
	if(pos == len) pos = 0,len = fread(ss,1,SZ,stdin);
	if(pos == len) return -1;
	return ss[pos ++];
}
LL xread() {
	LL f=1,x=0;int s = xchar();
    while(s < '0' || s > '9') {if(s=='-')f = -f;s = xchar();}
    while(s >= '0' && s <= '9') {x=x*10+(s^48);s = xchar();}
    return f*x;
}
LL read() {
    LL f=1,x=0;char s = getchar();
    while(s < '0' || s > '9') {if(s=='-')f = -f;s = getchar();}
    while(s >= '0' && s <= '9') {x=x*10+(s-'0');s = getchar();}
    return f*x;
}
void putpos(LL x) {
    if(!x) return ;
    putpos(x/10); putchar('0'+(x%10));
}
void putnum(LL x) {
    if(!x) putchar('0');
    else if(x < 0) putchar('-'),putpos(-x);
    else putpos(x);
}
void AIput(LL x,char c) {putnum(x);putchar(c);}

int n,m,s,o,k;
LL gcd(LL a,LL b) {return b==0 ? a:gcd(b,a%b);}
char a[MAXN],b[MAXN],ss[MAXN<<1];
int hd[MAXN<<1],nx[MAXN<<1],tl[MAXN<<1];
int ins(int i,int y) {
	if(!hd[i]) hd[i] = y;
	else nx[tl[i]] = y;
	return tl[i] = y;
}
int rk[MAXN<<1],sa[MAXN<<1],rn[MAXN<<1];
int h[MAXN<<1],ht[MAXN<<1];
vector<int> bu[MAXN];
struct it{
	LL sma,smb;
	int cta,ctb;
	int mi,mx;
	int c1,c2,c3,c4;
	it(){sma=smb=0;cta=ctb=mi=mx=c1=c2=c3=c4=0;}
}tre[MAXN<<3];
it merg(it a,it b) {
	it c;
	c.sma = a.sma + b.sma + a.cta *1ll* b.ctb;
	c.smb = a.smb + b.smb + a.ctb *1ll* b.cta;
	c.cta = a.cta + b.cta; c.ctb = a.ctb + b.ctb;
	c.mi = a.mi; c.mx = b.mx;
	c.c1 = a.c1,c.c2 = a.c2; c.c3 = b.c3,c.c4 = b.c4;
	if(a.mx == b.mi) {
		c.sma -= a.c3 *1ll* b.c2;
		c.smb -= a.c4 *1ll* b.c1;
		if(a.mi == a.mx) c.c1 += b.c1,c.c2 += b.c2;
		if(b.mi == b.mx) c.c3 += a.c3,c.c4 += a.c4;
	}
	return c;
}
it& operator += (it &a,int b) {
	a.mi += b; a.mx += b; return a;
}
int lz[MAXN<<3],M;
void maketree(int n) {
	M=1;while(M<n+2)M<<=1;
	for(int i = M+n+1;i < M*2;i ++) {
		tre[i].mi = tre[i].mx = 0x3f3f3f3f;
	}
	for(int i = M-1;i > 0;i --) {
		tre[i] = merg(tre[i<<1],tre[i<<1|1]);
	}return ;
}
void addp(int x,int y1,int y2) {
	int s = M+x;
	tre[s].cta = tre[s].c1 = tre[s].c3 = y1;
	tre[s].ctb = tre[s].c2 = tre[s].c4 = y2;
	s >>= 1;
	while(s) (tre[s] = merg(tre[s<<1],tre[s<<1|1])) += lz[s],s >>= 1;
}
void addtree(int l,int r,int y) {
	int s = M+l-1,t = M+r+1;
	while(s || t) {
		if(s<M) (tre[s]=merg(tre[s<<1],tre[s<<1|1]))+=lz[s];
		if(t<M) (tre[t]=merg(tre[t<<1],tre[t<<1|1]))+=lz[t];
		if((s>>1) != (t>>1)) {
			if(!(s&1)) tre[s^1]+=y,lz[s^1]+=y;
			if(t & 1) tre[t^1]+=y,lz[t^1]+=y;
		}s >>= 1;t >>= 1;
	}return ;
}
it findtree(int l,int r) {
	it ls,rs;
	rs.mi = rs.mx = 0x3f3f3f3f;
	int s = M+l-1,t = M+r+1;
	while(s || t) {
		ls += lz[s];
		rs += lz[t];
		if((s>>1) != (t>>1)) {
			if(!(s&1)) ls = merg(ls,tre[s^1]);
			if(t & 1) rs = merg(tre[t^1],rs);
		}s >>= 1;t >>= 1;
	}return merg(ls,rs);
}
int main() {
	freopen("game.in","r",stdin);
	freopen("game.out","w",stdout);
	scanf("%s",a + 1);
	n = strlen(a + 1);
	scanf("%s",b + 1);
	m = strlen(b + 1);
	for(int i = 1;i <= n;i ++) ss[i] = a[i];
	ss[n+1] = 0;
	for(int j = 1;j <= m;j ++) ss[n+1+j] = b[j];
	int nn = n+m+1;
	for(int i = 1;i <= nn;i ++) {
		nx[ins(ss[i],i)] = 0;
	}
	int cn = 0,nm = 0;
	for(int i = 0;i < 256;i ++) {
		int p = hd[i]; if(p) nm ++;
		while(p) {
			sa[++ cn] = p;
			rk[p] = nm;
			if(p == tl[i]) break;
			p = nx[p];
		} hd[i] = tl[i] = 0;
	}
	int mi = min(n,m);
	for(int ii = 1;ii <= nn;ii <<= 1) {
		for(int i = 1;i <= nn;i ++) rn[sa[i]] = rk[sa[i]];
		for(int i = nn-ii+1;i <= nn;i ++) {
			nx[ins(rn[i],i)] = 0;
		}
		for(int i = 1;i <= nn;i ++) {
			if(sa[i]-ii < 1) continue;
			nx[ins(rn[sa[i]-ii],sa[i]-ii)] = 0;
		}
		cn = 0;nm = 0;
		for(int i = 1;i <= nn;i ++) {
			int p = hd[i],pp = 0;
			while(p) {
				sa[++ cn] = p;
				rk[p] = (!pp || rn[pp+ii] != rn[p+ii] ? (++nm):nm);
				if(p == tl[i]) break;
				pp = p; p = nx[p];
			} hd[i] = tl[i] = 0;
		}
	}
	ht[0] = 0;
	for(int i = 1;i <= nn;i ++) {
		int k = sa[rk[i]-1];if(!k) {ht[i]=0;continue;}
		ht[i] = max(0,ht[i-1]-1);
		while(ss[i+ht[i]] == ss[k+ht[i]]) ht[i] ++;
	}
	for(int i = 1;i <= nn;i ++) h[i] = ht[sa[i]];
	for(int i = 1;i <= nn;i ++) {
		bu[h[i]+1].push_back(i);
	}
	maketree(nn);
	for(int i = 1;i <= n;i ++) addp(rk[i],1,0);
	for(int i = n+2;i <= nn;i ++) addp(rk[i],0,1);
	for(int i = 1;i <= mi;i ++) {
		for(int j = 0;j < (int)bu[i].size();j ++) {
			int y = bu[i][j];
			addtree(y,nn,1);
		}
		it as = tre[1];
		LL U = (n-i+1) *1ll* (m-i+1);
		LL A = as.sma,B = as.smb,C = U-A-B;
		LL g1 = gcd(A,U),g2 = gcd(B,U),g3 = gcd(C,U);
		printf("%lld/%lld %lld/%lld %lld/%lld\n",A/g1,U/g1,C/g3,U/g3,B/g2,U/g2);
		addp(rk[n-i+1],0,0); addp(rk[nn-i+1],0,0);
	}
	return 0;
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值