STAOI Conclusion Masters #2 原定T3(UTR #3 T2)
STAOI怎么又原了,把题解发出来吧
首先一个比较简单的结论是无论删除顺序如何,对于任何一个子串只要不断删除可以删除的数最后剩下的序列都一样。
这个东西的证明比较简单,就是删除一对数之后只会让后面的数被删除的机会更多,除非删除的字符旁边和删除的字符相等,而相等的情况显然选择任何一组字符消除剩下的局面都完全一样。
下面设 \(f(s)\) 为仅考虑子串 \(s\) 消除之后的串,用 \([l,r]\) 表示 \(S\) 的下标为 \(l \sim r\) 的子串。
这是个数据结构题,根据这个结论,我们可以考虑使用线段树或者分块维护,用线段树举例,我们建立一棵线段树,每个结点代表的是整个结点的答案以及贪心删除之后的序列,合并两个结点的时候只需要知道串拼起来之后以两个串的分界点为中心的回文串长度就好了(这次消除的部分)。
举个例子:将 \(\text{ABABAC}\) 与 \(\text{CABAC}\) 合并,那么最后生成的串肯定是 \(\text{ABC}\),中间的会被消除。
维护串的哈希值,每次二分找这个长度就好了,时间复杂度 \(O(q \log^2 n)\),空间是 \(O(n \log n)\),预计通过 50 分。
当然也可以通过分块之类的东西做到根号,感觉是能过 50 分的吧,因为数据里都没特意卡,基本上是跑不满的。
不过其实最后一个 subtask 也没有特意去卡前面两个做法,因为常数也不是特别小,感觉没啥希望能冲过 \(10^6\) 啊,要卡满也非常简单,随便构造一组答案全是 \(0\) 的就好了。
我们刚才忽略了一个结论,就是其实这个答案可以通过某种方式差分。
我们考虑两个前缀 \([1,l-1]\) 和 \([1,r]\),将它们的答案相减之后会得到 \([l,r]\) 的答案加上 \([1,l-1]\) 和 \([l,r]\) 分别区间内匹配之后两个区间互相匹配的答案。
所以我们只要知道这个两个区间互相匹配的答案即可,给出结论,它等于 \(f([1,l-1])\) 减去 \(f([1,l-1])\) 和 \(f([1,r])\) 的最长公共前缀。
考虑证明,首先我们要知道 "两个区间互相匹配的答案" 和 "\([l,r]\) 内匹配的答案" 加和是定值,而 \([l,r]\) 内匹配的答案的定义里是需要最大化的,所以 "两个区间互相匹配的答案" 必然要尽量小,然后后面的非公共部分显然需要让后面的消除,所以这部分是不能省去的,所以原结论成立。
那么我们直接建立一棵 trie 树来维护公共前缀,求解 LCA 即可。
套一个由乃救爷爷的 \(O(n)-O(1)\) LCA 就能到线性,但是不一定能跑过树剖,然后建 trie 树的时候用 map 比直接开数组要快。
代码比较清新(这份写的树剖):
#include <bits/stdc++.h>
using namespace std;
using ull=unsigned long long;
using llt=long long;
const int N = 2e6 + 233;
const uint64_t M = 1000000000000357;
const ull Base=131;
struct Generator
{
using u64 = uint64_t;
int n; u64 sd;
inline u64 S(u64 x){return x += 0x9e3779b97f4a7c15, x = (x ^ (x >> 30)) * 0xbf58476d1ce4e5b9, x = (x ^ (x >> 27)) * 0x94d049bb133111eb, x ^ (x >> 31);}
inline u64 X(){return sd ^= sd << 13, sd ^= sd >> 7, sd ^= sd << 17;}
inline pair<int, int> rnd(int lastans)
{
sd ^= lastans;
if (X() & 1)
{
int c = n / 5 * 4 + X() % (n / 5), l = X() % (n - c) + 1;
return {l, l + c};
}
else
{
int l = X() % n + 1, r = X() % n + 1;
return {min(l, r), max(l, r)};
}
}
explicit Generator(u64 seed, int len) : sd(S(seed)), n(len){}
};
int n, q, a[N];
uint64_t seed;
string s;
struct GRAPH
{
int head[N],nxt[N<<1],to[N<<1],siz;
void add(int st,int ed){to[++siz]=ed,nxt[siz]=head[st],head[st]=siz;}
}G;
int v[N],is[N];
struct TREE
{
int fa[N],h[N],top[N],son[N],siz[N],R[N][91],Q[N],sz=1;
int build(int now,int s=-1)
{
if(s==-1) now=fa[now];
else
{
if(R[now][s]) now=R[now][s];
else R[now][s]=++sz,fa[sz]=now,G.add(now,sz),Q[sz]=Q[now]+s,now=sz;
}
return now;
}
void dfs1(int now,int H)
{
h[now]=H;siz[now]=1;
for(int i=G.head[now];i;i=G.nxt[i])
{
dfs1(G.to[i],H+1);siz[now]+=siz[G.to[i]];
if(siz[G.to[i]]>siz[son[now]]) son[now]=G.to[i];
}
}
void dfs2(int now,int t)
{
top[now]=t;if(son[now]) dfs2(son[now],t);
for(int i=G.head[now];i;i=G.nxt[i])
if(G.to[i]!=son[now]) dfs2(G.to[i],G.to[i]);
}
int LCA(int x,int y)
{
while(top[x]!=top[y])
{
if(h[top[x]]<h[top[y]]) swap(x,y);
x=fa[top[x]];
}
if(h[x]>h[y]) swap(x,y);
return Q[x];
}
}tree;
void prework()
{
is[0]=1;vector<int> vec;vec.push_back(-1);
for(int i=1;i<=n;i++)
if(a[i]==vec.back()) vec.pop_back(),v[i]=v[i-1]+a[i],is[i]=tree.build(is[i-1]);
else vec.push_back(a[i]),v[i]=v[i-1],is[i]=tree.build(is[i-1],a[i]);
tree.dfs1(1,0);tree.dfs2(1,1);
}
int query(int l, int r)
{
int res=v[r]-v[l-1];
res-=tree.Q[is[l-1]];res+=tree.LCA(is[l-1],is[r]);
return res;
}
int main()
{
cin.tie(nullptr) -> sync_with_stdio(false);
cin >> n >> q >> seed >> s;
Generator rg(seed, n);
for (int i=1; i<=n; i++) a[i] = s[i - 1] - 32;
prework();
int lastans = 0;
uint64_t qwq = 0;
for (int i=1; i<=q; i++)
{
int l, r; tie(l, r) = rg.rnd(lastans);
qwq = (qwq * 131 + (lastans = query(l, r))) % M;
}
cout << qwq << endl;
return 0;
}
原题代码,思路啥的都差不多,贡献不太一样
#include "mythological.h"
#include <bits/stdc++.h>
using namespace std;
using ull=unsigned long long;
using llt=long long;
const int N = 2e6 + 233;
struct GRAPH
{
int head[N],nxt[N<<1],to[N<<1],siz;
void add(int st,int ed){to[++siz]=ed,nxt[siz]=head[st],head[st]=siz;}
}G;
int v[N],is[N];
struct TREE
{
int fa[N],h[N],top[N],son[N],siz[N],Q[N],sz=1;map<int,int> R[N];
int build(int now,int s=-1)
{
if(s==-1) now=fa[now];
else
{
if(R[now][s]) now=R[now][s];
else R[now][s]=++sz,fa[sz]=now,G.add(now,sz),Q[sz]=Q[now]+1,now=sz;
}
return now;
}
void dfs1(int now,int H)
{
h[now]=H;siz[now]=1;
for(int i=G.head[now];i;i=G.nxt[i])
{
dfs1(G.to[i],H+1);siz[now]+=siz[G.to[i]];
if(siz[G.to[i]]>siz[son[now]]) son[now]=G.to[i];
}
}
void dfs2(int now,int t)
{
top[now]=t;if(son[now]) dfs2(son[now],t);
for(int i=G.head[now];i;i=G.nxt[i])
if(G.to[i]!=son[now]) dfs2(G.to[i],G.to[i]);
}
int LCA(int x,int y)
{
while(top[x]!=top[y])
{
if(h[top[x]]<h[top[y]]) swap(x,y);
x=fa[top[x]];
}
if(h[x]>h[y]) swap(x,y);
return Q[x];
}
}tree;
void init(int n, int m, int a[], int t);
{
is[0]=1;vector<int> vec;vec.push_back(-1);
for(int i=1;i<=n;i++)
if(a[i]==vec.back()) vec.pop_back(),v[i]=v[i-1]+a[i],is[i]=tree.build(is[i-1]);
else vec.push_back(a[i]),v[i]=v[i-1],is[i]=tree.build(is[i-1],a[i]);
tree.dfs1(1,0);tree.dfs2(1,1);
}
int query(int l, int r)
{
int res=v[r]-v[l-1];
res-=tree.Q[is[l-1]];res+=tree.LCA(is[l-1],is[r]);
return res;
}
但是原题数据范围太小, \(\log^2\) 直接过了。
浙公网安备 33010602011771号