2018冬令营模拟测试赛(六)
[Problem A]string
试题描述
输入
见“试题描述”
输出
见“试题描述”
输入示例
见“试题描述”
输出示例
见“试题描述”
数据规模及约定
见“试题描述”
题解
一口一个前缀,二话不说建立 trie 树。
为了避免算重,我们在统计一个串的时候要求在最后的分割位置统计它,即不存在更靠后的分割位置。为了达到这一点我们先枚举后半部分,由于后半部分是某个串的一个前缀,即 trie 树上从根到某个点 (u) 的一条路径,那么现在就是要求前面填的串满足一个性质。
令 (v = fail[u]),即失配边指向的位置,(w) 是根节点到 (u) 的路径中刨去根节点到 (v) 的路径这个后缀的串所对应的节点(也即 (u) 在 trie 树上的 (k) 级祖先,(k) 为根到 (v) 的路径长度)。容易发现只要前面填的串不是 (v) 的失配树中的子树中的节点就好了。
#include <iostream>
#include <cstdio>
#include <cstdlib>
#include <cstring>
#include <cctype>
#include <algorithm>
#include <vector>
using namespace std;
#define rep(i, s, t) for(int i = (s); i <= (t); i++)
#define dwn(i, s, t) for(int i = (s); i >= (t); i--)
int read() {
int x = 0, f = 1; char c = getchar();
while(!isdigit(c)){ if(c == '-') f = -1; c = getchar(); }
while(isdigit(c)){ x = x * 10 + c - '0'; c = getchar(); }
return x * f;
}
#define maxn 1000010
#define maxa 30
#define maxlog 20
#define LL long long
char str[maxn];
int ToT, ch[maxn][maxa], dep[maxn], pa[maxn][maxlog];
void Insert(char *s) {
int n = strlen(s), u = 1;
rep(i, 0, n - 1) {
int x = s[i] - 'a';
if(!ch[u][x]) ch[u][x] = ++ToT;
u = ch[u][x];
}
return ;
}
void build(int u) {
rep(i, 1, maxlog - 1) pa[u][i] = pa[pa[u][i-1]][i-1];
rep(c, 0, maxa - 1) if(ch[u][c]) {
pa[ch[u][c]][0] = u;
dep[ch[u][c]] = dep[u] + 1;
build(ch[u][c]);
}
return ;
}
int KthPar(int u, int k) {
rep(i, 0, maxlog - 1) if(k >> i & 1) u = pa[u][i];
return u;
}
int fa[maxn], tofa[maxn], siz[maxn], Q[maxn], hd, tl;
void getfail() {
hd = tl = 0; Q[++tl] = 1;
while(hd < tl) {
int u = Q[++hd];
rep(c, 0, maxa - 1) if(ch[u][c]) {
int v = ch[u][c], j = fa[u];
while(j && !ch[j][c]) j = fa[j];
fa[v] = ch[j][c] ? ch[j][c] : 1;
if(fa[v] > 1) tofa[v] = KthPar(v, dep[fa[v]]);
Q[++tl] = v;
}
}
dwn(i, tl, 1) {
int u = Q[i];
siz[u]++; siz[fa[u]] += siz[u];
}
return ;
}
int main() {
read();
int n = read();
ToT = 1;
rep(i, 1, n) scanf("%s", str), Insert(str);
build(1);
getfail();
LL ans = 0; siz[0] = 0;
rep(i, 2, ToT) ans += ToT - siz[tofa[i]] - !tofa[i]; // (ToT - 1) - (siz[tofa[i]] - 1)
printf("%lld
", ans);
return 0;
}
[Problem B]tree
试题描述
输入
见“试题描述”
输出
见“试题描述”
输入示例
见“试题描述”
输出示例
见“试题描述”
数据规模及约定
见“试题描述”
题解
这是一道可以转化为基于深度维护信息的题,所以做法是长链剖分。
记 (f(i, j)) 表示节点 (i) 的子树中,还需要向子树 (i) 外部延伸 (j) 的距离能够组成的三元组的点对的贡献(这只是一个状态,具体计算还需要维护 (sum a_0 + a_1) 和 (sum a_0a_1))。
在合并的时候我们可以暴力扫描一下其他子树的短链,然后把子树之间的贡献利用 (f(i, j)) 计算,同时合并更新 (f(i, j))。在重链上上移的时候,需要数组整体移位,这个东西记录一个移了多少位的变量就行了;在每一步移道位置 (0)(即 (j = 0) 那一位)时 (f(i, j)) 和当前节点也可以组成三元组,记得累计一下贡献。
#include <iostream>
#include <cstdio>
#include <cstdlib>
#include <cstring>
#include <cctype>
#include <algorithm>
#include <vector>
using namespace std;
#define rep(i, s, t) for(int i = (s); i <= (t); i++)
#define dwn(i, s, t) for(int i = (s); i >= (t); i--)
int read() {
int x = 0, f = 1; char c = getchar();
while(!isdigit(c)){ if(c == '-') f = -1; c = getchar(); }
while(isdigit(c)){ x = x * 10 + c - '0'; c = getchar(); }
return x * f;
}
#define maxn 100010
#define maxm 200010
#define MOD 998244353
#define LL long long
int n, m, head[maxn], nxt[maxm], to[maxm], val[maxn];
void AddEdge(int a, int b) {
to[++m] = b; nxt[m] = head[a]; head[a] = m;
swap(a, b);
to[++m] = b; nxt[m] = head[a]; head[a] = m;
return ;
}
#define pll pair <LL, LL>
#define x first
#define y second
#define mp(x, y) make_pair(x, y)
vector <pll> info[maxn]; // x: _sum{a0+a1}, y: _sum{a0*a1}
int mxd[maxn], son[maxn], dep[maxn], clo, dfn[maxn], uid[maxn], top[maxn];
void build(int u, int fa) {
for(int e = head[u]; e; e = nxt[e]) if(to[e] != fa) {
dep[to[e]] = dep[u] + 1;
build(to[e], u);
if(!son[u] || mxd[son[u]] < mxd[to[e]]) son[u] = to[e];
}
mxd[u] = max(mxd[son[u]], dep[u]);
return ;
}
void gett(int u, int tp, int fa) {
if(u == tp) info[u].resize(mxd[u] - dep[u] + 3 << 1);
uid[dfn[u] = ++clo] = u; top[u] = tp;
if(son[u]) gett(son[u], tp, u);
for(int e = head[u]; e; e = nxt[e]) if(to[e] != fa && to[e] != son[u]) gett(to[e], to[e], u);
return ;
}
LL ans, sumv[maxn], cntv[maxn];
void solve(int u, int fa) {
if(son[u]) solve(son[u], u);
int minus = mxd[u] - dep[u];
// printf("solve(%d)
", u);
for(int e = head[u]; e; e = nxt[e]) if(to[e] != fa && to[e] != son[u]) {
solve(to[e], u);
// printf("merge %d and %d
", u, to[e]);
rep(h, 0, mxd[to[e]] - dep[to[e]]) {
// printf("for height %d
", h);
int i = h + 1 + minus, j = dfn[to[e]] + h;
ans += info[top[u]][i].x * sumv[j] % MOD;
ans += info[top[u]][i].y * cntv[j] % MOD;
// printf("%d and %d: %lld * %lld + %lld * %lld = %lld
", u, to[e], info[top[u]][i].x, sumv[j], info[top[u]][i].y, cntv[j], info[top[u]][i].x * sumv[j] + info[top[u]][i].y * cntv[j]);;
i = dfn[u] + h; j = h + 1 + mxd[to[e]] - dep[to[e]];
ans += sumv[i] * info[to[e]][j].x % MOD;
ans += cntv[i] * info[to[e]][j].y % MOD;
// printf("also: %lld * %lld + %lld * %lld = %lld
", sumv[i], info[to[e]][j].x, cntv[i], info[to[e]][j].y, sumv[i] * info[to[e]][j].x + cntv[i] * info[to[e]][j].y);
ans %= MOD;
}
(info[top[u]][minus].x += info[to[e]][mxd[to[e]]-dep[to[e]]+1].x) %= MOD;
(info[top[u]][minus].y += info[to[e]][mxd[to[e]]-dep[to[e]]+1].y) %= MOD;
rep(h, 1, mxd[to[e]] - dep[to[e]] + 1) {
int i = h + minus, j = h + 1 + mxd[to[e]] - dep[to[e]], di = dfn[u] + h, dj = dfn[to[e]] + h - 1;
info[top[u]][i].x += sumv[di] * cntv[dj] % MOD + sumv[dj] * cntv[di] % MOD;
info[top[u]][i].y += sumv[di] * sumv[dj] % MOD;
(info[top[u]][i].x += info[to[e]][j].x) %= MOD;
// printf("+= %lld
", info[to[e]][j].x);
(info[top[u]][i].y += info[to[e]][j].y) %= MOD;
(sumv[di] += sumv[dj]) %= MOD; (cntv[di] += cntv[dj]) %= MOD;
// printf("for height %d
merging: (a0+a1)%lld (a0*a1)%lld (sumv)%lld (cntv)%lld
", h, info[top[u]][i].x, info[top[u]][i].y, sumv[di], cntv[di]);
}
}
sumv[dfn[u]] = val[u]; cntv[dfn[u]] = 1;
(ans += info[top[u]][minus].x * val[u] % MOD + info[top[u]][minus].y) %= MOD;
// printf("ans += %lld * %lld + %lld = %lld
", info[top[u]][minus].x, val[u], info[top[u]][minus].y, info[top[u]][minus].x * val[u] + info[top[u]][minus].y);
return ;
}
int main() {
read();
n = read();
rep(i, 1, n - 1) {
int a = read(), b = read();
AddEdge(a, b);
}
rep(i, 1, n) val[i] = read();
build(1, 0);
gett(1, 1, 0);
/*rep(u, 1, n) {
printf("[%d] top, son: %d, %d
", u, top[u], son[u]);
if(top[u] == u) printf("vector_size: %d
", info[u].size());
} // */
solve(1, 0);
printf("%lld
", ans);
return 0;
}
[Problem C]heike
试题描述
输入
见“试题描述”
输出
见“试题描述”
输入示例
见“试题描述”
输出示例
见“试题描述”
数据规模及约定
注意:本题给出的数据范围是假的,应为 (n,m le 10^6)
题解
这题先要证明一下(略过)是个拟阵,但是我觉得考虑替换也可以意识到它是个贪心了。
然后就是要上霍尔定理,这样我们可以得到:一个区间是满的当且仅当它所包含的 boys 总数等于区间长度。
每加入一个 boy 的时候维护一下那些区间满了,每个区间包含它的满区间最短的是谁(由于“满”有可交、可并性,证明略,故这个是可以求的),这个可以在每次加入成功时 dp 一下。
顺便维护一下一个区间中包含的最小权值的 boy,用于“假插入”询问。
对于没加入的 boy,在包含它的最短满区间上打个标记,表示它是备选的,到时候如果某个 boy 被删了导致那个区间不满,就可以把这个 boy 加进去。
最后把上面说的那个标记再 dp 下传一下,这样删掉一个 boy 就可以 (O(1)) 求出替补是谁。
#include <iostream>
#include <cstdio>
#include <cstdlib>
#include <cstring>
#include <cctype>
#include <algorithm>
using namespace std;
#define rep(i, s, t) for(int i = (s); i <= (t); i++)
#define dwn(i, s, t) for(int i = (s); i >= (t); i--)
int read() {
int x = 0, f = 1; char c = getchar();
while(!isdigit(c)){ if(c == '-') f = -1; c = getchar(); }
while(isdigit(c)){ x = x * 10 + c - '0'; c = getchar(); }
return x * f;
}
#define maxn 310
#define maxm 1000010
#define oo 2147483647
#define LL long long
#define pii pair <int, int>
#define x first
#define y second
#define mp(x, y) make_pair(x, y)
struct Boy {
int l, r, v, id;
Boy() {}
Boy(int _1, int _2, int _3, int _4): l(_1), r(_2), v(_3), id(_4) {}
bool operator < (const Boy& t) const { return v > t.v; }
} bs[maxm];
bool used[maxm];
int n, real[maxm], cb, q, tot[maxn][maxn], inMin[maxn][maxn], getMax[maxn][maxn];
pii Full[maxn][maxn];
void upd(pii& a, pii b) {
if(!b.x) return ;
if(!a.x || a.y - a.x + 1 > b.y - b.x + 1) a = b;
return ;
}
int main() {
read();
n = read(); cb = read(); q = read();
rep(i, 1, cb) {
int l = read(), r = read(), v = read();
bs[i] = Boy(l, r, v, i);
}
rep(i, 1, n) rep(j, i, n) inMin[i][j] = oo;
LL ans = 0;
sort(bs + 1, bs + cb + 1);
rep(i, 1, cb) real[bs[i].id] = i;
rep(i, 1, cb) {
Boy& b = bs[i];
if(!Full[b.l][b.r].x) {
used[i] = 1;
ans += b.v;
rep(l, 1, b.l) rep(r, b.r, n) tot[l][r]++, inMin[l][r] = min(inMin[l][r], b.v);
dwn(len, n, 1) rep(l, 1, n - len + 1) {
int r = l + len - 1;
if(tot[l][r] == r - l + 1) upd(Full[l][r], pii(l, r));
if(len > 1) upd(Full[l][r-1], Full[l][r]), upd(Full[l+1][r], Full[l][r]);
}
}
else {
pii& fu = Full[b.l][b.r];
getMax[fu.x][fu.y] = max(getMax[fu.x][fu.y], b.v);
}
}
dwn(len, n, 2) rep(l, 1, n - len + 1) {
int r = l + len - 1, now = getMax[l][r];
getMax[l][r-1] = max(getMax[l][r-1], now);
getMax[l+1][r] = max(getMax[l+1][r], now);
}
printf("%lld
", ans);
while(q--) {
int tp = read();
if(tp == 1) {
int nl = read(), nr = read(), nv = read();
pii& fu = Full[nl][nr];
printf("%lld
", ans - inMin[fu.x][fu.y] + max(inMin[fu.x][fu.y], nv));
}
if(tp == 2) {
int i = real[read()];
if(!used[i]){ printf("%lld
", ans); continue; }
printf("%lld
", ans - bs[i].v + getMax[bs[i].l][bs[i].r]);
}
}
return 0;
}