T1
Description
給定一張 $n$ 個點的有向圖,每一個點多是黑色,白色,或者無色,一開始這張圖沒有邊。你要將這張圖補充完整:node
- 把每一個無色的點塗上黑色或白色。
- 對於 $\forall 1\leq i<j\leq n$,你能夠選擇連 $i→j$ 的有向邊或者不連。
定義一條路徑是合法的,當且僅當路徑上相鄰的點的顏色不一樣,路徑能夠只通過一個點。c++
定義一張圖是合法的,當且僅當這張圖的合法路徑數爲奇數。git
求有多少種補充的方案使得圖合法,答案對 $998244353$ 取模。數組
$1\leq n\leq 2×10^5$。spa
時空限制 $1s/128MB$。code
Solution
咱們記 $g_i$ 表示以點 $i$ 爲結尾的合法路徑條數 $%2$ 的結果。要使得圖合法,就是使得 $n$ 個點中 $g_i=1$ 的個數爲奇數。ip
顯然 $g_i=(\sum_{j=1}^{i-1}[color_i≠color_j]g_j)%2\ xor\ 1$。能夠發現 $g_i$ 只跟 $i$ 連了幾個 $color_i≠color_j$ 且 $g_j=1$ 的點有關。假設連了 $k$ 個這樣的點 $j$。若 $k$ 是偶數,$g_i=1$,不然 $g_i=0$。ci
考慮 $dp$,記 $f[i][j][a][b]$ 表示前 $i$ 個點,$g$ 的異或和是 $j(j∈[0,1])$,$g=1$ 的點中,有 $a$ 個是白色,$b$ 個是黑色的方案數。字符串
考慮從 $f[i-1][j][a][b]$ 轉移過來。咱們枚舉點 $i$ 染成白色仍是黑色。當點 $i$ 能夠染成白色(點 $i$ 原來是無色或白色)時,枚舉 $i$ 連了這 $b$ 個點中的 $k$ 個,有以下轉移:$$f[i][j\ xor\ 1][a+1][b]+=f[i-1][j][a][b]×h_0[b]$$$$f[i][j][a][b]+=f[i-1][j][a][b]×h_1[b]$$get
其中 $h_0[b]$ 表示在大小爲 $b$ 的集合中選出偶數個元素的方案數,$h_1[b]$ 表示在大小爲 $b$ 的集合中選出奇數個元素的方案數。
顯然有$$b=0:h_0[b]=1,h_1[b]=0$$$$b>0:h_0[b]=h_1[b]=2^{b-1}$$
所以記 $f[i][j][c][d],c∈[0,1],d∈[0,1]$ 表示前 $i$ 個點,$g$ 的異或和爲 $j$,是否 $a>0$,是否 $b>0$ 的方案數。
時間複雜度 $O(n)$。
Code
#include <bits/stdc++.h> using namespace std; #define ll long long template <class t> inline void read(t & res) { char ch; bool fl = 0; res = 0; while (ch = getchar(), !isdigit(ch) && ch != '-'); ch == '-' ? fl = 1 : res = ch ^ 48; while (ch = getchar(), isdigit(ch)) res = res * 10 + (ch ^ 48); fl ? res = ~res + 1 : 0; } const int e = 2e5 + 5, mod = 998244353; int f[e][2][2][2], n, ans, col[e], p[e]; inline void add(int &x, int y) { (x += y) >= mod && (x -= mod); } inline int plu(int x, int y) { add(x, y); return x; } inline int s(int x, int y) { x += y; return min(x, 1); } inline int mul(int x, int y) { return (ll)x * y % mod; } int main() { read(n); int i, j, a, b; for (i = 1; i <= n; i++) read(col[i]); p[0] = 1; for (i = 1; i <= n; i++) p[i] = plu(p[i - 1], p[i - 1]); f[0][0][0][0] = 1; for (i = 1; i <= n; i++) for (j = 0; j <= 1; j++) for (a = 0; a <= 1; a++) for (b = 0; b <= 1; b++) if (f[i - 1][j][a][b]) { int v = f[i - 1][j][a][b]; if (col[i] != 1) { if (b) { add(f[i][j ^ 1][s(a, 1)][b], mul(v, p[i - 2])); add(f[i][j][a][b], mul(v, p[i - 2])); } else add(f[i][j ^ 1][s(a, 1)][b], mul(v, p[i - 1])); } if (col[i] != 0) { if (a) { add(f[i][j ^ 1][a][s(b, 1)], mul(v, p[i - 2])); add(f[i][j][a][b], mul(v, p[i - 2])); } else add(f[i][j ^ 1][a][s(b, 1)], mul(v, p[i - 1])); } } for (a = 0; a <= 1; a++) for (b = 0; b <= 1; b++) add(ans, f[n][1][a][b]); cout << ans << endl; fclose(stdin); fclose(stdout); return 0; }
T2
Description
給定一張 $n$ 個點,$m$ 條邊的無向圖,如今你要給每條邊定向。
定義一張有向圖合法,當且僅當存在一個點 $u$,使得 $1$ 能走到 $u$ 且 $2$ 能走到 $u$。
求 $2^m$ 種定向方案中,有多少種定向以後的圖是合法的。
答案對 $10^9+7$ 取模,$n\leq 15,m\leq\frac{n(n-1)}{2}$。
時空限制 $1s/128MB$。
Solution
考慮補集轉化,即求出不合法的定向方案數。
$S$ 的導出子圖包括 $S$ 中的點,兩點都在 $S$ 中的邊。
枚舉集合 $S,T$,保證 $1∈S,2∈T,S∩T=\varnothing$。假設已經求出了: $f[S]$ 表示給 $S$ 的導出子圖中的邊定向,使得 $1$ 能走到 $S$ 中的全部點的方案數,$g[T]$ 表示給 $T$ 的導出子圖中的邊定向,使得 $2$ 能走到 $T$ 中的全部點的方案數。
咱們強制 $1$ 走不到 $S$ 之外的點,$2$ 走不到 $T$ 之外的點。那麼要保證不存在橫跨 $S,T$ 的邊,且不屬於 $S∪T$ 的點和 $S,T$ 中的點的連邊的方向已經肯定。設 $\complement(S∪T)$ 的導出子圖邊數爲 $cnt$,那麼 $$ans+=f[S]×g[T]×2^{cnt}$$
接下來考慮怎麼求 $f[S]$。$g[T]$ 同理。
一樣考慮補集轉化,先令 $f[S]=2^{S的導出子圖邊數}$。接下來,枚舉 $S$ 的一個真子集 $T$,強制 $1$ 只能走到 $T$ 中的點。那麼 $T$ 和 $S∩\complement T$ 之間的邊的方向已經肯定,設 $S∩\complement T$ 的導出子圖邊數爲 $cnt$,那麼 $$f[S]-=f[T]×2^{cnt}$$ 時間複雜度 $O(3^n)$。
#include <bits/stdc++.h> using namespace std; #define ll long long template <class t> inline void read(t & res) { char ch; while (ch = getchar(), !isdigit(ch)); res = ch ^ 48; while (ch = getchar(), isdigit(ch)) res = res * 10 + (ch ^ 48); } const int e = (1 << 15) + 5, mod = 1e9 + 7; int f[e], g[e], h[e], n, m, ans, p[e], id, tmp; inline void add(int &x, int y) { (x += y) >= mod && (x -= mod); } inline void del(int &x, int y) { (x -= y) < 0 && (x += mod); } inline int mul(int x, int y) { return (ll)x * y % mod; } int main() { read(n); read(m); read(id); tmp = 1 << n; int i, x, y, s, t; p[0] = 1; for (i = 1; i <= m; i++) { read(x); read(y); p[i] = 2ll * p[i - 1] % mod; for (s = 0; s < tmp; s++) if ((s & (1 << x - 1)) && (s & (1 << y - 1))) h[s]++; } ans = p[m]; f[1] = 1; for (s = 2; s < tmp; s++) if (s & 1) { f[s] = p[h[s]]; for (t = (s - 1) & s; t; t = (t - 1) & s) del(f[s], mul(f[t], p[h[s ^ t]])); } g[2] = 1; for (s = 3; s < tmp; s++) if (s & 2) { g[s] = p[h[s]]; for (t = (s - 1) & s; t; t = (t - 1) & s) del(g[s], mul(g[t], p[h[s ^ t]])); } for (i = 0; i < tmp; i++) for (t = i; t; t = (t - 1) & i) if (t & 2) { s = (tmp - 1) ^ i; if (s & 1 && (h[s | t] - h[s] - h[t] == 0)) del(ans, mul(mul(f[s], g[t]), p[h[(tmp - 1) ^ (s | t)]])); } cout << ans << endl; fclose(stdin); fclose(stdout); return 0; }
T3
Description
給定一個長度爲 $n$ 的只包含小寫英文字母的字符串 $s$,你須要找到一個最大的 $k$,使得存在:
$$1\le l_1\le r_1<l_2\le r_2<l_3\le r_3<\dots<l_k\le r_k\le n$$
(即 $k$ 個區間 $[l_1,r_1][l_2,r_2]\dots[l_k,r_k]$ 的左右端點都遞增且兩兩不相交)
使得對於每一個 $1\le i<k$,都知足 $s[l_{i+1}\dots r_{i+1}]$ 是 $s[l_i\dots r_i]$ 的嚴格子串。
其中 $s[l\dots r]$ 表示字符串 $s$ 的第 $l$ 到第 $r$ 個字符組成的字符串。
字符串 $A$ 是字符串 $B$ 的嚴格子串,當且僅當從 $B$ 的開頭和結尾各刪掉若干個字符(從開頭和結尾刪掉的字符個數均可以是零個,但刪掉的字符個數之和必須大於 $0$)可以獲得 $A$。
$n\leq 5×10^5$。
時空限制 $2.5s/512MB$。
Solution
首先要知道幾個性質:
- 最優狀況下,全部的 $s[l_i\dots r_i]$ 都是 $s[l_{i+1}\dots r_{i+1}]$ 在開頭或結尾添加一個字符獲得的,且 $l_k=r_k$。
- 記 $f_i$ 表示 $l_1=i$ 時,$k$ 最大是多少,有 $f_i\leq f_{i+1}+1$
考慮證實性質 $2$,即 $f_{i+1}\geq f_i-1$。
假設已經獲得一個 $l_1=i$,且 $f_i=k$ 的劃分方案。在這個方案中,若 $s[l_j\dots r_j]$ 是 $s[l_{j+1}\dots r_{j+1}]$ 在開頭加字符獲得的,記 $a_j=0$,不然 $a_j=1$。考慮去掉 $s[l_1\dots r_1]$ 的第一個字符,即 $l_1++$。
接下來令 $j=1$,若是 $a_j=1$,那麼令 $l_{j+1}++,j++$,繼續循環。不然此時必有 $s[l_j\dots r_j]=s[l_{j+1}\dots r_{j+1}]$,那麼把 $s[l_{j+1}\dots r_{j+1}]$ 刪掉便可。這樣就獲得了一個 $f_{i+1}=k$ 的劃分方案。
所以每次先令 $f_i=f_{i+1}+1$,判斷這個 $f_i$ 是否能取到,不能就 $f_i--$,直到 $f_i=1$。答案就是 $max(f_i)$。
問題轉化爲判斷是否 $f_i\leq mid$。顯然須要存在一個 $j$ 知足下面 $3$ 個條件:
- $i+mid\leq j\leq n$
- $max(lcp(suf_i,suf_j),lcp(suf_{i+1},suf_j)\geq mid-1$
- $f_j\geq mid-1$
求出 $sa,rank$ 數組,那麼條件 $2$ 能夠看做是 $rank_j∈[l,r]$,$[l,r]$ 能夠用二分 + $\text{RMQ}$ 求出。
那咱們要求的就是知足 $i+mid\leq j\leq n,rank_j∈[l,r]$ 的 $max(f_j)$。主席樹便可,時間複雜度 $O(n \log n)$。
Code
#include <bits/stdc++.h> using namespace std; const int e = 1e6 + 5; struct node { int l, r, w; }c[e * 30]; char s[e]; int rk[e], h[e], w[e], sa[e], a[e], b[e], tot, tmp[e], f[e], n, ans = 1, d[e][20], rt[e]; int logn[e], pool; inline void init() { int i, k, r = 0, j; for (i = 1; i <= n; i++) w[s[i]]++; for (i = 1; i <= 256; i++) w[i] += w[i - 1]; for (i = n; i >= 1; i--) sa[w[s[i]]--] = i; for (i = 1; i <= n; i++) if (s[sa[i - 1]] == s[sa[i]]) a[sa[i]] = r; else a[sa[i]] = ++r; for (k = 1; r < n; k <<= 1) { tot = 0; for (i = n - k + 1; i <= n; i++) b[++tot] = i; for (i = 1; i <= n; i++) if (sa[i] > k) b[++tot] = sa[i] - k; for (i = 1; i <= n; i++) w[a[b[i]]] = 0; for (i = 1; i <= n; i++) w[a[b[i]]]++; for (i = 2; i <= n; i++) w[i] += w[i - 1]; for (i = n; i >= 1; i--) sa[w[a[b[i]]]--] = b[i]; for (i = 1; i <= n; i++) tmp[i] = a[i]; r = 0; for (i = 1; i <= n; i++) if (tmp[sa[i]] == tmp[sa[i - 1]] && tmp[sa[i] + k] == tmp[sa[i - 1] + k]) a[sa[i]] = r; else a[sa[i]] = ++r; } for (i = 1; i <= n; i++) rk[sa[i]] = i; for (i = 1; i <= n; i++) { if (rk[i] == 1) continue; h[i] = max(0, h[i - 1] - 1); int x = sa[rk[i] - 1]; while (s[i + h[i]] == s[x + h[i]] && max(i, x) + h[i] <= n) h[i]++; } logn[0] = -1; for (i = 1; i <= n; i++) logn[i] = logn[i >> 1] + 1, d[i][0] = h[sa[i]]; for (j = 1; (1 << j) <= n; j++) for (i = 1; i + (1 << j) - 1 <= n; i++) d[i][j] = min(d[i][j - 1], d[i + (1 << j - 1)][j - 1]); } inline int ask(int l, int r) { if (l > r) swap(l, r); if (l == r) return n - sa[l] + 1; l++; int k = logn[r - l + 1]; return min(d[l][k], d[r - (1 << k) + 1][k]); } inline void insert(int y, int &x, int l, int r, int s, int v) { c[x = ++pool] = c[y]; c[x].w = max(c[x].w, v); if (l == r) return; int mid = l + r >> 1; if (s <= mid) insert(c[y].l, c[x].l, l, mid, s, v); else insert(c[y].r, c[x].r, mid + 1, r, s, v); } inline int query(int x, int l, int r, int s, int t) { if (l == s && r == t) return c[x].w; int mid = l + r >> 1; if (t <= mid) return query(c[x].l, l, mid, s, t); else if (s > mid) return query(c[x].r, mid + 1, r, s, t); else return max(query(c[x].l, l, mid, s, mid), query(c[x].r, mid + 1, r, mid + 1, t)); } inline void upt(int &s, int &t, int x, int len) { int l = 1, pos = rk[x], r = pos; s = pos; while (l <= r) { int mid = l + r >> 1; if (ask(mid, pos) >= len) s = mid, r = mid - 1; else l = mid + 1; } t = pos; l = pos; r = n; while (l <= r) { int mid = l + r >> 1; if (ask(pos, mid) >= len) t = mid, l = mid + 1; else r = mid - 1; } } inline bool check(int x, int mid) { int l, r, s = x + mid, t = n; if (s > t) return 0; upt(l, r, x, mid - 1); if (query(rt[s], 1, n, l, r) >= mid - 1) return 1; upt(l, r, x + 1, mid - 1); return query(rt[s], 1, n, l, r) >= mid - 1; } int main() { cin >> n; scanf("%s", s + 1); n = strlen(s + 1); init(); f[n] = 1; int i; insert(rt[n + 1], rt[n], 1, n, rk[n], 1); for (i = n - 1; i >= 1; i--) { f[i] = f[i + 1] + 1; while (f[i] > 1 && !check(i, f[i])) f[i]--; ans = max(ans, f[i]); insert(rt[i + 1], rt[i], 1, n, rk[i], f[i]); } cout << ans << endl; fclose(stdin); fclose(stdout); return 0; }