字符串作业(四)

「BJOI2020」封印

给出只包含小写字母 a a a, b b b的两个字符串 s s s, t t t q q q次询问,每次询问 s [ l . . . r ] s[l...r] s[l...r] t t t的最长公共子串长度。

建出 t t t的后缀自动机后在自动机上跑 s s s串得到对于每个 s s s的前缀在 t t t中匹配的最长长度 p i p_i pi,然后对于 s [ l . . . r ] s[l...r] s[l...r],二分答案 m i d mid mid,检查是否有 max ⁡ i = l + m i d − 1 r p i ≥ m i d \max_{i=l+mid-1}^rp_i \geq mid maxi=l+mid1rpimid即可。

A C   C o d e \mathcal AC \ Code AC Code

#include
#define maxn 400005
#define rep(i,j,k) for(int i=(j),LIM=(k);i<=LIM;i++)
#define per(i,j,k) for(int i=(j),LIM=(k);i>=LIM;i--)
#define lim 18
using namespace std;

char s[maxn],t[maxn];
int q,ls,lt;
int last , fa[maxn] , len[maxn] , tr[maxn][2] , tot;
int st[lim][maxn] , lg[maxn];
void ins(int c){
	int u = ++tot , p = last , q;
	len[last = u] = len[p] + 1;
	for(;p != -1 && !tr[p][c];p=fa[p]) tr[p][c] = u;
	if(p == -1) fa[u] = 0;
	else if(len[q = tr[p][c]] == len[p] + 1) fa[u] = q;
	else{
		int v = ++tot;
		memcpy(tr[v],tr[q],sizeof tr[q]),fa[v] = fa[q] , len[v] = len[p] + 1;
		for(;p != -1 && tr[p][c] == q;p=fa[p]) tr[p][c] = v;
		fa[q] = fa[u] = v;
	}
}

int qry(int u,int v){
	int t = lg[v-u+1];
	return max(st[t][u] , st[t][v-(1<<t)+1]);
}

int main(){
	scanf("%s%s%d",s,t,&q);
	fa[0] = -1;
	ls = strlen(s) , lt = strlen(t);
	rep(i,0,lt-1) ins(t[i] - 'a');
	int u=0,L=0;
	rep(i,0,ls-1){
		int c = s[i] - 'a';
 		for(;u!=-1 && tr[u][c]==0;u=fa[u]);
 		if(u == -1) u = 0 , L = 0;
 		else L = min(L+1 , len[u] + 1) ,u = tr[u][c];
 		st[0][i] = L;
	}
	rep(i,2,ls) lg[i] = lg[i >> 1] + 1;
	rep(j,1,lim-1) rep(i,0,ls-(1<<j))
		st[j][i] = max(st[j-1][i] , st[j-1][i+(1<<j-1)]);
	for(;q--;){
		int l,r;scanf("%d%d",&l,&r);l--,r--;
		int L = 0 , R = min(r-l+1,lt) , mid;
		for(;L<R;){
			mid = L+R+1 >> 1;
			if(qry(l+mid-1,r) < mid) R = mid - 1;
			else L = mid;
		}
		printf("%d\n",L);
	}
}

LOJ #6537. 毒瘤题加强版再加强版

三倍经验
具体来说就是利用异或的性质,所有数异或起来等于出现奇数次的数的异或和。
如果我们要得到单独一个数字,就 h a s h hash hash一下,具体来说我们只统计   m o d   p = i \bmod p = i modp=i的数的异或和。
那么多用几个 p p p,统计出来出现次数多的数就很有可能是单独一个数字的异或和,反之多个数字的异或和很难在多个不同的 p p p下都一起满足条件。

A C   C o d e \mathcal AC \ Code AC Code

#include
#define vi vector
using namespace std;

int chk(int x){
	for(int i=2;i*i<=x;i++) if(x % i == 0) return 0;
	return 1;
}

int n,K;
vi mod;
int a[20][20100];
map<int,int>mAp;

int main(){
	scanf("%d%d",&n,&K);	
	for(int i=10000;mod.size() < 20;i++) if(chk(i) && rand() % 2)
		mod.push_back(i);
	for(int i=1;i<=n;i++){
		int x;scanf("%d",&x);
		for(int j=0;j<mod.size();j++)
			a[j][x % mod[j]] ^= x;
	}
	for(int j=0;j<mod.size();j++) for(int i=0;i<mod[j];i++)
		if(a[j][i]) mAp[a[j][i]]++;
	vector<int>ans;
	for(auto v:mAp) if(v.second > 2) ans.push_back(v.first);
	sort(ans.begin(),ans.end());
	for(int i=0;i<ans.size();i++) printf("%d\n",ans[i]);
}

CF963D Frequency of String

给出 S S S Q Q Q次询问 S S S中最短的子串 t t t的长度使得字符串 m m m t t t中出现了 k k k次。
所有询问m互不相同

可以想到求出后缀自动机的 r i g h t right right集合 S S S O ( ∣ S ∣ ) O(|S|) O(S)回答一次询问。
然后就这样写,然后就可以过。
因为所有询问 m m m互不相同,又因为对于所有长度为 k k k的串他们的 r i g h t right right集合大小之和为 O ( n ) O(n) O(n)
串不相同代表着只有 O ( n ) O(\sqrt n) O(n )种不同的长度,所以所有询问的复杂度为 O ( n n ) O(n\sqrt n) O(nn )
事实上因为我们只需要 m m m r i g h t right right集合,所以可以不用写后缀树上启发式合并,直接离线求出所有 m m m A C AC AC自动机,拿 s s s在自动机上跑,对于 s s s的前缀所在的节点我们需要更新所有它的后缀的 m m m r i g h t right right集合,新开一个数组 f a fa fa表示沿着 f a i l fail fail链跑下一个是 m m m中的一个的节点,因为 r i g h t right right集合大小是 O ( n n ) O(n \sqrt n) O(nn ),每次暴力爬 f a fa fa也是 O ( n n ) O(n \sqrt n) O(nn )的。

来了来了,对于这种屑题,肯定是要写 b i t s e t bitset bitset的啦。
A C   C o d e \mathcal AC \ Code AC Code

#include
#define maxn 100005
#define rep(i,j,k) for(int i=(j),LIM=(k);i<=LIM;i++)
using namespace std;

char s[maxn],m[maxn];
int n,q,K;
bitset<maxn>C[26];

int main(){
	scanf("%s",s);
	n = strlen(s);
	rep(i,0,n-1) C[s[i] - 'a'][i] = 1;
	scanf("%d",&q);
	for(;q--;){
		scanf("%d%s",&K,m);
		int L = strlen(m);
		static bitset<maxn>ans;
		ans.set();
		rep(i,0,L-1) ans &= C[m[i] - 'a'] >> i;
		vector<int>a;
		for(int i=ans._Find_first();i!=ans.size();i=ans._Find_next(i))
			a.push_back(i);
		if(a.size() < K) puts("-1");
		else{
			int ret = 0x3f3f3f3f;
			rep(i,K-1,a.size()-1) ret = min(ret , a[i] - a[i-K+1] + L); 
			printf("%d\n",ret);
		}
	}
}

CF356E Xenia and String Problem

字符串作业(四)_第1张图片

分类讨论屑题。
g r a y gray gray串的长度只有可能是 2 k − 1 2^k-1 2k1,所以可以计算出所有 g r a y gray gray串。
改字符,
一.变为 g r a y gray gray的情况:
1.改中间字符原来在外面出现过现在没出现过,暴力哈希判断即可。
2.改两边字符,原来差一个字符匹配现在不差了,对于所有 g r a y gray gray串求出往左/右差一个字符的位置贡献上去。
二.变成不是 g r a y gray gray的情况:
1.改中间字符原来在外面没出现过现在出现过,暴力哈希判断即可。
2.改两边字符,原来就是 g r a y gray gray串,只要改了两边就一定不是,对于所有 g r a y gray gray串写区间加即可。

综上,每次就是先减去改字符是中间字符的贡献,再减去(二.2),然后枚举改成那个字符,加上(一.2),加上中间字符的贡献。

真的屑
A C   C o d e \mathcal AC \ Code AC Code

#include
#define maxn 200005
#define rep(i,j,k) for(int i=(j),LIM=(k);i<=LIM;i++)
#define per(i,j,k) for(int i=(j),LIM=(k);i>=LIM;i--)
using namespace std;
#define lim 17
#define LL long long
#define mod 998244353
#define S 131
char s[maxn];
int f[lim][maxn],n;
int sm[26][maxn];
LL hs[maxn],pw[maxn],p12[maxn][26],p22[maxn],Sm;

LL calc(int u,int v){ return ((hs[v] - hs[u-1] * pw[v-u+1]) % mod + mod) % mod; }
int LCP(int u,int v){
	int L = 0 , R = min(n-u+1,n-v+1) , mid;
	while(L<R){
		mid = (L+R+1) >> 1;
		if(calc(u,u+mid-1) == calc(v,v+mid-1))
			L = mid;
		else 
			R = mid - 1;
	}
	return L;
}

int main(){
	
	scanf("%s",s+1);
	n = strlen(s+1);
	pw[0] = 1;
	rep(i,1,n){
		f[0][i] = 1;
		Sm++;
		hs[i] = (hs[i-1] * S + s[i]) % mod;
		pw[i] = pw[i-1] * S % mod;
		rep(j,0,25) 
			sm[j][i] = sm[j][i-1] + (s[i] - 'a' == j);
	}
	rep(j,1,lim-1) rep(i,1,n-(1<<j+1)+2){
		if(f[j-1][i] && f[j-1][i+(1<<j)] && sm[s[i+(1<<j)-1] - 'a'][i+(1<<j+1)-2] - sm[s[i+(1<<j)-1] - 'a'][i-1] == 1
			&& calc(i,i+(1<<j)-2) == calc(i+(1<<j),i+(1<<j+1)-2)) 
			f[j][i] = 1 , Sm += ((1<<j+1)-1ll) * ((1<<j+1)-1ll) , p22[i] += ((1<<j+1)-1ll) * ((1<<j+1)-1ll) , 
			p22[i+(1<<j)-1] -= ((1<<j+1)-1ll) * ((1<<j+1)-1ll) , p22[i+(1<<j)] += ((1<<j+1)-1ll) * ((1<<j+1)-1ll),
			p22[i+(1<<j+1)-1] -= ((1<<j+1)-1ll) * ((1<<j+1)-1ll);
		else if(f[j-1][i]){
			int t = min(LCP(i,i+(1<<j)) , (1<<j)-1);
			if(calc(i,i+t-1) == calc(i+(1<<j),i+(1<<j)+t-1)
				&& calc(i+t+1,i+(1<<j)-2) == calc(i+(1<<j)+t+1,i+(1<<j+1)-2)
				&& sm[s[i+(1<<j)-1] - 'a'][i+(1<<j)-1] - sm[s[i+(1<<j)-1] - 'a'][i-1] == 1)
				p12[i+(1<<j)+t][s[i+t]-'a'] += ((1<<j+1)-1ll) * ((1<<j+1)-1ll);
				
			if(f[j-1][i+(1<<j)] && calc(i,i+t-1) == calc(i+(1<<j),i+(1<<j)+t-1)
				&& calc(i+t+1,i+(1<<j)-2) == calc(i+(1<<j)+t+1,i+(1<<j+1)-2)
				&& sm[s[i+(1<<j)-1] - 'a'][i+(1<<j+1)-2] - sm[s[i+(1<<j)-1] - 'a'][i+(1<<j)-2] == 1)
				p12[i+t][s[i+t+(1<<j)]-'a'] += ((1<<j+1)-1ll) * ((1<<j+1)-1ll);
		}
		else if(f[j-1][i+(1<<j)]){
			int t = min(LCP(i,i+(1<<j)) , (1<<j)-1);
			if(calc(i,i+t-1) == calc(i+(1<<j),i+(1<<j)+t-1)
				&& calc(i+t+1,i+(1<<j)-2) == calc(i+(1<<j)+t+1,i+(1<<j+1)-2)
				&& sm[s[i+(1<<j)-1] - 'a'][i+(1<<j+1)-2] - sm[s[i+(1<<j)-1] - 'a'][i+(1<<j)-2] == 1)
				p12[i+t][s[i+t+(1<<j)]-'a'] += ((1<<j+1)-1ll) * ((1<<j+1)-1ll);
		}
	}
	rep(i,1,n) p22[i] += p22[i-1];
	LL ans = Sm;
	rep(i,1,n){
		LL t = Sm;
		rep(j,1,lim-1) if(i-(1<<j)+1 > 0)
			if(f[j][i-(1<<j)+1])
				t -= ((1<<j+1)-1ll) * ((1<<j+1)-1ll);
		t -= p22[i];
		rep(j,0,25) if(j != s[i] - 'a'){
			LL ret = t + p12[i][j];
			
			rep(p,1,lim-1) if(i-(1<<p)+1 > 0){
				if(f[p-1][i-(1<<p)+1] && f[p-1][i+1] && calc(i-(1<<p)+1,i-1) == calc(i+1,i+(1<<p)-1) && 
					sm[j][i-1] - sm[j][i-(1<<p)] == 0)
						ret +=((1<<p+1)-1ll) * ((1<<p+1)-1ll);
			}
			ans = max(ans , ret);
		}
	}
	printf("%lld\n",ans);
}

LOJ #517. 「LibreOJ β Round #2」计算几何瞎暴力

字符串作业(四)_第2张图片
那就看代码吧。

#include
#define maxn 200005
#define LL long long
#define rep(i,j,k) for(int i=(j),LIM=(k);i<=LIM;i++)
#define per(i,j,k) for(int i=(j),LIM=(k);i>=LIM;i--)
#define inf 0x3f3f3f3f
using namespace std;

int XOR,qXOR,n,tot,ch[maxn*30][2],last,a[maxn];
struct node{
	int a[30],sz;
	void ins(int u){ ++sz;rep(i,0,29) if(u>>i&1) a[i]++; }
	LL sum(int x=inf){
		LL r = 0;
		rep(i,0,29) r += (1ll << i) * min(x , XOR>>i&1 ? sz - a[i] : a[i]);
		return r;
	}
}s[maxn],tr[maxn*30];

void ins(int x){
	for(int u=1,p=29;p>=0;tr[u=ch[u][x>>p&1]].ins(x),p--)
		if(!ch[u][x>>p&1]) ch[u][x>>p&1]=++tot;
}
LL sum(int x){
	if(x > last) return s[x].sum();
	int u=1;LL r = 0;
	per(i,29,0){
		int c=(qXOR>>i&1);
		if(x <= tr[ch[u][c]].sz) u =ch[u][c];
		else r += tr[ch[u][c]].sum(),x-=tr[ch[u][c]].sz,u=ch[u][!c];
	}
	return r + tr[u].sum(x);
}

int main(){
	scanf("%d",&n);int x;
	rep(i,1,n) scanf("%d",&x),a[i]=x,s[i] = s[i-1] , s[i].ins(x);
	int Q;scanf("%d",&Q);
	tot = 1;
	for(int op,l,r;Q--;){
		scanf("%d",&op);
		if(op == 1) scanf("%d",&x),x^=XOR,s[n+1]=s[n],s[++n].ins(x),a[n]=x;
		if(op == 2) scanf("%d%d",&l,&r),printf("%lld\n",sum(r)-sum(l-1));
		if(op == 3) scanf("%d",&x),XOR ^= x;
		if(op == 4) for(qXOR = XOR;last < n;) ins(a[++last]);
	}
}

LOJ #2168. 「POI2011 R3 Day1」周期性 Periodicity

社论
设字符串 s s s的最短周期为 L L L L L L的计算可以简单通过 k m p kmp kmp得到。
那么对 L L L分类讨论,
L = 1 L=1 L=1则应该对于一个全为 0 0 0的字符串。
L = ∣ s ∣ L=|s| L=s则应该对应一个前 L − 1 L-1 L1位全为 0 0 0,最后一位为 1 1 1的字符串。
2 L ≤ ∣ s ∣ 2L \leq |s| 2Ls,则将字符串分解为 t t t t . . . t ′ tttt...t' tttt...t的形式,递归求 t t ′ tt' tt的答案,然后根据最短周期倒推出 s s s所对应的字符串,之所以可以这样做,是因为可以通过有两个周期 p , q p,q p,q并且 p + q ≤ ∣ s ∣ p+q \leq |s| p+qs那么他们的 gcd ⁡ \gcd gcd也是周期来证明,最短周期是 l l l的情况下长度大于 l + ( ∣ s ∣   m o d   l ) l+(|s|\bmod l) l+(smodl)的所有周期其实都是 l l l的倍数。
因为我们递归求出的答案是一定会满足所有 ≤ l + ( ∣ s ∣   m o d   l ) \leq l+(|s|\bmod l) l+(smodl)的周期,所以 t t t t . . t ′ tttt..t' tttt..t是可以满足题意的。
2 L > ∣ s ∣ 2L\gt |s| 2L>s,将字符串分解为 t a t tat tat的形式,其中 ∣ t ∣ = ∣ s ∣   m o d   L |t| = |s| \bmod L t=smodL,递归求 t t t的答案,发现 a a a要么是全为 0 0 0,要么是只有最后一个为 1 1 1,简单 k m p kmp kmp判断一下即可。
字符串作业(四)_第3张图片

A C   C o d e \mathcal AC \ Code AC Code

#include
#define maxn 200005
#define rep(i,j,k) for(int i=(j),LIM=(k);i<=LIM;i++)
#define per(i,j,k) for(int i=(j),LIM=(k);i>=LIM;i--)
using namespace std;

char s[maxn];
int n,nxt[maxn];

void solve(char *s,int n){
	for(int j=-1,k=0;k<n;)
		if(j == -1 || s[j] == s[k]) nxt[++k] = ++j;
		else j = nxt[j];
	if(nxt[n] == n-1){
		rep(i,0,n-1)s[i] = '0';
		return;
	}
	if(nxt[n] == 0){
		rep(i,0,n-2) s[i] = '0';
		s[n-1] = '1';
		return;
	}
	int L = n - nxt[n];
	if(2 * L <= n){
		solve(s+(n/L-1)*L,n%L+L);
		per(i,n-L-1,0) s[i] = s[i+L];
	}
	else{
		int l = n % L;
		solve(s,l);
		rep(i,1,l) s[n-i] = s[l-i];
		rep(i,l,n-l-1) s[i] = '0';
		 
		for(int j=-1,k=0;k<=n;)
			if(j == -1 || s[j] == s[k]) nxt[++k] = ++j;
			else j = nxt[j];
		if(n - nxt[n] != L) s[n-l-1] = '1';
	}
}

int main(){
	int T;
	scanf("%d",&T);
	nxt[0] = -1;
	for(;T--;){
		scanf("%s",s+1);
		n = strlen(s+1);
		solve(s+1,n);
		puts(s+1);
	}
}

#2278. 「HAOI2017」字符串

如果从前往后第一次失配和从后往前第一次失配的位置之差 ≤ K − 1 \leq K-1 K1则匹配,求 p i p_i pi s s s中出现次数,那么我们建出 p i p_i pi A C AC AC自动机,对于 p i p_i pi的前 x x x个字符,在 A C AC AC自动机上有个位置 a a a,对于 p i [ x + K + 1.... ∣ p i ∣ ] p_i[x+K+1....|p_i|] pi[x+K+1....pi],我们求出 p i p_i pi的反串的 A C AC AC自动机,那么这个 p i [ x + K + 1.... ∣ p i ∣ ] p_i[x+K+1....|p_i|] pi[x+K+1....pi]反过来进入自动机也有一个位置 b b b
对于 s s s的前 y y y个字符在正串自动机上有个位置 c c c,后 ∣ s ∣ − y − K |s|-y-K syK个字符在反串自动机上也有个位置 d d d
s s s p i p_i pi同时删去中间的 K K K个字符后 p i p_i pi s s s中出现,可以看做在 A C AC AC自动机的后缀树上 c c c a a a的子树中, d d d b b b的子树中。
但是可能会有多种删去连续 K K K个字符的方案使得他们相等,会算重,我们发现这些删去的 K K K个字符的所有方案是连续的(比如删去区间是 [ x , x + K + 1 ] , [ x + 1 , x + K + 2 ] , [ x + 2 , x + K + 3 ] [x,x+K+1],[x+1,x+K+2],[x+2,x+K+3] [x,x+K+1],[x+1,x+K+2],[x+2,x+K+3]),所以相邻两个方案( [ x , x + K + 1 ] , [ x + 1 , x + K + 2 ] [x,x+K+1],[x+1,x+K+2] [x,x+K+1],[x+1,x+K+2])对应了一个删去 K − 1 K-1 K1个字符的方案( x + 1 , x + K + 1 x+1,x+K+1 x+1,x+K+1),所以直接减去删去 K − 1 K-1 K1个字符的方案即可,但是因为 [ 0.... K − 1 ] [0....K-1] [0....K1]不能对应相邻两个方案,所以不能计算形如这类的贡献。
那么就是两棵树,求在两棵树中点都在当前点对的点的子树内的数量,这可以看做二维平面上的数点问题,但是还有更简单的做法,用天天爱跑步的方法树上差分后即可用 d f s dfs dfs解决一棵树的限制。

A C   C o d e \mathcal AC \ Code AC Code

#include
#define maxn 400005
#define rep(i,j,k) for(int i=(j),LIM=(k);i<=LIM;i++)
#define per(i,j,k) for(int i=(j),LIM=(k);i>=LIM;i--)
#define LL long long
#define pb push_back
#define maxc 94
using namespace std;

int K,n,m;
struct node{int id,x,y;node(const int &id=0,const int &x=0,const int &y=0):id(id),x(x),y(y){}};
vector<node>v1[maxn];
vector<int>v2[maxn];
char s[maxn],t[maxn];
int tr[maxn][maxc] , fa[maxn] , tot , pos[maxn] , ps[maxn];
int st[maxn],ed[maxn],tim,ans[maxn];
vector<int>G[maxn];
void dfs(int u){
	st[u] = ++tim;
	for(int v:G[u]) dfs(v);
	ed[u] = tim; 
}
int t1[maxn],t2[maxn];
void upd(int u,int v,int *tr){ for(;u<=tot+1;u+=u&-u) tr[u] += v; }
int qry(int u,int *tr){ int r=0;for(;u>0;u-=u&-u) r += tr[u]; return r; }
void dfs2(int u){
	for(node t:v1[u]) ans[t.id] -= qry(ed[t.x],t1) - qry(st[t.x]-1,t1) - qry(ed[t.y],t2) + qry(st[t.y]-1,t2);
	for(int t:v2[u]) upd(st[pos[t+K+1]],1,t1),upd(st[pos[t+K]],1,t2);
	for(int v:G[u]) dfs2(v);
	for(node t:v1[u]) ans[t.id] += qry(ed[t.x],t1) - qry(st[t.x]-1,t1) - qry(ed[t.y],t2) + qry(st[t.y]-1,t2);
}
int main(){
	scanf("%d%s%d",&K,s+1,&n);
	m = strlen(s+1);
	rep(i,1,n){
		scanf("%s",t+1);
		int L = strlen(t+1);
		if(L <= K){
			ans[i] = m-L+1;
			continue;
		}
		int u = 0;
		rep(i,1,L){
			int v = t[i]-33;
			if(!tr[u][v]) tr[u][v] = ++tot;
			u = tr[u][v];
		}
		ps[L+1] = u = 0;
		per(i,L,1){
			int v = t[i] - 33;
			if(!tr[u][v]) tr[u][v] = ++tot;
			u = tr[u][v];
			ps[i] = u;
		}
		for(int j=0,u=0;j+K<=L;u=tr[u][t[j+1]-33],j++)	
			v1[u].pb(node(i,ps[j+K+1],j?ps[j+K]:maxn-1));
	}
	static int q[maxn],L=0,R=0;
	rep(i,0,93) if(tr[0][i]) q[R++] = tr[0][i];
	for(int u;L<R;){
		u = q[L++];
		rep(i,0,93) if(tr[u][i]) fa[tr[u][i]] = tr[fa[u]][i] , q[R++] = tr[u][i];
			else tr[u][i] = tr[fa[u]][i];
	}
	rep(i,1,tot) G[fa[i]].pb(i);
	dfs(0);
	for(int i=m,u=0;i>=1;i--) u=tr[u][s[i]-33] , pos[i] = u;
	for(int i=0,u=0;i+K<=m;i++) v2[u].pb(i),u=tr[u][s[i+1]-33];
	dfs2(0);
	rep(i,1,n) printf("%d\n",ans[i]);
}

你可能感兴趣的:(字符串作业(四))