[冲刺国赛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);
}
}