NOI 模拟赛
T1 Article
给 $m$ 个好串,定义一个字符串分割方案是好的当且仅当它分割出来的子串中"是好串的子串"的串长占原串串长超过 85%,定义一个好的分割方案的权值为这种分割方案中每个"是好串的子串"的子串的最短长度,给 $n$ 个询问串,对每个询问串求最大权值
$n,m \leq 10^5, \sum |S|,\sum |T| \leq 10^6$
sol:
二分最短长度 $L$,做一个 dp
$f_i$ 表示前 $i$ 位字符串最多的"是好串的子串"的长度和是多少,转移就是 $f_i = max\{f_j + (i - j)\} (S[j+1,i] 是好串的子串,i-j \geq L)$
由于 $S[j+1,i]$ 的右端单调递增,可以用后缀自动机来预处理每一个 $S[1,i]$ 最大匹配长度,最大匹配长度显然是单调的,所以可以用单调队列维护 $i+f_i$
然后还要注意一点是这题 85% 以上就行,所以还要加一个 $f_i = f_{i-1}$ 的转移,最后判一下 $f_{len}$ 是否超过原串的 85%
#include <bits/stdc++.h> #define LL long long #define rep(i, s, t) for(register int i = (s), i##end = (t); i <= i##end; ++i) #define dwn(i, s, t) for(register int i = (s), i##end = (t); i >= i##end; --i) using namespace std; inline int read() { int x = 0, f = 1; char ch = getchar(); for(; !isdigit(ch); ch = getchar()) if(ch == '-') f = -f; for(; isdigit(ch); ch = getchar()) x = 10 * x + ch - '0'; return x * f; } const int maxn = 2000010; int m, n; char s[maxn]; int f[maxn]; int root, last, dfn; int tr[maxn][3], fa[maxn], mxlen[maxn], Q[maxn]; inline void extend(int c) { int p = last, np = last = ++dfn; mxlen[np] = mxlen[p] + 1; for(; p && !tr[p][c]; p = fa[p]) tr[p][c] = np; if(!p) fa[np] = root; else { int q = tr[p][c]; if(mxlen[q] == mxlen[p] + 1) fa[np] = q; else { int nq = ++dfn; mxlen[nq] = mxlen[p] + 1; fa[nq] = fa[q]; fa[q] = fa[np] = nq; memcpy(tr[nq], tr[q], sizeof(tr[q])); for(; p && tr[p][c] == q; p = fa[p]) tr[p][c] = nq; } } } int chk(int mid) { int len = strlen(s + 1); int p = root, cl = 0, HD = 1, TL = 0; rep(i, 1, len) { int c = s[i] - 'a'; if(tr[p][c]) p = tr[p][c], cl++; else { for(; p && !tr[p][c]; p = fa[p]); if(!p) cl = 0, p = root; else cl = mxlen[p] + 1, p = tr[p][c]; } f[i] = f[i - 1]; int lf = i - cl, rig = i - mid; if(rig < 0) continue; while(HD <= TL && f[Q[TL]] - Q[TL] < f[rig] - rig) --TL; Q[++TL] = rig; while(HD <= TL && Q[HD] < lf) ++HD; if(HD <= TL)f[i] = max(f[i], i + f[Q[HD]] - Q[HD]); } return (f[len] * 20) >= (len * 17); } int main() { // freopen("article.in","r",stdin); // freopen("article.out","w",stdout); root = last = ++dfn; n = read(), m = read(); rep(i, 1, m) { scanf("%s", s + 1); int len = strlen(s + 1); last = 1; rep(i, 1, len) extend(s[i] - 'a'); } rep(i, 1, n) { scanf("%s", s + 1); int l = 0, r = strlen(s + 1), ans = -1; while(l <= r) { int mid = (l + r) >> 1; if(chk(mid)) l = mid + 1, ans = mid; else r = mid - 1; } cout << ans << '\n'; } } /* 1 2 babba aaaaabbba babbaabbaa */
T2 work
考场上 sb 了,一直觉得这题是个费用流,然后死磕,然后磕死
然后发现这是一个小清新最小割题...
考虑两个从 $A$,$B$ 出发“相交”的炮弹是什么样的,显然可以看成最多拐一个弯的连接 $A,B$ 的路径,任意一个合法方案必然不包含这样的路径,所以相当于割掉这条路径的一边,于是是最小割...
一开始搜出所有路径最大值,然后减去最小代价就是答案
“最多拐一个弯”可以这样:把一个点拆成纵和横,然后纵横分别连边,每个点纵向横连 inf 即可
#include <bits/stdc++.h> #define LL long long #define rep(i, s, t) for(register int i = (s), i##end = (t); i <= i##end; ++i) #define dwn(i, s, t) for(register int i = (s), i##end = (t); i >= i##end; --i) using namespace std; inline int read() { int x = 0, f = 1; char ch = getchar(); for(; !isdigit(ch); ch = getchar()) if(ch == '-') f = -f; for(; isdigit(ch); ch = getchar()) x = 10 * x + ch - '0'; return x * f; } const int oo = 2147483233, maxn = 100010; struct Dinic { int n, m, s, t; int cur[maxn], head[maxn], nx[maxn]; struct Edge { int from, to, caps; Edge() {} Edge(int _1, int _2, int _3) : from(_1), to(_2), caps(_3) {} } es[maxn]; void add(int u, int v, int w) { es[m] = Edge(u, v, w); nx[m] = head[u]; head[u] = m++; es[m] = Edge(v, u, 0); nx[m] = head[v]; head[v] = m++; } Dinic() { m = 0; memset(head, -1, sizeof(head)); } queue<int> q; int dis[maxn]; int BFS() { for (int i = 0; i <= n; i++) dis[i] = 0; dis[t] = 1; q.push(t); while (!q.empty()) { int now = q.front(); q.pop(); for (int i = head[now]; i != -1; i = nx[i]) { Edge &e = es[i ^ 1]; if (!dis[e.from] && e.caps) { dis[e.from] = dis[now] + 1; q.push(e.from); } } } return (dis[s] > 1); } int DFS(int u, int a) { if (u == t || !a) return a; int flow = 0, f; for (int &i = cur[u]; i != -1; i = nx[i]) { Edge &e = es[i]; if (dis[e.to] == dis[u] - 1 && (f = DFS(e.to, min(e.caps, a)))) { flow += f; a -= f; e.caps -= f; es[i ^ 1].caps += f; if (a == 0) return flow; } } return flow; } int MaxFlow(int _s, int _t) { s = _s, t = _t; int flow = 0, f; while (BFS()) { for (int i = 0; i <= n; i++) cur[i] = head[i]; while (f = DFS(s, oo)) flow += f; } return flow; } } sol; int pos[60][60][2], dfn; const int dx[] = {0, -1, 1, 0, 0}; const int dy[] = {0, 0, 0, -1, 1}; inline int t(int i, int j, int type) { return pos[i][j][type] ? pos[i][j][type] : (pos[i][j][type] = ++dfn); } int n, m, S, T, ans; int a[60][60]; int main() { n = read(), m = read(); rep(i, 1, n) rep(j, 1, m) { a[i][j] = read(); sol.add(t(i, j, 0), t(i, j, 1), oo); } S = ++dfn, T = ++dfn; rep(i, 1, n) rep(j, 1, m) { if(a[i][j] >= 0) continue; int opt = -a[i][j]; a[i][j] = 0; int cx = i + dx[opt], cy = j + dy[opt], mx = 0; while(cx >= 1 && cx <= n && cy >= 1 && cy <= m) { mx = max(mx, a[cx][cy]); cx += dx[opt], cy += dy[opt]; } ans += mx; if(opt <= 2) { int cx = i + dx[opt], cy = j + dy[opt]; sol.add(S, t(i, j, 0), oo); while(cx >= 1 && cx <= n && cy >= 1 && cy <= m) { sol.add(t(cx - dx[opt], cy - dy[opt], 0), t(cx, cy, 0), mx - a[cx - dx[opt]][cy - dy[opt]]); cx += dx[opt], cy += dy[opt]; } } else { int cx = i + dx[opt], cy = j + dy[opt]; sol.add(t(i, j, 1), T, oo); while(cx >= 1 && cx <= n && cy >= 1 && cy <= m) { sol.add(t(cx, cy, 1), t(cx - dx[opt], cy - dy[opt], 1), mx - a[cx - dx[opt]][cy - dy[opt]]); cx += dx[opt], cy += dy[opt]; } } } sol.n = dfn; cout << ans - sol.MaxFlow(S, T) << endl; }
T3 sej
一个长度为 $n$ 的排列 $\{a_i\}$,定义 $p_x = max_{i=1}^x a_i$,$s_x = max_{i=x}^n a_i$
对这个排列求出 $p$ 数组和 $s$ 数组,将 $p,s$ 分别去重、排序,求 $p,s$ 长度分别为 $a,b$ 的排列有多少个
$n,a,b \leq 100000$
sol:
发现 $p$ 数组和 $s$ 数组一定能变成一个峰,两边递减,而两边的情况是对称的,于是可以考虑枚举每次左边,右边峰的出现
记 $f_{(i,j)}$ 表示插入了前 $i$ 大的数,左边的峰改变了$j$ 次的排列数
考虑每次把 $1$ 加进去,就是 $f_{(i,j)} = f_{(i-1,j-1)} + (i-1) \times f_{(i-1,j)}$
因为左右时对称的,所以这也是右边的峰改变了 $j$ 次的排列数,由于最后一个 $n$ 和第一个 $1$ 会同时改变左右的峰,所以一共改变次数是 $a+b-2$
这 $a+b-2$ 中,有 $a-1$ 次是单独改左边(把 $1$ 和 $n$ 分配给左右边),考虑改的是哪几次,显然有 $\binom{a+b-2}{a-1}$ 种方法,于是答案就是 $f_{(n,a+b-2)} \times \binom{a+b-2}{a-1}$
然后发现 $f$ 就是第一类斯特林数,考虑第一类斯特林数的下降幂形式 $\prod\limits_{i=1}^{n-1} (x-i) = \sum\limits_{i=0}^n (-1)^{(n-i)} \times Stirling1(n,i) \times x^i$
右边是个生成函数,左边是 $n$ 个多项式相乘
所以分治+FFT即可
#include <bits/stdc++.h> #define LL long long #define rep(i, s, t) for(register int i = (s), i##end = (t); i <= i##end; ++i) #define dwn(i, s, t) for(register int i = (s), i##end = (t); i >= i##end; --i) using namespace std; inline int read() { int x = 0, f = 1; char ch = getchar(); for(; !isdigit(ch); ch = getchar()) if(ch == '-') f = -f; for(; isdigit(ch); ch = getchar()) x = 10 * x + ch - '0'; return x * f; } const int mod = 998244353, maxn = 200010; inline int inc(int x, int y) { x += y; if(x >= mod) x -= mod; return x; } inline int dec(int x, int y) { x -= y; if(x < 0) x += mod; return x; } inline int mul(int x, int y) { return 1LL * x * y % mod; } inline int ksm(int x, int t, int res = 1) { for(; t; x = mul(x, x), t = t >> 1) if(t & 1) res = mul(res, x); return res; } int f[maxn], fac[maxn], ifac[maxn], lg[maxn], r[maxn]; inline int C(int x, int y) { return mul(fac[x], mul(ifac[y], ifac[x - y])); } vector<int> G[maxn]; int A[maxn], B[maxn], fnl[maxn]; inline void fft(int *a, int n, int f) { rep(i, 0, n-1) r[i] = (r[i >> 1] >> 1) | ((i & 1) << (lg[n] - 1)); rep(i, 0, n-1) if(i < r[i]) swap(a[i], a[r[i]]); for(register int i = 1; i < n; i <<= 1) { int wn = ksm(3, (mod - 1) / (i << 1)); if(f == -1) wn = ksm(wn, mod - 2); for(register int j = 0; j < n; j += (i << 1)) { int w = 1; for(register int k = 0; k < i; ++k, w = mul(w, wn)) { int x = a[j + k], y = mul(w, a[j + k + i]); a[j + k] = inc(x, y); a[j + k + i] = dec(x, y); } } } if(f == -1) { int inv_n = ksm(n, mod - 2); rep(i, 0, n-1) a[i] = mul(a[i], inv_n); } } inline int mul(int wlen) { fft(A, wlen, 1); fft(B, wlen, 1); rep(i, 0, wlen - 1) A[i] = mul(A[i], B[i]); fft(A, wlen, -1); --wlen; while(!A[wlen])--wlen; return wlen; } inline int Mul(int l, int r) { if(l == r) return G[l].size() - 1; int mid = (l + r) >> 1; int ls = Mul(l, mid), rs = Mul(mid + 1, r); int len = 1; for(; len <= (ls + rs); len <<= 1); rep(i, 0, ls) A[i] = G[l][i]; rep(i, ls+1, len) A[i] = 0; rep(i, 0, rs) B[i] = G[mid+1][i]; rep(i, rs+1, len) B[i] = 0; G[l].clear(); G[mid+1].clear(); len = mul(len); rep(i, 0, len) G[l].push_back(A[i]); return len; } int n, a, b; int main() { // freopen("sej.in","r",stdin); // freopen("sej.out","w",stdout); lg[0] = -1; rep(i, 1, maxn - 1) lg[i] = lg[i >> 1] + 1; n = read(), a = read(), b = read(); fac[0] = fac[1] = ifac[0] = ifac[1] = 1; rep(i, 1, a + b) fac[i] = mul(fac[i - 1], i); rep(i, 2, a + b) ifac[i] = mul(dec(0, mod / i), ifac[mod % i]); rep(i, 1, a + b) ifac[i] = mul(ifac[i], ifac[i - 1]); n--; rep(i, 1, n) G[i].push_back(dec(0, i-1)), G[i].push_back(1); Mul(1, n); rep(i, 0, G[1].size() - 1) fnl[i] = G[1][i]; rep(i, 0, n) if((n-i) & 1) fnl[i] = mul(fnl[i], dec(0, 1)); cout << mul(fnl[a + b - 2], C(a + b - 2, a - 1)) << endl; }