多校Play Games with Rounddog

题意:
给母串S
记子串 S l , r S_{l,r} Sl,r在母串中出现次数为 C l , r C_{l,r} Cl,r
对于每一个次数都有一个权重 w i w_i wi
q组询问,对于一个子串 T = S [ l , r ] T=S[l,r] T=S[l,r],A要选出k个A的子串 R i = S l i , r i R_i=S_{l_i,r_i} Ri=Sli,ri,使得 T ∈ s u f f i x ( R i ) T\in suffix(R_i) Tsuffix(Ri),k大于0之外没有限制,由此,每一个子串 R i = S l i , r i R_i=S_{l_i,r_i} Ri=Sli,ri,A都会获得一堆个数为 W C l i , r i W_{C_{l_i,r_i}} WCli,ri的石堆,B在A的基础上要去掉若干个石堆,但不能全去掉,使得A先手做Nim博弈不会赢。
当然A不会让B得逞,在确保自己能赢得情况下,A要使 ∑ W C l i , r i \sum W_{C_{l_i,r_i}} WCli,ri最大,对于每个询问输出这个最大值
W < 2 58 ,   ∣ S ∣ ≤ 1 0 5 ,   q ≤ 2 ∗ 1 0 5 W<2^{58},\ |S|\leq 10^5,\ q\leq 2*10^5 W<258, S105, q2105


不想让B得逞,那么选出的石堆权值要线性无关,并使权和最大
这很容易证明贪心地从大到小往线性基里丢,取线性基里面的元素作为答案,是对的。

要求T为选出串的后缀,不难想到建SAM
存在T这个后缀,等价于在parent树上T的祖先,也就是说你要倍增找到T的位置,将询问挂在那个点上,最后遍历parent树启发式合并线性基,线性基每次加点如果能加进去就加进去,如果加不进去考虑能不能将表示它的权值最小那个替换掉,和动态生成树有异曲同工之妙,也就是线性基的时候顺便记一下每个位置由哪几个构成
总复杂度 O ( n log ⁡ n log ⁡ W ) O(n\log n\log W) O(nlognlogW)

#include
#define N 200100
#define fo(i,a,b) for(int i=a,__=b;i
#define fd(i,a,b) for(int i=a,__=b;i-->__;)
#define ul unsigned long long
#define ll long long
using namespace std;

struct base{
	ll a[58],b[58],c[58];int tot;
	void init(){
		memset(a,0,sizeof a);
		memset(b,0,sizeof b);
		memset(c,0,sizeof c);
		tot=0;
	}
	void ins(ll v){
		ll V=v,e=0;
		fd(i,58,0)if(V&1ll<<i){
			if(!a[i]){
				a[i]=V;c[tot]=v;
				b[i]=e|1ll<<tot++;
				return;
			}V^=a[i];e^=b[i];
		}ll mn=1e18;int k;
		fo(i,0,tot)if(e&1ll<<i)
			if(c[i]<mn)mn=c[i],k=i;
		if(mn>=v)return;
		fo(i,0,58)if(b[i]&1ll<<k){
			fd(j,58,i)if(b[j]&1ll<<k)a[j]^=a[i],b[j]^=b[i];
			break;
		}e=1ll<<k;c[k]=v;
		fd(i,58,0)if(v&1ll<<i){
			if(!a[i]){
				a[i]=v;b[i]=e;return;
			}v^=a[i];e^=b[i];
		}
	}
	ul sum(){
		ul r=0;
		fo(i,0,tot)r+=c[i];return r;
	}
}b[N];

int nx[N][26],pre[N],dp[N],cnt,ls,t[N],sz[N],hvy[N],ps[N],n,q,f[18][N];
ll w[N];
ul ans[N];
vector<int>r[N],qry[N];

void init(){
	memset(nx,0,sizeof nx);
	memset(dp,0,sizeof dp);
	memset(pre,0,sizeof pre);
	memset(t,0,sizeof t);cnt=ls=1;
}
int nw(int d){dp[++cnt]=d+1;return cnt;}
void ins(int c){
	int now=nw(dp[ls]);
	for(;ls&&!nx[ls][c];ls=pre[ls])nx[ls][c]=now;
	if(!ls)pre[now]=1;else{
		int u=ls,v=nx[u][c];
		if(dp[v]==dp[u]+1)pre[now]=v;else{
			int _v=nw(dp[u]);
			memcpy(nx[_v],nx[v],sizeof nx[v]);
			pre[_v]=pre[v];pre[now]=pre[v]=_v;
			for(;u&&nx[u][c]==v;u=pre[u])nx[u][c]=_v;
		}
	}ls=now;
}

void dfs(int x){
	sz[x]=1;
	int y,mx=0,p=0;
	fo(i,0,r[x].size()){
		y=r[x][i];dfs(y);
		t[x]+=t[y];sz[x]+=sz[y];
		if(sz[y]>mx)mx=sz[y],p=y;
	}hvy[x]=p;
}
void put(int x,int y){
	b[y].ins(w[t[x]]);
	fo(i,0,r[x].size())put(r[x][i],y);
}
void work(int x){
	fo(i,0,r[x].size())work(r[x][i]);
	b[x]=b[hvy[x]];
	b[x].ins(w[t[x]]);
	fo(i,0,r[x].size())if(r[x][i]^hvy[x])put(r[x][i],x);
	if(b[x].tot){
		ul r=b[x].sum();
		fo(i,0,qry[x].size())
			ans[qry[x][i]]=r;
	}
}

int main(){
	int _;scanf("%d",&_);b[0].init();
	while(_--){
		scanf("%d\n",&n);init();
		for(int i=1;i<=n;++i)ins(getchar()-97),ps[i]=ls;
		for(int i=1;i<=cnt;++i)r[i].clear(),qry[i].clear();
		for(int i=2;i<=cnt;++i)r[pre[i]].push_back(i),f[0][i]=pre[i],b[i].init();
		fo(j,1,18)for(int i=1;i<=cnt;++i)f[j][i]=f[j-1][f[j-1][i]];
		for(int i=1;i<=n;++i)t[ps[i]]=1;
		
		for(int i=1;i<=n;++i)scanf("%lld",w+i);
		
		scanf("%d",&q);
		for(int i=1;i<=q;++i){
			int l,r;scanf("%d %d",&l,&r);
			int x=ps[r];
			fd(j,18,0)if(dp[f[j][x]]>=r-l+1)x=f[j][x];
			qry[x].push_back(i);
		}dfs(1);work(1);
		for(int i=1;i<=q;++i)printf("%llu\n",ans[i]);
	}
}

你可能感兴趣的:(博弈,线性基,SAM,启发式算法)