「HNOI2019」JOJO-主席树+kmp自动机

Description

两种操作

  • 1 x c 在当前字符串末尾添加 x x x c c c字符。
  • 2 x 把当前字符串变为第 x x x次操作之后的状态。

每次操作后,输出当前字符串的 n e x t next next数组只和。

操作数 ≤ 1 0 5 \leq 10^5 105 c ≤ 1 0 4 c \leq 10^4 c104

Solution

2操作显然可以用离线建树解决。

考虑如果一段前缀匹配一段后缀,那么除了第一段的字符,其他段的二元组 ( x , c ) (x,c) (x,c)需要满足完全相等。这本质上可以将 ( x , c ) (x,c) (x,c)视为一种特殊的字符进行匹配。

先考虑在当前串的末尾新加入一个二元组 ( x , c ) (x,c) (x,c)怎么计算答案,显然是不断跳 n e x t next next,如果当前前缀后接的字符为 c c c,那么可以增加一段首项为当前前缀长度,公差为 1 1 1的等差数列的贡献(要与之前的贡献取 m a x max max,也就是只能覆盖之前没覆盖的位置)。

而如果在 n e x t next next链上有恰好等于 ( x , c ) (x,c) (x,c)的二元组,那么当前串结尾的 n e x t next next指向最靠后的二元组,否则指向 0 0 0。因为如果存在一个二元组 ( x ′ , c ) (x',c) (x,c)满足 x ′ > x x'>x x>x,那么由于下次添加的字符一定与当前字符不同,所以一定无法匹配。

但是由于 k m p kmp kmp的复杂度是均摊的,所以直接把上述算法套进树上dfs是行不通的。

考虑一个叫 k m p kmp kmp自动机的东西,它的本质是把 k m p kmp kmp n e x t next next的过程预处理,由于本题字符集大小很大,用主席树维护。

f i , j , k f_{i,j,k} fi,j,k表示在串的 s i − 1 s_{i-1} si1位置添加一个字符 ( j , k ) (j,k) (j,k) n e x t next next所到达的位置,同理设 g i , j , k g_{i,j,k} gi,j,k表示增加的答案。在dfs的时候,修改 f i , x , c f_{i,x,c} fi,x,c的值,并将 g i , x , 1.. c g_{i,x,1..c} gi,x,1..c设置为首项为当前串长度,公差为 1 1 1的等差数列。dfs下一层前把 f i + 1 f_{i+1} fi+1的状态由 f n e x t [ i ] + 1 f_{next[i]+1} fnext[i]+1继承过来。

由于每次操作都是把数列设为一个公差为 1 1 1的等差数列,所以只用维护首项所覆盖的位置,剩下的用高斯求和单独算即可。

#include <bits/stdc++.h>
using namespace std;

inline int gi()
{
	char c = getchar();
	while (c < '0' || c > '9') c = getchar();
	int sum = 0;
	while ('0' <= c && c <= '9') sum = sum * 10 + c - 48, c = getchar();
	return sum;
}

inline char gc()
{
	char c = getchar();
	while (c < 'a' || c > 'z') c = getchar();
	return c;
}

typedef long long ll;
const int maxn = 100005, M = 1e4 + 7, mod = 998244353;

int n;
int val[maxn], pos[maxn], ans[maxn], a[maxn], b[maxn], top;
vector<int> to[maxn];

int rt[maxn][26], mx[maxn][26], tot;
struct seg
{
	int l, r, lch, rch, sum, lzy, nxt;
} t[maxn * 60];

#define mid ((l + r) >> 1)

inline void new_node(int &s) {t[++tot] = t[s]; s = tot;}
inline void add(int s, int v, int len) {t[s].sum = (ll)v * len % mod; t[s].lzy = v;}
inline void push_down(int s, int l, int r)
{
	if (!t[s].lzy) return ;
	new_node(t[s].lch); add(t[s].lch, t[s].lzy, mid - l + 1);
	new_node(t[s].rch); add(t[s].rch, t[s].lzy, r - mid);
	t[s].lzy = 0;
}

void modify(int &s, int l, int r, int x, int v, int p)
{
	new_node(s);
	if (r < x) return add(s, v, r - l + 1);
	if (l == r) return t[s].nxt = p, add(s, v, 1);
	push_down(s, l, r);
	modify(t[s].lch, l, mid, x, v, p);
	if (x > mid) modify(t[s].rch, mid + 1, r, x, v, p);
	t[s].sum = (t[t[s].lch].sum + t[t[s].rch].sum) % mod;
}

void query(int &s, int l, int r, int x, int &ans, int &nxt)
{
	if (r < x) return ans = (ans + t[s].sum) % mod, void();
	if (l == r) return ans = (ans + t[s].sum) % mod, nxt = t[s].nxt, void();
	push_down(s, l, r);
	query(t[s].lch, l, mid, x, ans, nxt);
	if (x > mid) query(t[s].rch, mid + 1, r, x, ans, nxt);
}

inline int getsum(int x) {return ((ll)x * (x + 1) >> 1) % mod;}

void dfs(int u)
{
	++top;
	int x = val[u] / M, y = val[u] % M, nxt = 0;
	a[top] = val[u]; b[top] = b[top - 1] + y;
	if (top == 1) ans[u] = getsum(y - 1);
	else {
		ans[u] = (ans[u] + getsum(min(mx[top][x], y))) % mod;
		query(rt[top][x], 1, M, y, ans[u], nxt);
		if (!nxt && a[1] / M == x && b[1] < y) nxt = 1, ans[u] = (ans[u] + (ll)b[1] * max(0, y - mx[top][x])) % mod;
	}
	mx[top][x] = max(mx[top][x], y);
	modify(rt[top][x], 1, M, y, b[top - 1], top);
	for (int v : to[u]) {
		memcpy(mx[top + 1], mx[nxt + 1], sizeof(mx[top + 1]));
		memcpy(rt[top + 1], rt[nxt + 1], sizeof(rt[top + 1]));
		ans[v] = ans[u]; dfs(v);
	}
	--top;
}

int main()
{
	n = gi();
	for (int op, x, i = 1; i <= n; ++i) {
		op = gi(); x = gi();
		if (op == 1) val[++tot] = (gc() - 'a') * M + x, pos[i] = tot, to[pos[i - 1]].push_back(pos[i]);
		else pos[i] = pos[x];
	}

	for (int i : to[0]) {
		tot = 0;
		memset(rt[1], 0, sizeof(rt[1]));
		memset(mx[1], 0, sizeof(mx[1]));
		dfs(i);
	}

	for (int i = 1; i <= n; ++i) printf("%d\n", ans[pos[i]]);
	
	return 0;
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值