省选模拟测试21
wdnmd,真就全都不会做呗。
T1 字符串值
题意描述
定义 \(S[l:r]\) 表示字符串 \(S\) 中第 \(l\) 个到第 \(r\) 个字符构成的子串,\(|S|\) 表示字符串 \(S\) 的长度。
对于字符串 \(S\),我们定义 \(fail_i\) 等于最大的 \(x\),满足 \(x<i\) 且 \(s[1:x] = s[i-x+1:i]\) 。将每个 \(0\leq i\leq |S|\) 看成结点,除 \(i=0\) 以外,\(i\) 向 \(fail_i\) 连边,会形成一颗树,根是 \(0\)(根深度默认为 \(-1\)),我们将其称为 \(fail\) 树。
定义 \(F(S)\) 为 \(S\) 的 \(fail\) 树上的除了 \(0\) 号节点外所有节点的深度和。定义 \(\displaystyle G(S) = \sum_{1\leq l\leq r\leq |S|} F(S[l:r])\),即对于 \(S\) 所有子串的 \(F\) 求和。
一开始有一个空串 \(S\),接下来有 \(n\) 次操作,每次在空串末尾添加一个小写字母,要求你能够快速求出 。由于答案很大,请对 \(1e9+7\) 取模。
数据范围:\(n\leq 2\times 10^5\)
solution
后缀自动机套树链剖分。
对于一个字符串 \(s\), 他的 \(fail\) 树上的一个节点 \(i\), 对应的是 \(s\) 的一个前缀,即 \(s[1....i]\) 。\(i\) 的深度则表示有多少个 \(j\) 满足 \(s[1...j] = s[i-j+1...i]\) 。那么 \(F(s)\) 则表示 \(s\) 的前缀的所有符合条件的 \(j\) 的数量。
根据这个我们考虑 \(G(s)\) 怎么算,假设 \(s\) 的两个子串 \(s_1,s_2\) 相等,对应的区间分别为:\([l_1,r_1],[l_2,r_2]\)即:
不难发现 \(s[l_1,r_2]\) 有一个符合条件的 \(j\) 使得:\(s_1 = s_2\) 。同时 \(s[l_1,r_2]\) 可以作为 \(s[l_1,r]\) (\(r_2\leq r\leq len(s)\))前缀。所以 \(s[l_1,r_2]\) 对 \(G(s)\) 的贡献则为:\(len(s)-r_2+1\) 。
对于这道题,我们考虑离线处理。先求出以 \(i\) 为右端点的 \(s_2\) 满足条件的 \((s_1,s_2)\) 的数量,设其为 \(h(i)\), 求一遍前缀和就可以得到以 \(i\) 为右端点的字符串的 \(F(s)\) 的和,在求一遍前缀和就可以得到 \(G(s[1..i])\) 。
先用后缀自动机建出 \(partent\) 树来。然后考虑每加入一个字符串产生的贡献。
设当前字符串 \(s\) 在后缀自动机上对应的节点为 \(x\) ,那么 \(x\) 到根节点的路径上的点就可以表示 \(s\) 的所有后缀。
显然这些后缀都可以作为 \(s_2\) ,对应的 \(s_1\) 的个数即为 \(s_2\) 在 \(s[1...i-1]\) 出现的次数。
设 \(cnt(u)\) 表示没加入第 \(i\) 个字符之前的 \(u\) 的 \(endpos\) 的大小。
则有:\(h(i) = \displaystyle\sum_{u\in\{x的祖先\}} cnt(u)\times (maxlen(u)-maxlen(fa(u)))\)
\(maxlen(u)\) 在建后缀自动机的时候就可以统计出来。
\(cnt(u)\) 表示的是 \(u\) 的子树中 \(np\) 节点的个数,每次把 \(x\) 到根节点的路径上的点的 \(cnt(u)+1\) 即可。
用线段树和树链剖分就可以快速维护出 \(cut(u)\) 。
Code
#include<iostream>
#include<cstdio>
#include<algorithm>
#include<cstring>
using namespace std;
const int p = 1e9+7;
const int N = 4e5+10;
int n,m,tot,cnt,last,num;
int head[N],len[N],link[N],tr[N][26],st[N];
int dep[N],fa[N],top[N],siz[N],son[N],ans[N],dfn[N],id[N];
char s[N];
struct node
{
int to,net;
}e[N<<1];
struct Tree
{
int lc,rc;
int sum,w,tag;
}Tr[N<<2];
#define l(o) Tr[o].lc
#define r(o) Tr[o].rc
#define sum(o) Tr[o].sum
#define w(o) Tr[o].w
#define tag(o) Tr[o].tag
void add(int x,int y)
{
e[++tot].to = y;
e[tot].net = head[x];
head[x] = tot;
}
void Extend(int ch)
{
int cur = ++cnt, p = last;
len[cur] = len[last] + 1;
for(; p && !tr[p][ch]; p = link[p]) tr[p][ch] = cur;
if(!p) link[cur] = 1;
else
{
int x = tr[p][ch];
if(len[x] == len[p] + 1) link[cur] = x;
else
{
int y = ++cnt;
len[y] = len[p] + 1;
memcpy(tr[y],tr[x],sizeof(tr[x]));
link[y] = link[x];
link[x] = link[cur] = y;
while(p && tr[p][ch] == x)
{
tr[p][ch] = y;
p = link[p];
}
}
}
last = cur;
}
void get_tree(int x)
{
dep[x] = dep[fa[x]] + 1; siz[x] = 1;
for(int i = head[x]; i; i = e[i].net)
{
int to = e[i].to;
if(to == fa[x]) continue;
fa[to] = x;
get_tree(to);
siz[x] += siz[to];
if(siz[to] > siz[son[x]]) son[x] = to;
}
}
void dfs(int x,int topp)
{
top[x] = topp; dfn[x] = ++num; id[dfn[x]] = x;
if(son[x]) dfs(son[x],topp);
for(int i = head[x]; i; i = e[i].net)
{
int to = e[i].to;
if(to == fa[x] || to == son[x]) continue;
dfs(to,to);
}
}
void up(int o)
{
w(o) = (w(o<<1) + w(o<<1|1)) % p;
sum(o) = (sum(o<<1) + sum(o<<1|1)) % p;
}
void cover(int o,int val)
{
tag(o) += val;
sum(o) = (sum(o) + w(o) * val % p) % p;
}
void down(int o)
{
if(tag(o))
{
cover(o<<1,tag(o));
cover(o<<1|1,tag(o));
tag(o) = 0;
}
}
void build(int o,int L,int R)
{
l(o) = L, r(o) = R;
if(L == R)
{
int x = id[L];
w(o) = len[x]-len[link[x]];
return;
}
int mid = (L+R)>>1;
build(o<<1,L,mid);
build(o<<1|1,mid+1,R);
up(o);
}
void chenge(int o,int L,int R)
{
if(L <= l(o) && R >= r(o))
{
cover(o,1);
return;
}
down(o);
int mid = (l(o)+r(o))>>1;
if(L <= mid) chenge(o<<1,L,R);
if(R > mid) chenge(o<<1|1,L,R);
up(o);
}
int query(int o,int L,int R)
{
int res = 0;
if(L <= l(o) && R >= r(o)) return sum(o);
down(o);
int mid = (l(o) + r(o))>>1;
if(L <= mid) res = (res + query(o<<1,L,R)) % p;
if(R > mid) res = (res + query(o<<1|1,L,R)) % p;
return res;
}
void modify(int x,int y)
{
while(top[x] != top[y])
{
if(dep[top[x]] < dep[top[y]]) swap(x,y);
chenge(1,dfn[top[x]],dfn[x]);
x = fa[top[x]];
}
if(dep[x] >= dep[y]) swap(x,y);
chenge(1,dfn[x],dfn[y]);
}
int get_ans(int x,int y)
{
int res = 0;
while(top[x] != top[y])
{
if(dep[top[x]] < dep[top[y]]) swap(x,y);
res = (res + query(1,dfn[top[x]],dfn[x])) % p;
x = fa[top[x]];
}
if(dep[x] >= dep[y]) swap(x,y);
res = (res + query(1,dfn[x],dfn[y]));
return res;
}
int main()
{
freopen("string.in","r",stdin);
freopen("string.out","w",stdout);
scanf("%d%s",&n,s+1);
last = cnt = 1;
for(int i = 1; i <= n; i++) Extend(s[i]-'a'), st[i] = last;
for(int i = 2; i <= cnt; i++) add(link[i],i), add(i,link[i]);
get_tree(1); dfs(1,1); build(1,1,cnt);
for(int i = 1; i <= n; i++)
{
ans[i] = get_ans(1,st[i]);
modify(1,st[i]);
}
for(int i = 1; i <= n; i++) ans[i] = (ans[i] + ans[i-1]) % p;
for(int i = 1; i <= n; i++)
{
ans[i] = (ans[i] + ans[i-1]) % p;
printf("%d\n",ans[i]);
}
fclose(stdin); fclose(stdout);
return 0;
}
T2 花钟计数
题意描述
花卉时钟一直矗立在镜湖边。尽管不能留住时间,但它提醒人们时间的过去,使人们想起过去美好的时光。
在花卉时钟的边缘一共有 \(2n\) 朵花,顺时针依次编号为 \(1\) 到 \(2n\),每一朵花的颜色为 \(n\) 种可能的颜色中的一种。对于每种颜色,恰有两朵花是这种颜色的,这两朵花的距离要么不大于 \(2\),要么等于 \(n\)。另外,如果花 \(u\) 和花 \(v\) 有相同的颜色,那么 \(u\) 对面的那一朵花和 \(v\) 对面的那一朵花的颜色也应该相同——对称是美的!
形式化的说,两朵花之间的距离是 \(1\) 加上夹在以这两朵花为端点的劣弧(或者半圆)之间的花的数目。下面是当 \(n=6\) 时一种可能的安排颜色的方式:
我们定义一种安排方式的美感为被所有相对(距离为 \(n\))且颜色相同的花分开的连续段的长度的乘积。换句话说,为了计算美感,我们先在环上移除那些与相对的花颜色相同的花。然后,它的美感就是剩下的连续段长度的乘积。注意到本题中包括了长度为 \(0\) 的那些连续段。在某种安排方式中,如果没有任何一朵花使得与它相对的花的颜色与它相同,那么这种安排方式的美感为 00。比如说,上图的安排方式的美感等于 \(1\times 3\times 1\times 3=9\)——这些连续段为 \(\{2\},\{4,5,6\},\{8\},\{10,11,12\}\)。
尽管保持了这些约束条件,但是可能的安排方式会有多种,你需要计算出所有可能的安排方式的美感之和,对 \(998244353\) 取模。两个安排方式被视为不同的,当且仅当存在一个二元组 \((u,v)(1\leq u,v\leq 2n)\) 使得在其中一种安排方式中花 \(u\) 和花 \(v\) 颜色相同而在另一种安排方式中颜色不同。
数据范围:\(n\leq 50000\)
solution
计数套分治 \(NTT\)
自己的计数水平大概是小学生水平,看了半天题解都没看懂 /cy。
T3 平面标记
题意描述
平面上有 \(n\) 个点,第 \(i\) 个点的坐标为 \((x_i,y_i)\)。
你会依次收到 \(m\) 个操作,第 \(i\) 个操作形如 \(k_i,a_i\),表示你需要标记所有满足 \(y_j>{k_i\over x_j^{a_i}}\) 的点 \(j\)。
在执行完所有操作后,你想知道每个点在哪次操作中第一次被标记。
数据范围:\(1\leq n\leq m,0<x,y<1000,0<k<10^6,0<a<10\)。
solution
线段树分治+半平面交或者整体二分套半平面交。
考虑先转化一下条件,即:
\(y_j>{k_i\over x_j^{a_i}} \Leftrightarrow y_j\times x_j^{a_i} > k_i\) 。
对于两个大数比较大小,我们有一个很常用的技巧就是分别对其去 \(ln\),则有:
\(\ln y_j + a_i \ln x_j > \ln k_i\)
这显然是一个半平面交的形式。将询问的点 \((x,y)\) 变为 \((\ln x,\ln y)\) ,并把所有的限制条件取反,二分一个答案 \(t\),
然后问题就转化为判断前 \(t\) 条直线所组成的半平面是否包含 \((\ln x,\ln y)\) 这个点。
注意到给定的半平面一定是一条斜率小于 0 的直线下方的区域,因此组成的半平面交一定是一个上凸壳下方的区域,可以直接在凸壳上二分得到横坐标范围包含询问点的线段来判断。
具体地,我们对所有半平面维护一棵线段树,在线段树上的每个结点记录该结点对应区间的上凸壳,询问一个点时就直接在线段树上二分。
时间复杂度:\(O(nlogn^2)\) 。
代码
//太难写了,咕咕咕