2023牛客+杭电补题和总结记录(后半)

前半继续写要被编辑器卡飞了,换个档

杭电第六场

\(1002.Pair\ Sum\ and\ Perfect\ Square\)
对于每个位置可以求出该位置的数和哪些位置的数能够组成完全平方数,因为原序列是排列,并且完全平方数个数不多,所以预处理的复杂度不高。(也可以在后续统计过程中处理)
处理出点对以后就变成了二维数点问题。我一开始把所有询问拆成两个端点排序,这样复杂度会基于 \(2*q\)。后来经大佬提点后领悟了,直接对右端点排序,从 \(1\) 处理到 \(n\) 的过程中只需要对于每个位置在树状数组中加进另一个数比自己小的点对,然后在查询时一样用右端点的查询减去左端点-1的查询。此时因为每个位置只有另一维比自己小的点对,矩形区域相减减去了下边多出来的部分,而左边的部分本来就是空的,这样保证了正确性。

Pair Sum and Perfect Square
#include
using namespace std;
const int N = 1e5 + 10;
int t, n, a[N], rec[N], m, ans[N], tree[N], vis[N];
struct node {
    int id, l, r;
}q[N];
inline bool cmp(node x, node y) {
    return x.r == y.r ? x.l < y.l : x.r < y.r;
}
inline void add(int x) {
    for (;x <= n;x += (x & -x))tree[x]++;
}
inline int query(int x) {
    int val = 0;
    for (;x;x -= (x & -x))val += tree[x];
    return val;
}
int main()
{
    ios::sync_with_stdio(0);
    cin.tie(0);
    cin >> t;
    while (t--) {
        cin >> n;
        for (int i = 1;i <= n;i++) {
            cin >> a[i];
            rec[a[i]] = i;
            tree[i] = 0;
            vis[i] = 0;
        }
        cin >> m;
        for (int i = 1;i <= m;i++) {
            ans[i] = 0;
            cin >> q[i].l >> q[i].r;
            q[i].id = i;
        }
        sort(q + 1, q + m + 1, cmp);
        int tag = 1;
        for (int i = 1;i <= n;i++) {
            for (int j = 1;j * j <= 2 * n;j++) {
                int y = j * j;
                if (y - a[i] > 0 && y - a[i] <= n && vis[rec[y - a[i]]]) {
                    add(rec[y - a[i]]);
                }
            }
            while (q[tag].r <= i && tag <= m) {
                ans[q[tag].id] = query(q[tag].r) - query(q[tag].l - 1);
                tag++;
            }
            vis[i] = 1;
        }
        for (int i = 1;i <= m;i++)cout << ans[i] << '\n';
    }
    return 0;
}

牛客第六场

\(H.traffic\)
一棵树多两条边,每天要删去两条边使树保持连通且剩下的总边权最小。树上加两条边可能会形成两个独立环,也有可能形成两个有重复部分的环,后者的情况可以把环的部分看作三部分:只在环1上,只在环2上,在环1上也在环2上,这三部分中删去两部分的最大边即可。
在不同的自变量下取多个一次函数中的最大值,这个可以用李超线段树维护。(板子++)
把两个独立环或者三部分环边分别建李超线段树,每天查询两棵/三棵树中对于这一天的最大值,总代价减去两个最大值就是答案。

traffic
#include
#define ll long long
using namespace std;
const int N=1e5+10;
int n,t,vis[N],cnt,fa[N],dep[N],edge[N],num,tag[N],tim;
int ver[N<<1],head[N],tot=1,Next[N<<1],id[N<<1];
int tree[N<<2][4],flag;
ll suma,sumb,A[N],B[N];
struct Edge{
	ll k,b;
}p[N];
void add(int x,int y,int i){
	ver[++tot]=y;
	Next[tot]=head[x];
	head[x]=tot;
	id[tot]=i;
}
ll calc(int id,int d){
	return p[id].b+p[id].k*d;
}
void upd(int root,int cl,int cr,int u,int op){
	int &v=tree[root][op],mid=(cl+cr)>>1;
	if(calc(u,mid)>calc(v,mid))swap(u,v);
	int bl=(calc(u,cl)>calc(v,cl)),br=(calc(u,cr)>calc(v,cr));
	if(bl==1)upd(root<<1,cl,mid,u,op);
	if(br==1)upd(root<<1|1,mid+1,cr,u,op);
}
void update(int root,int cl,int cr,int l,int r,int u,int op){
	if(l<=cl&&cr<=r){
		upd(root,cl,cr,u,op);
		return;
	}
	int mid=(cl+cr)>>1;
	if(l<=mid)update(root<<1,cl,mid,l,r,u,op);
	if(mid>1;
	ll res=calc(tree[root][op],d);
	if(mid>=d)res=max(res,query(root<<1,l,mid,d,op));
	else res=max(res,query(root<<1|1,mid+1,r,d,op));
	return res;
}
int main()
{
	scanf("%d%d",&n,&t);
	for(int i=1,u,v;i<=n+1;i++){
		scanf("%d%d%lld%lld",&u,&v,&A[i],&B[i]);
		add(u,v,i),add(v,u,i);
		suma+=A[i],sumb+=B[i];
	}
	dfs(1,0);
	for(int i=1;i<=n+1;i++){
		if(tag[i]){
		//	printf("x:%d y:%d tag:%d\n",i,fa[i],tag[edge[i]]);
			p[++num].k=A[i],p[num].b=B[i];
			update(1,0,t,0,t,num,tag[i]);
			if(tag[i]==3)flag=1;
		}
	}
	ll ans1,ans2,ans3=0;
	for(int i=0;i<=t;i++){
		ans1=query(1,0,t,i,1),ans2=query(1,0,t,i,2);
		if(flag){
			ans3=query(1,0,t,i,3);
			printf("%lld\n",suma*i+sumb-ans1-ans2-ans3+min({ans1,ans2,ans3}));
		}
		else printf("%lld\n",suma*i+sumb-ans1-ans2);
	//	printf("i:%d ans1:%lld ans2:%lld ans3:%lld\n",i,ans1,ans2,ans3);
	}
	return 0;
}

\(J.Even\)
是我写不出来的题意转化
可以把数分成两部分,先处理偶数部分,再处理奇数部分(一个偶数处理掉所有的2以后也会加入奇数部分进行处理)。
开权值线段树存区间内某种数的个数,把每个数仍为偶数时的数值都加进偶数主席树,然后根据数的个数来判断取到的最大值(区间第k大)。
奇数的情况可以算出一个奇数到达下一个奇数所需的步数,这个步数也要加入主席树,用于判断找到某一个值的数,但是不知道这个数在处理过程中是不是最大值的情况。
在主席树上判断某个数值是不是答案时,有可能会遇到同一个值的奇数都被处理过,这个值的最后一个奇数在被处理的过程中,直到变成下一个奇数为止,会一直是被处理的对象。但是同一时刻,它可能已经不是最大值。所以用它被处理剩余步数后的值,和下一个奇数进行比较,取最大值。

Even
#include
#define ll long long
using namespace std;
const int N=1e4+10,inf=1e9;
int n,q,a[N],num[N],ql,qr,qk;
struct node{
	int ls,rs;
	ll cnt,sum;
};
struct tree{
	int T[N],cnt;
	node tr[N*1000];
	void pushup(int p){
		tr[p].cnt=tr[tr[p].ls].cnt+tr[tr[p].rs].cnt;
		tr[p].sum=tr[tr[p].ls].sum+tr[tr[p].rs].sum;
	}
	void add(int &p,int p0,int l,int r,int pos,int c,int s){
		p=++cnt;
		tr[p]=tr[p0];
		if(l==r){
			tr[p].cnt+=c,tr[p].sum+=s;
			return;
		}
		int mid=(l+r)>>1;
		if(pos<=mid)add(tr[p].ls,tr[p0].ls,l,mid,pos,c,s);
		else add(tr[p].rs,tr[p0].rs,mid+1,r,pos,c,s);
		pushup(p);
	}
	int query(int p,int p0,int l,int r,int k,int flag){
		ll s=tr[p].sum-tr[p0].sum,c=tr[p].cnt-tr[p0].cnt;
		if(l==r){
			if(k>s)return 0;
			if(!flag)return l;
			int step=s/c;
			if(k/step<=c-2)return l;
			else{
				int res=k-step*(c-1);
				return max(l>>res,query(T[qr],T[ql-1],1,inf,qk+step-res+1,0));
			}
		}
		int mid=(l+r)>>1;
		ll sr=tr[tr[p].rs].sum-tr[tr[p0].rs].sum;
		if(k<=sr)return query(tr[p].rs,tr[p0].rs,mid+1,r,k,flag);
		else return query(tr[p].ls,tr[p0].ls,l,mid,k-sr,flag);
	}
}T1,T2;
int main()
{
	scanf("%d%d",&n,&q);
	for(int i=1;i<=n;i++){
		scanf("%d",&a[i]);
		T1.T[i]=T1.T[i-1];
		T2.T[i]=T2.T[i-1];
		while(a[i]%2==0){
			T1.add(T1.T[i],T1.T[i],1,inf,a[i],1,1);
			num[i]++;
			a[i]>>=1;
		}
		num[i]+=num[i-1];
		T1.add(T1.T[i],T1.T[i],1,inf,a[i],inf,inf);
		while(a[i]){
			int tmp=a[i],res=1;
			a[i]/=2;
			while(a[i]&&a[i]%2==0)a[i]/=2,res++;
			T2.add(T2.T[i],T2.T[i],1,inf,tmp,1,res);
		}
	}
	while(q--){
		scanf("%d%d%d",&ql,&qr,&qk);
		if(num[qr]-num[ql-1]>=qk){
			printf("%d\n",T1.query(T1.T[qr],T1.T[ql-1],1,inf,qk,1));
		}
		else{
			qk-=num[qr]-num[ql-1];
			printf("%d\n",T2.query(T2.T[qr],T2.T[ql-1],1,inf,qk,1));
		}
	}
	return 0;
}

牛客第七场

\(E.Star\ Wars\)
很妙的根号划分思想,把操作时每个点要遍历修改或统计的边数控制在 \(\sqrt m\) 内。
把每条边定向,对每个点 \(i\) 统计有边指向自己的点的权值和 \(d_i\),存下不超过 \(\sqrt m\) 条边。修改时修改每个点自己的权值,并遍历出边,修改出边指向的点 \(j\)\(d_j\)。统计点 \(i\) 的答案时,首先令答案统计到自己的权值 \(a_i\) 和上面所说的 \(d_i\),然后遍历所有出边统计 \(a_j\)。每次操作的复杂度都是 \(O(\sqrt m)\),总复杂度为 \(O(q\sqrt m)\)

Star Wars
#include
#define ll long long
using namespace std;
const int N = 3e5 + 10, inf = 1073741824;
int n, q, c[N], cnt;
ll ans, d[N], a[N];
vectorv[N], e[N];
mapmp;
void init() {
	ans = cnt = 0;
	mp.clear();
	for (int i = 1;i <= n;i++) {
		v[i].clear();
		e[i].clear();
		c[i] = a[i] = d[i] = 0;
	}
}
int main()
{
	scanf("%d%d", &n, &q);
	init();
	ll x, y;
	for (int i = 1, op;i <= q;i++) {
		scanf("%d", &op);
		if (op == 1) {
			scanf("%lld%lld", &x, &y);
			x ^= ans, y ^= ans;
			if (!mp.count(x))mp[x] = ++cnt;
			if (!mp.count(y))mp[y] = ++cnt;
			x = mp[x], y = mp[y];
			if (c[x] > c[y])swap(x, y);
			int flag = 0;
			for (int j = 0;j < v[x].size();j++) {
				if (v[x][j] == y) {
					flag = 1;
					if (e[x][j] > 0) {
						e[x][j]++;
					}
					else {
						e[x][j]++;
						d[y] += a[x];
					}
					break;
				}
			}
			if (flag)continue;
			for (int j = 0;j < v[y].size();j++) {
				if (v[y][j] == x) {
					flag = 1;
					if (e[y][j] > 0) {
						e[y][j]++;
					}
					else {
						e[y][j]++;
						d[x] += a[y];
					}
					break;
				}
			}
			if (!flag) {
				v[x].emplace_back(y);
				e[x].emplace_back(1);
				c[x]++;
				d[y] += a[x];
			}
		}
		else if (op == 2) {
			scanf("%lld%lld", &x, &y);
			x ^= ans, y ^= ans;;
			if (!mp.count(x))mp[x] = ++cnt;
			if (!mp.count(y))mp[y] = ++cnt;
			x = mp[x], y = mp[y];
			int flag = 0;
			for (int j = 0;j < v[x].size();j++) {
				if (v[x][j] == y) {
					flag = 1;
					e[x][j]--;
					if (!e[x][j])d[y] -= a[x];
					break;
				}
			}
			if (flag)continue;
			for (int j = 0;j < v[y].size();j++) {
				if (v[y][j] == x) {
					flag = 1;
					e[y][j]--;
					if (!e[y][j])d[x] -= a[y];
					break;
				}
			}
		}
		else if (op == 3) {
			scanf("%lld%lld", &x, &y);
			x ^= ans, y ^= ans;
			if (!mp.count(x))mp[x] = ++cnt;
			x = mp[x];
			a[x] += y;
			for (int j = 0;j < v[x].size();j++) {
				if (e[x][j])d[v[x][j]] += y;
			}
		}
		else {
			scanf("%lld", &x);
			x ^= ans;
			if (!mp.count(x))mp[x] = ++cnt;
			x = mp[x];
			ans = a[x] + d[x];
			for (int j = 0;j < v[x].size();j++) {
				if (e[x][j])ans += a[v[x][j]];
			}
			printf("%lld\n", ans);
			ans %= inf;
		}
	}
	return 0;
}

\(L.Misaka\ Mikoto's\ Dynamic\ KMP\ Problem\)
应该是一道歪了榜没出的题,赛时策略还是太被动了。
询问时暴力kmp统计答案即可。

Star Wars
#include
#define ll long long
using namespace std;
const int N=2e6+10;
ll S,m,b,p,cnt,T,nxt[N];
ll s[N];
ll ans,res,lst;
ll kmp(){
	nxt[1]=0;
	ll x=0;
	for(int i=2,j=0;i<=S+1+T;i++){
		while(j>0&&s[i]!=s[j+1])j=nxt[j];
		if(s[i]==s[j+1])j++;
		nxt[i]=j;
		if(nxt[i]==S&&i!=S)x++;
	}
	return x*nxt[S];
}
int main()
{
	scanf("%lld%lld%lld%lld",&S,&m,&b,&p);
	for(int i=1;i<=S;i++){
		scanf("%lld",&s[i]);
	}
	s[S+1]=0;
	int op;
	ll x,c;
	res=1;
	while(m--){
		scanf("%d",&op);	
		if(op==1){
			scanf("%lld%lld",&x,&c);
			x=(x^lst)%S+1,c^=lst;
			s[x]=c;
		}
		else{
			res=res*b%p;
			scanf("%lld",&T);
			for(int i=1;i<=T;i++){
				scanf("%lld",&s[i+S+1]);
				s[i+S+1]^=lst;
			}
			if(S>T)lst=0;
			else{
				lst=kmp();
				ans=(ans+lst%p*res%p)%p; 
			}
		}
	}
	printf("%lld\n",ans);
	return 0;
}

杭电第七场

\(E.Subsequence\ Not\ Substring\)
没搞懂SAM的做法,所以照着题解的分类讨论过了。好细节的分类讨论,具体写在代码里。

Subsequence Not Substring
#include
#define ll long long
using namespace std;
const int N = 210, M = 5010, inf = 1e9 + 10;
const ll INF = 1e16;
int n, m, S, x, s, t;
int cur[N], vis[N];
int ver[M << 1], head[N], tot = 1, Next[M << 1], edge[M << 1];
ll cost[M << 1], dis[M << 1], ans;
void add(int x, int y, int z, int c) {
	ver[++tot] = y;
	Next[tot] = head[x];
	head[x] = tot;
	edge[tot] = z;
	cost[tot] = c;
	ver[++tot] = x;
	Next[tot] = head[y];
	head[y] = tot;
	edge[tot] = 0;
	cost[tot] = -c;
}
bool bfs() {
	for (int i = s;i <= t;i++)cur[i] = head[i], dis[i] = INF, vis[i] = 0;
	dis[s] = 0;
	queueq;
	q.push(s);
	while (q.size()) {
		int x = q.front();
		q.pop();
		vis[x] = 0;
		for (int i = head[x];i;i = Next[i]) {
			int y = ver[i];
			if (edge[i] && dis[y] > dis[x] + cost[i]) {
				dis[y] = dis[x] + cost[i];
				if (!vis[y]) {
					vis[y] = 1;
					q.push(y);
				}
			}
		}
	}
	return (dis[t] != INF);
}
int dinic(int x, int limit) {
	if (x == t || !limit)return limit;
	int flow = 0, f;
	vis[x] = 1;
	for (int& i = cur[x];i;i = Next[i]) {
		int y = ver[i];
		if (!vis[y] && dis[y] == dis[x] + cost[i] && (f = dinic(y, min(limit, edge[i])))) {
			flow += f, limit -= f;
			edge[i] -= f, edge[i ^ 1] += f;
			ans += f * cost[i];
			if (!limit)break;
		}
	}
	vis[x] = 0;
	return flow;
}
int main()
{
	scanf("%d%d%d", &n, &m, &S);
	s = 0, t = n + 1;
	for (int i = 1;i <= n;i++) {
		scanf("%d", &x);
		if (i < n)add(i, i + 1, S, m);
		add(i, t, x, 0);
	}
	for (int i = 1;i <= n;i++) {
		scanf("%d", &x);
		add(s, i, inf, x);
	}
	while (bfs())dinic(s, inf);
	printf("%lld\n", ans);
	return 0;
}#include
using namespace std;
const int N = 2e5 + 10;
int t, n, flag, f[N][27], g[N], vis[N], num;
char s[N], A, B, C, T[N];
int main()
{
	//	freopen("input.txt","r",stdin);
	//	freopen("my.txt","w",stdout);
	scanf("%d", &t);
	while (t--) {
		scanf("%s", s + 1);
		//		printf("%s\n",s+1);
		n = strlen(s + 1);
		A = B = C = 'z' + 1;
		flag = 0;
		for (int i = 1;i <= n;i++) {
			if (s[i] != s[i - 1] && i != 1)flag++;
			if (s[i] < A)C = B, B = A, A = s[i];
			else if (s[i] < B && s[i] != A)C = B, B = s[i];
			else if (s[i] < C && s[i] != A && s[i] != B)C = s[i];//找最小的三个字符 
		}
		if (flag <= 1) {//极长相等连续段不超过2个 
			printf("-1\n");
			continue;
		}
		int cnt = 0, len = 0, pos = 0, maxx = 0;
		for (int i = 1;i <= n;i++) {
			if (s[i] == A)cnt++, len++, maxx = max(maxx, len), pos = i;
			else len = 0;
		}
		//	printf("A:%c B:%c C:%c cnt:%d pos:%d max:%d\n",A,B,C,cnt,pos,maxx);
		if (cnt != maxx) {//A的出现位置不构成一段连续区间 
			for (int i = 1;i <= maxx + 1;i++)printf("%c", A);
			printf("\n");
			continue;
		}
		if (pos == n) {//A区间的右端点为n 
			int posA = n - maxx + 1;
			maxx = cnt = len = pos = 0;
			for (int i = 1;i < posA;i++) {
				if (s[i] == B)cnt++, len++, maxx = max(maxx, len), pos = i;
				else len = 0;
			}
			//			printf("A:%c B:%c C:%c cnt:%d pos:%d max:%d\n",A,B,C,cnt,pos,maxx);
						//B的出现位置为一个区间且之后全为A,答案为CA 
			if (cnt == maxx && pos + 1 == posA)printf("%c%c\n", C, A);
			else {
				//若A之前的B是最长的一段B,最长一段B的区间长度为k,
				//答案为B(*k+1)A
				//否则A前一段的B区间长度为h,答案为B(*h+1) 
				if (pos + 1 == posA && len == maxx) {
					for (int i = 1;i <= len + 1;i++)printf("%c", B);
					printf("\n");
				}
				else {
					for (int i = 1;i <= len + 1;i++)printf("%c", B);
					printf("%c\n", A);
				}
			}
		}
		else {//A区间之后有字符 
			flag = 0;
			for (int i = pos + 2;i <= n;i++) {
				if (s[i] != s[i - 1])flag = 1;
			}
			if (flag) {//之后字符不全相同,则贪心选取答案
				//若某时刻取到的不是子串,则为答案
				//若某时刻后缀只剩下一种字符,则删去已选取的最后一个位置,然后在剩下的字符中取一个 
				g[n + 1] = 0;
				for (int i = 0;i < 26;i++)f[n + 1][i] = vis[i] = 0;
				for (int i = n;i >= 1;i--) {
					for (int j = 0;j < 26;j++) {
						f[i][j] = f[i + 1][j];
						if (s[i] == (char)(j + 'a'))f[i][j]++;
					}
					g[i] = g[i + 1];
					if (!vis[s[i] - 'a'])vis[s[i] - 'a'] = 1, g[i]++;
				}
				num = 0;
				for (int i = 1;i <= maxx;i++) {
					T[++num] = A;
				}
				pos++;
				while (pos <= n) {
					if (g[pos] == 1) {
						T[num] = s[pos];
						break;
					}
					for (int i = 0;i < 26;i++) {
						if (f[pos][i]) {
							if (s[pos] != (char)(i + 'a')) {
								T[++num] = (char)(i + 'a');
								pos = n + 1;
								break;
							}
							else {
								T[++num] = (char)(i + 'a');
								pos++;
								break;
							}
						}
					}
				}
				T[++num] = '\0';
				printf("%s\n", T + 1);
			}
			else {//A区间后的字符全相同,找到A区间前最小的字符X 
				int posA = pos - maxx + 1;
				int X = 'z' + 1;
				for (int i = 1;i < posA;i++) {
					if (s[i] < X)X = s[i];
				}
				//A区间的前一个字符不为X,答案为XA 
				if (s[posA - 1] != X)printf("%c%c\n", X, A);
				else if (cnt > 1 || X != B) {
					//A的个数不为1或X不为B,则A区间后的全是B
					//答案为X+A(*A区间长度-1)+B 
					printf("%c", X);
					for (int i = 1;i < cnt;i++)printf("%c", A);
					printf("%c\n", s[posA + cnt]);
				}
				else {
					//X在A前存在一个相邻区间,且A的个数为1 
					int cntX = 0, posX = 0, maxX = 0, lenX = 0;
					for (int i = 1;i < posA;i++) {
						if (s[i] == X)cntX++, posX = i, lenX++, maxX = max(maxX, lenX);
						else lenX = 0;
					}
					if (maxX == cntX && posX + 1 == posA) {
						//X的全部出现位置为一个与A区间相邻的区间 
						if (X != s[posA + cnt]) {//X与A后的字符不同 
							for (int i = 1;i <= cntX;i++)printf("%c", X);
							printf("%c\n", s[posA + cnt]);
						}
						else {
							//X与A后的字符相同且都为B 
							int lenB = max(cntX, n - posA - cnt + 1);
							for (int i = 1;i <= lenB + 1;i++)printf("%c", B);
							printf("\n");
						}
					}
					else {//X的出现不为一个区间或是不与A相邻,且X为B 
						int posend = posA + cnt - 1;
						maxx = cnt = len = pos = 0;
						for (int i = 1;i < posA;i++) {
							if (s[i] == B)cnt++, len++, maxx = max(maxx, len), pos = i;
							else len = 0;
						}
						int len1 = 0;
						for (int i = posend + 1;i <= n;i++) {
							if (s[i] == B)len1++, maxx = max(maxx, len1);
							else len1 = 0;
						}
						if (pos + 1 == posA && len == maxx) {
							for (int i = 1;i <= len + 1;i++)printf("%c", B);
							printf("\n");
						}
						else {
							for (int i = 1;i <= len + 1;i++)printf("%c", B);
							printf("%c\n", A);
						}
					}
				}
			}
		}
	}
	return 0;
}

杭电第八场

\(1006.Nested\ String\)
赛场上手写哈希表没过,但是想不出 \(O(n)\) 做法orz
正解是对 \(S\) 串和 \(T1\) 串求最长公共前缀 \(p_l\),对 \(S\) 串和 \(T1\) 串求最长公共后缀 \(q_r\),都是利用 \(exKMP\) 算法。在 \(S\) 每一个长为 \(|T1|+|T2|\) 的子串中,开头的 \(p_l\) 和结尾的 \(q_r\) 可以限制出一个交集,这个交集就是 \(T2\) 串可以放的地方。对 \(S\)\(kmp\)\(T2\) 所有匹配的位置,用前缀和来 \(O(1)\) 得到区间内 \(T2\) 可以放的位置的数量。

1006.Nested String
#include
#include
#include
#define ll long long
using namespace std;
const int N=2e7+10;
int t,m1,m2,n,nxt[N],sum[N],z[N],lcp[N],lcs[N];
char T1[N],T2[N],s[N];
inline void kmp(int m){
    for(int i=2;i<=m;i++){
        int j=nxt[i-1];
        while(j&&T2[j+1]!=T2[i])j=nxt[j];
        if(T2[j+1]==T2[i])nxt[i]=j+1;
    }
}
inline void exkmp(int m){
    z[1]=m1;
    int l=1;
    while(T1[l]==T1[l+1]&&l+1<=m)l++;
    z[2]=l-1;
    int pos=2,r=l-1;
    for(int i=3;i<=m;i++){
        int j=i-pos+1;
        if(z[j]+i-10?pos+r-i:0;
            while(T1[i+z[i]]==T1[z[i]+1]&&i+z[i]<=m)z[i]++;
            pos=i,r=z[i];
        }
    }
}
int main()
{
    scanf("%d",&t);
    while(t--){
        scanf("%s%s",T1+1,T2+1);
        m1=strlen(T1+1),m2=strlen(T2+1);
        scanf("%s",s+1);
        n=strlen(s+1);
        for(int i=m2+2;i<=m2+n+1;i++)T2[i]=s[i-m2-1];
        T2[m2+1]='.',T2[m2+n+2]='\0';
        for(int i=1;i<=n+m2+1;i++)nxt[i]=0;
        kmp(m2+n+1);
        int len=m2<<1;
        for(int i=1;i<=n;i++){
            sum[i]=(nxt[i+len]==m2);
            sum[i]+=sum[i-1];
        }
        for(int i=m1+2;i<=m1+n+1;i++)T1[i]=s[i-m1-1];
        T1[m1+1]='.',T1[m1+n+2]='\0';
        exkmp(m1+n+1);
        for(int i=m1+2;i<=m1+n+1;i++)lcp[i-m1-1]=z[i];
        for(int i=1;i<=m1/2;i++)swap(T1[i],T1[m1-i+1]);
        for(int i=1;i<=n/2;i++)swap(T1[i+m1+1],T1[n-i+m1+2]);
        exkmp(m1+n+1);
        for(int i=m1+2;i<=m1+n+1;i++)lcs[n-i+m1+2]=z[i];
        ll ans=0;
        for(int L=1,R=m1+m2;R<=n;L++,R++){
            int l0=R-lcs[R]-m2+1,r0=L+lcp[L];
            if(l0>r0)continue;
            ans+=sum[r0]-sum[l0-1];
        }
        printf("%lld\n",ans);
    }
    return 0;
}

牛客第八场

\(I.Make\ It\ Square\)
正解用到了KMP,不过双哈希+分类讨论能过。用队友的思路,可以分类讨论串二分之一的位置,然后用哈希验证重叠部分。赛场上分类讨论少了个细节,然后没来得及写双哈希orz

I.Make It Square
#include
#define ull unsigned long long
#define ll long long
using namespace std;
const int N=1e6+10,p=31,mod=998244353,p1=27,mod1=1e9+7;
int m,S,T;
char s[N],t[N];
ull hasS[N],hasT[N],tag[N];
ll hasS1[N],hasT1[N],tag1[N];
ll pw(ll x,ll p){
    if(p<0)return 0;
    ll num=1;
    while(p){
        if(p&1)num=num*x%mod;
        x=x*x%mod;
        p>>=1;
    }
    return num;
}
ull HasT(int l,int r){
    if(l>r)return 0;
    return hasT[r]-hasT[l-1]*tag[r-l+1];
}
ull HasS(int l,int r){
    if(l>r)return 0;
    return hasS[r]-hasS[l-1]*tag[r-l+1];
}
ll HasT1(int l,int r){
    if(l>r)return 0;
    return (hasT1[r]-hasT1[l-1]*tag1[r-l+1]%mod1+mod1)%mod1;
}
ll HasS1(int l,int r){
    if(l>r)return 0;
    return (hasS1[r]-hasS1[l-1]*tag1[r-l+1]%mod1+mod1)%mod1;
}
int main()
{
    scanf("%d",&m);
    scanf("%s",s+1);
    scanf("%s",t+1);
    tag[0]=tag1[0]=1;
    S=strlen(s+1),T=strlen(t+1);
    for(int i=1;i<=S;i++)hasS[i]=hasS[i-1]*p+s[i],hasS1[i]=(hasS1[i-1]*p1%mod1+s[i])%mod1;
    for(int i=1;i<=T;i++)hasT[i]=hasT[i-1]*p+t[i],hasT1[i]=(hasT1[i-1]*p1%mod1+t[i])%mod1;
    for(int i=1;i<=max(S,T);i++)tag[i]=tag[i-1]*p,tag1[i]=tag1[i-1]*p1%mod1;
    int len;
    for(int i=1;i<=m;i++){
        if((S+T)&1){
            printf("0%c"," \n"[i == m]);
            continue;
        }
        len=(S+T+2*i)/2;
        if(i+S==len){
            if(hasS[S]==hasT[T]&&hasS1[S]==hasT1[T])printf("%lld%c",pw(26,i)," \n"[i == m]);
            else printf("0%c"," \n"[i == m]);
        }
        else if(i*2+S==len){
            if(hasS[S]==HasT(i+1,i+S)&&hasS1[S]==HasT1(i+1,i+S))printf("1%c"," \n"[i == m]);
            else printf("0%c"," \n"[i == m]);
        }
        else if(len>i+S&&leni&&len=i+S-len+1?min(len-T+1,i)-(i+S-len+1)+1:0)," \n"[i == m]);
            }
            else printf("0%c"," \n"[i == m]);
        }
        else{
        //	printf("l:%d r:%d\n",i+T-len+i,i+T-len+S-1+i);
            if(hasS[S]==HasT(i+T-len+1,i+T-len+S)&&hasS1[S]==HasT1(i+T-len+1,i+T-len+S)&&HasT(1,T-len)==HasT(len+1,T)&&HasT1(1,T-len)==HasT1(len+1,T))printf("1%c"," \n"[i == m]);
            else printf("0%c"," \n"[i == m]);
        }
    }
    return 0;
}
posted @ 2023-08-07 22:33  Chloris_Black  阅读(34)  评论(0编辑  收藏  举报