[冲刺国赛2022] 模拟赛12

牌佬

题目描述

给定一棵大小为 \(n\) 的树,判断是否存在三个点 \((x,y,z)\) 满足:

  • \(y\)\((x,z)\) 的简单路径上。
  • \(x+z=2y\)

\(n\leq 2^{20}\)

解法

首先考虑序列上怎么做,我们枚举点 \(y\),然后考察有解的条件。我们把序列上在 \(y\) 左侧的点设置为 \(1\),在 \(y\) 右侧的点设置为 \(0\),那么在值域序列以 \(y\) 中心构成回文串(抵着值域边界)就说明无解。这是因为如果不构成回文串,那么势必有一对对称的 \((0,1)\),满足 \(x+z=2y\),并且在原序列上是经过 \(y\) 的。

把上面的做法搬到序列上,现在我们要判断对于点 \(y\) 和在子树 \(S\) 处是否有解,把子树 \(S\) 设置为 \(1\) 即可。

但是把子树设置为 \(1\) 不太好维护,考虑把 \(y\) 在值域序列上的回文串设置为 \(1\),然后判断在子树 \(S\) 中是否出现了正反的回文序列。那么扫描值域,预处理出树上的 \(\tt dfn\) 序,然后用树状数组维护哈希值即可,时间复杂度 \(O(n\log n)\)

#include <cstdio>
#include <vector>
#include <cstring>
#include <iostream>
using namespace std;
const int M = 1100005;
const int MOD = 998244353;
#define ll long long
#define pb push_back
int read()
{
	int x=0,f=1;char c;
	while((c=getchar())<'0' || c>'9') {if(c=='-') f=-1;}
	while(c>='0' && c<='9') {x=(x<<3)+(x<<1)+(c^48);c=getchar();}
	return x*f;
}
int n,cnt,dfn[M],siz[M],pw[M],ip[M],fa[M];
vector<int> g[M];int k,s[M],b[M];
struct fenwick
{
	int b[M];
	fenwick() {memset(b,0,sizeof b);}
	void add(int x,int c)
	{
		for(int i=x;i<=n;i+=i&(-i))
			b[i]=(b[i]+c)%MOD;
	}
	int ask(int x)
	{
		int r=0;
		for(int i=x;i>0;i-=i&(-i))
			r=(r+b[i])%MOD;
		return r;
	}
	int ask(int l,int r)
	{
		if(l>r) return 0;
		return (ask(r)-ask(l-1)+MOD)%MOD;
	}
}A,B;
void dfs(int u,int p)
{
	dfn[u]=++cnt;siz[u]=1;fa[u]=p;
	for(int v:g[u]) if(v^p)
		dfs(v,u),siz[u]+=siz[v];
}
void get(int u,int p)
{
	s[++k]=u;
	for(int v:g[u]) if(v^p)
		get(v,u);
}
void print(int u)
{
	for(int v:g[u])
	{
		k=0;get(v,u);
		for(int i=1;i<=k;i++)
		{
			int x=2*u-s[i];
			if(x>=1 && x<=n && b[x])
			{
				printf("YES %d %d %d\n",x,u,s[i]);
				return ;
			}
		}
		for(int i=1;i<=k;i++)
			b[s[i]]=1;
	}
}
signed main()
{
	freopen("gangster.in","r",stdin);
	freopen("gangster.out","w",stdout);
	n=read();
	for(int i=1;i<n;i++)
	{
		int u=read(),v=read();
		g[u].pb(v);g[v].pb(u);
	}
	dfs(1,0);pw[0]=ip[0]=1;
	for(int i=1;i<=n;i++)
	{
		pw[i]=pw[i-1]*3ll%MOD;
		ip[i]=ip[i-1]*332748118ll%MOD;
	}
	int p=1,q=1,s=2,t=2;
	for(int i=1;i<=n;i++)
	{
		while(p<i) A.add(dfn[p],ip[p]),p++;
		while(2*i-q>n && q<i) A.add(dfn[q],MOD-ip[q]),q++;
		while(s<=i) B.add(dfn[s],MOD-pw[s]),s++;
		while(2*i-t>=1 && t<=n) B.add(dfn[t],pw[t]),t++;
		for(int v:g[i]) if(v^fa[i])
		{
			int l=A.ask(dfn[v],dfn[v]+siz[v]-1);
			int r=B.ask(dfn[v],dfn[v]+siz[v]-1);
			l=(ll)l*pw[i]%MOD*pw[i]%MOD;
			if(l!=r) {print(i);return 0;}
		}
	}
	puts("NO");
}

唱诗

题目描述

给定一棵 \(n\) 个点的 \(\tt trie\) 树,定义子串为 \(\tt trie\) 树上一条从上到下的路径所代表的字符串。

\(q\) 次询问,每次查询字典序第 \(k\) 小的非空子串,只需要输出子串的 \(\tt ASCII\) 码之和即可。

\(n\leq 2\cdot 10^5,q\leq 5\cdot 10^5\),保证只会出现 \(26\) 个小写字母。

解法

根据 \(\tt trie\) 树可以直接建出广义后缀自动机(还省去了你多模板串建立 \(\tt trie\) 树的过程),然后在后缀自动机上搜就可以找到第 \(k\) 小的串(“搜”类似于线段树二分),这样可以做到 \(O(nq)\) 的复杂度。

上面做法的本质是在一个 \(\tt DAG\) 上搜第 \(k\) 小。考虑倍增加速,首先要对每个节点确定一个后继节点。这个后继节点可以选择子串数最多的后继,有一个很好的性质是:如果不走到这个后继,那么当前节点的子串数折半

既然已经确定了唯一的后继,我们可以直接倍增。如果倍增不动了就用暴力的方法跳一步,时间复杂度 \(O(q\log^2 n)\)虽然有更为优秀的做法,但是这种做法已经可以卡过去了

#include <cstdio>
#include <vector>
#include <cstring>
#include <iostream>
#include <queue>
using namespace std;
const int M = 400005;
#define ll long long
int read()
{
	int x=0,f=1;char c;
	while((c=getchar())<'0' || c>'9') {if(c=='-') f=-1;}
	while(c>='0' && c<='9') {x=(x<<3)+(x<<1)+(c^48);c=getchar();}
	return x*f;
}
int n,q,cnt=1,lt[M],fa[M],fc[M];
ll sz[M],h[M][26],f[M][20],g[M][20];
int nx[M][20],z[M][20];vector<int> e[M];
struct node{int fa,len,ch[26];}a[M];
int add(int p,int c)
{
	int np=++cnt;
	a[np].len=a[p].len+1;
	for(;p && !a[p].ch[c];p=a[p].fa)
		a[p].ch[c]=np;
	if(!p) a[np].fa=1;
	else
	{
		int q=a[p].ch[c];
		if(a[p].len+1==a[q].len) a[np].fa=q;
		else
		{
			int nq=++cnt;a[nq]=a[q];
			a[nq].len=a[p].len+1;
			a[q].fa=a[np].fa=nq;
			for(;p && a[p].ch[c]==q;p=a[p].fa)
				a[p].ch[c]=nq;
		}
	}
	return np;
}
void build()
{
    queue<int> q;lt[1]=1;
    for(int v:e[1]) q.push(v);
    while(!q.empty())
    {
        int u=q.front();q.pop();
        lt[u]=add(lt[fa[u]],fc[u]);
        for(int v:e[u]) q.push(v);
    }
}
void dfs(int u)
{
	if(!u || sz[u]) return ;
	sz[u]=f[u][0]=1;int son=0,c=0;
	for(int i=0;i<26;i++)
	{
		int v=a[u].ch[i];dfs(v);
		sz[u]+=sz[v];h[u][i]=sz[u]-1;
		if(sz[v]>sz[son]) son=v,c=i;
	}
	nx[u][0]=son;z[u][0]=c+'a';
	for(int i=0;i<c;i++) f[u][0]+=sz[a[u].ch[i]];
	g[u][0]=f[u][0]+sz[son];
	for(int i=1;i<=18;i++)
	{
		nx[u][i]=nx[nx[u][i-1]][i-1];
		z[u][i]=z[u][i-1]+z[nx[u][i-1]][i-1];
		f[u][i]=f[u][i-1]+f[nx[u][i-1]][i-1];
		g[u][i]=f[u][i-1]+g[nx[u][i-1]][i-1];
	}
}
signed main()
{
	freopen("hymn.in","r",stdin);
	freopen("hymn.out","w",stdout);
	n=read();q=read();
	for(int i=1;i<n;i++)
	{
		int u=read(),v=read();
		char s[5];scanf("%s",s);
		fa[v]=u;fc[v]=s[0]-'a';
		e[u].push_back(v);
	}
	build();dfs(1);
	while(q--)
	{
		ll k;scanf("%lld",&k);
		if(k>=sz[1]) {puts("-1");continue;}
		int x=1,ans=0;k++;// 1 contains empty string
		while(k>1)
		{
			for(int i=18;i>=0;i--) if(nx[x][i])
				if(k>f[x][i] && k<=g[x][i])
					ans+=z[x][i],k-=f[x][i],x=nx[x][i];
			if(k==1) break;k--;
			int t=lower_bound(h[x],h[x]+26,k)-h[x];
			if(t>0) k-=h[x][t-1];
			x=a[x].ch[t];ans+=t+'a';
		}
		printf("%d\n",ans);
	}
}
posted @ 2022-07-16 15:59  C202044zxy  阅读(340)  评论(9编辑  收藏  举报