HAOI2017 简要题解
「HAOI2017」新型城市化
題意
有一個 \(n\) 個點的無向圖,其中只有 \(m\) 對點之間沒有連邊,保證這張圖可以被分為至多兩個團。
對于 \(m\) 對未連邊的點對,判斷有哪些點對滿足將他們連邊后最大團的大小增加。
\(n \le 10^4 , m \le 1.5 × 10^5\)
題解
腦洞圖論題我真的一道都不會。
考慮原圖的話,有 \(n^2\) 條邊,顯然是不行的。可以考慮補圖,那么只有它給出的 \(m\) 條邊,那么這個圖一定是個二分圖。
因為題目保證了原圖可以被至多兩個團覆蓋,也就是意味著剩下的 \(m\) 條邊兩個端點各屬于兩個團中的一個。
原圖上的最大團 \(=\) 反圖上的最大獨立集 \(=\) 二分圖的最大獨立集 \(=\) 點數減去最大匹配數。
那么題目就是問去掉哪些邊后最大匹配數減少,也就是哪些邊一定在二分圖最大匹配上。這題中 \(n, m\) 較大,需要用 \(Dinic\) 算二分圖匹配。接下來就只需要判斷哪些邊在最大匹配上啦。
顯然它們一定要滿流,其次邊上的兩個點在殘量網絡上不能在同一個強連通分量中。
因為如果他們在同一個環中,就可以將環上未匹配的邊設為匹配邊,匹配邊設為未匹配邊,最大匹配顯然不變。
最后復雜度是 \(\mathcal O(m \sqrt n)\) 的,瓶頸在網絡流上。
代碼
注意一開始給的是無向邊,不能直接二分圖上連邊,先二分圖染色后,左邊向右邊連邊。
#include <bits/stdc++.h>#define For(i, l, r) for (register int i = (l), i##end = (int)(r); i <= i##end; ++i) #define Fordown(i, r, l) for (register int i = (r), i##end = (int)(l); i >= i##end; --i) #define Rep(i, r) for (register int i = (0), i##end = (int)(r); i < i##end; ++i) #define Set(a, v) memset(a, v, sizeof(a)) #define Cpy(a, b) memcpy(a, b, sizeof(a)) #define debug(x) cout << #x << ": " << (x) << endlusing namespace std;template<typename T> inline bool chkmin(T &a, T b) { return b < a ? a = b, 1 : 0; } template<typename T> inline bool chkmax(T &a, T b) { return b > a ? a = b, 1 : 0; }inline int read() {int x(0), sgn(1); char ch(getchar());for (; !isdigit(ch); ch = getchar()) if (ch == '-') sgn = -1;for (; isdigit(ch); ch = getchar()) x = (x * 10) + (ch ^ 48);return x * sgn; }void File() { #ifdef zjp_shadowfreopen ("2276.in", "r", stdin);freopen ("2276.out", "w", stdout); #endif }const int N = 1e4 + 1e3, M = 1.5e5 + 1e3, inf = 0x3f3f3f3f;template<int Maxn, int Maxm> struct Dinic {int Head[Maxn], Next[Maxm], to[Maxm], cap[Maxm], e;Dinic() { e = 1; }inline void add_edge(int u, int v, int w) {to[++ e] = v; cap[e] = w; Next[e] = Head[u]; Head[u] = e;}inline void Add(int u, int v, int w) {add_edge(u, v, w); add_edge(v, u, 0);}int dis[Maxn], S, T;bool Bfs() {queue<int> Q;Set(dis, 0); dis[S] = 1; Q.push(S);while (!Q.empty()) {int u = Q.front(); Q.pop();for (int i = Head[u], v = to[i]; i; v = to[i = Next[i]])if (cap[i] && !dis[v]) dis[v] = dis[u] + 1, Q.push(v);}return dis[T];}int cur[Maxn];int Dfs(int u, int flow) {if (u == T || !flow) return flow;int res = 0, f;for (int &i = cur[u], v = to[i]; i; v = to[i = Next[i]]) if (dis[v] == dis[u] + 1 && (f = Dfs(v, min(flow, cap[i])))) {cap[i] -= f; cap[i ^ 1] += f; res += f;if (!(flow -= f)) break;}return res;}int Run() {int res = 0;while (Bfs())Cpy(cur, Head), res += Dfs(S, inf);return res;}};Dinic<N, M << 1> T;int n, m; vector<int> G[N]; map<int, bool> Map[N];void Build() {For (u, 1, T.T)for (int i = T.Head[u], v = T.to[i]; i; v = T.to[i = T.Next[i]]) if (T.cap[i]) G[u].push_back(v), Map[v][u] = true; }int lowlink[N], dfn[N], sccno[N], stk[N], top, scc_cnt;void Tarjan(int u) {static int clk = 0;lowlink[u] = dfn[stk[++ top] = u] = ++ clk;for (int v : G[u]) if (!dfn[v])Tarjan(v), chkmin(lowlink[u], lowlink[v]);else if (!sccno[v]) chkmin(lowlink[u], dfn[v]);if (lowlink[u] == dfn[u]) {++ scc_cnt; int cur;do sccno[cur = stk[top --]] = scc_cnt; while (cur != u);} }int u[M], v[M];vector<pair<int, int>> ans;vector<int> E[N];int col[N]; void Color(int u) {for (int v : E[u]) if (!col[v])col[v] = col[u] ^ 3, Color(v); }int main () {File();n = read(); m = read();T.T = (T.S = n + 1) + 1;For (i, 1, m) {u[i] = read(), v[i] = read(); E[u[i]].push_back(v[i]);E[v[i]].push_back(u[i]);}For (i, 1, n) if (!col[i]) col[i] = 1, Color(i);For (i, 1, n)if (col[i] == 1) T.Add(T.S, i, 1); else T.Add(i, T.T, 1);For (i, 1, m) {if (col[u[i]] == 2) swap(u[i], v[i]);T.Add(u[i], v[i], 1);}T.Run(); Build();For (i, 1, T.T) if (!dfn[i]) Tarjan(i);For (i, 1, m)if (sccno[u[i]] != sccno[v[i]] && Map[u[i]][v[i]]) {if (u[i] > v[i]) swap(u[i], v[i]); ans.emplace_back(u[i], v[i]);}sort(ans.begin(), ans.end());printf ("%d\n", int(ans.size()));for (auto it : ans)printf ("%d %d\n", it.first, it.second);return 0;}「HAOI2017」方案數
題意
考慮定義非負整數間的 “$ \subseteq $” ,如果 $ a \subseteq b $,那么 $ a \land b = a $,其中 $ \land $ 表示二進制下的“與”操作。
考慮現在有一個無限大的空間,現在你在 \((0, 0, 0)\),有三種位移操作。
有 \(o\) 個點不能經過了。現在問你到某個點 \((n, m, r)\) 的方案數,答案對 \(998244353\) 取模。
\(n, m, r \le 10^{18}, o \le 10^4\)
題解
首先考慮沒有障礙的時候怎么做,不難發現答案只與 \(n, m, r\) 二進制下 \(1\) 的個數有關。
為什么呢?考慮操作的實質,其實就是個 \(x, y, z\) 中其中一個數在二進制下的一些 \(0\) 變成 \(1\) 。
那么就令 \(g_{i, j, k}\) 為三維分別有 \(i, j, k\) 個 \(1\) 的方案數,這部分是 \(O(\log^4 \max\{n, m, r\})\) 的。
那么預處理這個后就比較好做了,此時變成一個經典容斥模型。
設 \(f_i\) 為第一次碰到的關鍵點為 \(i\) 個點的方案數,那么直接做 \(\mathcal O(o^2)\) 容斥即可。
這樣常數其實挺小的,可以跑過。但不知道有什么更高妙的做法 QAQ
 如果是啥高維偏序就沒啥意思了。。
代碼
#include <bits/stdc++.h>#define For(i, l, r) for (register int i = (l), i##end = (int)(r); i <= i##end; ++i) #define Fordown(i, r, l) for (register int i = (r), i##end = (int)(l); i >= i##end; --i) #define Rep(i, r) for (register int i = (0), i##end = (int)(r); i < i##end; ++i) #define Set(a, v) memset(a, v, sizeof(a)) #define Cpy(a, b) memcpy(a, b, sizeof(a)) #define debug(x) cout << #x << ": " << (x) << endl #define bit(x) __builtin_popcountll(x)using namespace std;using ll = long long;template<typename T> inline bool chkmin(T &a, T b) { return b < a ? a = b, 1 : 0; } template<typename T> inline bool chkmax(T &a, T b) { return b > a ? a = b, 1 : 0; }template<typename T = ll> inline ll read() {ll x(0), sgn(1); char ch(getchar());for (; !isdigit(ch); ch = getchar()) if (ch == '-') sgn = -1;for (; isdigit(ch); ch = getchar()) x = (x * 10) + (ch ^ 48);return x * sgn; }void File() { #ifdef zjp_shadowfreopen ("2277.in", "r", stdin);freopen ("2277.out", "w", stdout); #endif }const int N = 1e4 + 1e3, LN = 80, Mod = 998244353;ll n, m, r;struct Node {ll x, y, z; } P[N];struct Cmp {inline bool operator () (const Node &lhs, const Node &rhs) const {if (lhs.x != rhs.x) return lhs.x < rhs.x;if (lhs.y != rhs.y) return lhs.y < rhs.y;return lhs.z < rhs.z;} };int f[N], g[LN][LN][LN], comb[LN][LN];int main () {File();n = read(); m = read(); r = read();int lim = ceil(log2(max({n, m, r})) + 1);For (i, 0, lim) {comb[i][0] = 1;For (j, 1, i) comb[i][j] = (comb[i - 1][j] + comb[i - 1][j - 1]) % Mod;}g[0][0][0] = 1;For (x, 0, lim) For (y, 0, lim) For (z, 0, lim) {For (ax, 0, x - 1) g[x][y][z] = (g[x][y][z] + 1ll * g[ax][y][z] * comb[x][x - ax]) % Mod;For (ay, 0, y - 1) g[x][y][z] = (g[x][y][z] + 1ll * g[x][ay][z] * comb[y][y - ay]) % Mod;For (az, 0, z - 1)g[x][y][z] = (g[x][y][z] + 1ll * g[x][y][az] * comb[z][z - az]) % Mod;}int o = read<int>();For (i, 1, o) {ll x = read(), y = read(), z = read();P[i] = (Node) {x, y, z};}P[++ o] = (Node) {n, m, r};sort(P + 1, P + o + 1, Cmp());For (i, 1, o) {f[i] = g[bit(P[i].x)][bit(P[i].y)][bit(P[i].z)];For (j, 1, i - 1)if ((P[j].x & P[i].x) == P[j].x && (P[j].y & P[i].y) == P[j].y && (P[j].z & P[i].z) == P[j].z)f[i] = (f[i] - 1ll * f[j] * g[bit(P[i].x ^ P[j].x)][bit(P[i].y ^ P[j].y)][bit(P[i].z ^ P[j].z)]) % Mod;}f[o] = (f[o] + Mod) % Mod;printf ("%d\n", f[o]);return 0;}「HAOI2017」字符串
題意
給出一個字符串 $ s $ 和 $ n $ 個字符串 $ p_i $,求每個字符串 $ p_i $ 在 $ s $ 中出現的次數。注意這里兩個字符串相等的定義稍作改變。
給定一個常數 $ k $,對于兩個字符串 $ a, b $,如果 $ a = b $,那么滿足:
特別地,如果 $ |a| = |b| \le k $,那么認為 $ a = b $。
$ |s|, \sum |p_i| \le 2 \cdot 10^5 $
題解
神仙題 QAQ 還是對字符串不太熟
考慮把所有 \(p_i\) 的正串和反串一起建一個 \(AC\) 自動機。
然后原串在上面跑,考慮一個自動機上一個節點 \(u\) 假設深度為 \(i\) ,如果他的下一位不匹配,那么我們只需要讓 \(i + k + 1\) 之后的都匹配就可以了。
我們現在需要統計的就是對于這個節點 \(u\) 來說 \(s\) 有多少個位置 恰好 匹配了前 \(i\) 個位置,然后隔著 \(i + k + 1\) 后面都能匹配上。
其實就是所有滿足 \(u\) 的 \(fail\) 樹內 \(s\) 匹配到的位置 \(j\) 滿足 \(j + k + 1\) 在 \(u\) 對應節點的字符串的反串的第 \(i + k + 1\) 位對應節點的 \(fail\) 樹內的個數(注意此處所有提到位置都以正串為準)。
我們其實就是要統計這樣一個東西,把每個字符串詢問都掛在對應每一位的 \(AC\) 自動機上的節點。
\(s\) 匹配的位置產生貢獻同樣掛到 \(AC\) 自動機上的節點上。
然后顯然是可以用線段樹合并統計貢獻的,但是沒有必要。由于是加減,滿足差分,那么我們進子樹的時候減掉,出子樹的時候加上就行了。
但是這樣是會算重復的,記得前面我們提到的恰好嗎?此處對于 \(i + k\) 也匹配上的方案也是會加上來的。
我們多掛個詢問把重復算的貢獻減掉就行啦。
最后復雜度是 \(\mathcal O((|s| + \sum |p|)\log (\sum |p|))\) 的啦。
代碼
#include <bits/stdc++.h>#define For(i, l, r) for (register int i = (l), i##end = (int)(r); i <= i##end; ++i) #define Fordown(i, r, l) for (register int i = (r), i##end = (int)(l); i >= i##end; --i) #define Rep(i, r) for (register int i = (0), i##end = (int)(r); i < i##end; ++i) #define Set(a, v) memset(a, v, sizeof(a)) #define Cpy(a, b) memcpy(a, b, sizeof(a)) #define debug(x) cout << #x << ": " << (x) << endl #define epb emplace_back #define fir first #define sec secondusing namespace std;using PII = pair<int, int>;template<typename T> inline bool chkmin(T &a, T b) { return b < a ? a = b, 1 : 0; } template<typename T> inline bool chkmax(T &a, T b) { return b > a ? a = b, 1 : 0; }inline int read() {int x(0), sgn(1); char ch(getchar());for (; !isdigit(ch); ch = getchar()) if (ch == '-') sgn = -1;for (; isdigit(ch); ch = getchar()) x = (x * 10) + (ch ^ 48);return x * sgn; }void File() { #ifdef zjp_shadowfreopen ("2278.in", "r", stdin);freopen ("2278.out", "w", stdout); #endif }const int N = 2e5 + 1e3;vector<int> G[N << 1];template<int Maxn, int Alpha> struct Aho_Corasick_Automaton {int ch[Maxn][Alpha], fail[Maxn], Node;Aho_Corasick_Automaton() { Node = 1; }inline int Insert(int pos, int c) {if (!ch[pos][c]) ch[pos][c] = ++ Node; return ch[pos][c];}void Get_Fail() {queue<int> Q; Rep (i, Alpha) {if (ch[1][i]) Q.push(ch[1][i]), fail[ch[1][i]] = 1;else ch[1][i] = 1;}while (!Q.empty()) {int u = Q.front(); Q.pop();Rep (i, Alpha) {int &v = ch[u][i];if (v) fail[v] = ch[fail[u]][i], Q.push(v);else v = ch[fail[u]][i];}}}void Get_Tree() {For (i, 2, Node) G[fail[i]].epb(i);}};int clk;template<int Maxn> struct Fenwick_Tree {#define lowbit(x) (x & -x)int sumv[Maxn];inline void Update(int pos) {for (; pos <= clk; pos += lowbit(pos)) ++ sumv[pos];}inline int Query(int pos) {int res = 0;for (; pos; pos -= lowbit(pos)) res += sumv[pos];return res;}};Aho_Corasick_Automaton<N << 1, 94> ACAM;Fenwick_Tree<N << 1> FT[2];int dfn[N << 1], efn[N << 1];void Dfs_Init(int u = 1) {dfn[u] = ++ clk; for (int v : G[u]) Dfs_Init(v); efn[u] = clk; }inline int Ask(int opt, int pos) {return FT[opt].Query(efn[pos]) - FT[opt].Query(dfn[pos] - 1); }int ans[N]; vector<PII> Q[N << 1]; vector<int> V[N << 1];void Process(int u) {for (PII cur : Q[u])ans[cur.fir] += (cur.sec > 0 ? -1 : 1) * Ask(cur.sec < 0, abs(cur.sec));for (int cur : V[u])FT[cur < 0].Update(dfn[abs(cur)]);for (int v : G[u]) Process(v);for (PII cur : Q[u])ans[cur.fir] += (cur.sec > 0 ? 1 : -1) * Ask(cur.sec < 0, abs(cur.sec)); }int n, k; char S[N], T[N];int L[N], R[N], pos[2][N];int main () {File();k = read(); scanf ("%s", S + 1); int lenS = strlen(S + 1);n = read();For (i, 1, n) {scanf ("%s", T + 1);int lenT = strlen(T + 1), u = 1;if (lenT < k) {ans[i] = lenS - lenT + 1; continue;}For (j, 1, lenT) L[j] = u = ACAM.Insert(u, T[j] - 33); u = 1;Fordown (j, lenT, 1) R[j] = u = ACAM.Insert(u, T[j] - 33);For (j, 0, lenT - k) Q[j ? L[j] : 1].epb(i, j == jend ? 1 : R[j + k + 1]);For (j, 1, lenT - k) Q[L[j]].epb(i, - R[j + k]);}ACAM.Get_Fail(); ACAM.Get_Tree(); Dfs_Init();pos[0][0] = pos[1][lenS + 1] = 1;For (i, 1, lenS)pos[0][i] = ACAM.ch[pos[0][i - 1]][S[i] - 33];Fordown (i, lenS, 1)pos[1][i] = ACAM.ch[pos[1][i + 1]][S[i] - 33];For (i, 0, lenS - k)V[pos[0][i]].epb(pos[1][i + k + 1]);For (i, 1, lenS - k)V[pos[0][i]].epb(- pos[1][i + k]);Process(1);For (i, 1, n) printf ("%d\n", ans[i]);return 0;}「HAOI2017」八縱八橫
題意
一開始有個 \(n\) 個點 \(m\) 條邊的連通圖,有 \(P\) 次操作。支持動態加邊,刪邊(只會刪加進去的邊),修改邊的權值。
每次操作后詢問從 \(1\) 號點出發在 \(1\) 號點結束的最大異或和路徑。不強制在線。
\(n \le 500, m \le 500, Q \le 1000, len \le 1000\)
\(len\) 為邊權的二進制位長度。
題解
如果沒有修改怎么做呢?知道一個結論就好啦。
任意一條 \(1\) 到 \(n\) 的路徑的異或和,都可以由任意一條 \(1\) 到 \(n\) 路徑的異或和與圖中的一些環的異或和來組合得到。
為什么?
如果我們走一條路徑的話,如果路徑上存在一個環,那么這個環的總異或值就可以下放到線性基。因為把這個環走兩遍就等于沒走這個環,同樣如果是由一條路徑到的這個環,沿原路返回,那等于那條路徑沒走,只走了環。
在這種條件下,我們可以考慮把環儲存為一個線性基的元素。因為這個元素是隨意選不選的。
由于一開始的邊是不會刪除的,所以我們可以對一開始讀入的邊用并查集找環,然后搞出一棵原圖的生成樹,這樣之后的插入就會構造出一個唯一的環,然后這個環的權值也可以方便地算出。
因為線性基不好撤銷,我們考慮進行線段樹分治。這樣可以直接把線性基存在分治結構里,最多只會同時存在 \(O(\log)\) 個。
我們先記錄好每條環邊的存在區間,一開始的環邊直接就是 \([0,q]\) 。注意每次對環邊進行權值修改時,我們也要劃分成兩個存在區間。
 做成這樣后直接把這些邊插到線段樹里,然后直接線段樹分治就可以了。
復雜度是 \(\displaystyle \mathcal O(n \alpha (n) + \frac{len^2}{\omega} (q \log q + (q + m -n)))\) 的,可以跑過。
代碼
#include <bits/stdc++.h>#define For(i, l, r) for(register int i = (l), i##end = (int)(r); i <= i##end; ++i) #define Fordown(i, r, l) for(register int i = (r), i##end = (int)(l); i >= i##end; --i) #define Set(a, v) memset(a, v, sizeof(a)) #define Cpy(a, b) memcpy(a, b, sizeof(a)) #define debug(x) cout << #x << ": " << (x) << endl #define DEBUG(...) fprintf(stderr, __VA_ARGS__)using namespace std;template<typename T> inline bool chkmin(T &a, T b) {return b < a ? a = b, 1 : 0;} template<typename T> inline bool chkmax(T &a, T b) {return b > a ? a = b, 1 : 0;}inline int read() {int x(0), sgn(1); char ch(getchar());for (; !isdigit(ch); ch = getchar()) if (ch == '-') sgn = -1;for (; isdigit(ch); ch = getchar()) x = (x * 10) + (ch ^ 48);return x * sgn; }void File() { #ifdef zjp_shadowfreopen ("2312.in", "r", stdin);freopen ("2312.out", "w", stdout); #endif }const int N = 1005, M = N << 1;typedef bitset<N> Info;struct Base {Info B[N];inline void Insert(Info cur) {Fordown (i, N - 5, 0)if (cur[i]) {if (!B[i].any()) { B[i] = cur; break; }else cur ^= B[i];}}Info Query() {Info res; res.reset();Fordown (i, N - 5, 0)if (!res[i]) res ^= B[i];return res;}};char str[N]; Info Trans() {Info res; res.reset();scanf ("%s", str);int len = strlen(str);reverse(str, str + len);For (i, 0, len)res[i] = str[i] == '1';return res; }inline void Out(Info cur) {bool flag = false;Fordown (i, N - 5, 0) {if (cur[i] == 1) flag = true;if (flag) putchar (cur[i] + 48);}putchar ('\n'); }int n, m, q;int fa[N]; int find(int x) { return fa[x] == x ? x : fa[x] = find(fa[x]); }int Head[N], Next[M], to[M], e = 0; Info val[M]; inline void add_edge(int u, int v, Info w) {to[++ e] = v; Next[e] = Head[u]; Head[u] = e; val[e] = w; }Info dis[N]; void Dfs_Init(int u = 1, int fa = 0) {for (int i = Head[u]; i; i = Next[i]) {int v = to[i]; if (v == fa) continue ;dis[v] = dis[u] ^ val[i];Dfs_Init(v, u);} }int tim[N], now = 0;struct Option {int x, y; Info z;} lt[N];#define lson o << 1, l, mid #define rson o << 1 | 1, mid + 1, rInfo ans[N];template<int Maxn> struct Segment_Tree {vector<Option> V[Maxn];void Update(int o, int l, int r, int ul, int ur, Option uv) {if (ul <= l && r <= ur) { V[o].push_back(uv); return ; }int mid = (l + r) >> 1;if (ul <= mid) Update(lson, ul, ur, uv);if (ur > mid) Update(rson, ul, ur, uv);}void Dfs(int o, int l, int r, Base cur) {For (i, 0, V[o].size() - 1) {int u = V[o][i].x, v = V[o][i].y; Info w = V[o][i].z;cur.Insert(dis[u] ^ dis[v] ^ w);}if (l == r) { ans[l] = cur.Query(); return ; }int mid = (l + r) >> 1;Dfs(lson, cur); Dfs(rson, cur);}};Segment_Tree<N << 2> T;bool Cancel[N];int main () {File();n = read(); m = read(); q = read(); For (i, 1, n) fa[i] = i;For (i, 1, m) {int u = read(), v = read(); Info w = Trans();if (find(u) != find(v))fa[find(u)] = find(v), add_edge(u, v, w), add_edge(v, u, w);elseT.Update(1, 1, q + 1, 1, q + 1, (Option){u, v, w});}Dfs_Init();For (i, 1, q) {scanf ("%s", str + 1);if (str[1] == 'A') {int u = read(), v = read(); Info w = Trans();lt[++ now] = (Option){u, v, w}, tim[now] = i + 1;} else if (str[2] == 'a') {int id = read(); Cancel[id] = true;T.Update(1, 1, q + 1, tim[id], i, lt[id]);} else {int id = read(); Info w = Trans();T.Update(1, 1, q + 1, tim[id], i, lt[id]); tim[id] = i + 1; lt[id].z = w; }}For (i, 1, q) if (!Cancel[i])T.Update(1, 1, q + 1, tim[i], q + 1, lt[i]);T.Dfs(1, 1, q + 1, Base());For (i, 1, q + 1) Out(ans[i]);return 0;}「HAOI2017」供給側改革
題意
一個長度為 $ n $ 的 $ 01 $ 字符串 \(S\) ,令 \(\operatorname{data}(l,r)\) 表示:在字符串 \(S\) 中,起始位置在 \([l,r]\) 之間的這些后綴之中,具有最長公共前綴的兩個后綴的最長公共前綴的長度。
\(Q\) 次詢問。對于每一個詢問 \(L\),\(R\)。求
\[ \mathit{ans} = \sum\limits_{ L \le i \lt R } \operatorname{data}(i, R) \]
\(S\) 隨機生成。
$ n \leq 100000, Q \leq 100000 $
題解
首先是隨機,答案長度肯定不會太大,我們設它為 \(L\) ,大概不超過 \(40\) 。
那么就有一個很顯然的暴力了,把 \(n\) 個位置向后延伸的 \(L\) 個字符的串插入到 \(Trie\) 中。
每次從 \(R\) 向 \(L\) 掃,然后在樹上把這個點到根的路徑打標記,然后把當前的 ans 與之前打過標記且在這條路徑上的最深點深度取 \(\max\) ,最后求和就是答案了。
復雜度是 \(\mathcal O(nQL)\) 的。
考慮優化,由于答案不超過 \(L\) ,且答案是單調不降的。我們可以考慮對于答案相同的一段連續算。
這個我們在 \(Trie\) 預處理出每一層對于每個 \(r\) 左邊最靠右的滿足條件的 \(l\) 即可。然后最后排次序,算貢獻即可。
復雜度優化到了 \(\mathcal O((n + Q)L)\) 。
代碼
#include <bits/stdc++.h>#define For(i, l, r) for (register int i = (l), i##end = (int)(r); i <= i##end; ++i) #define Fordown(i, r, l) for (register int i = (r), i##end = (int)(l); i >= i##end; --i) #define Rep(i, r) for (register int i = (0), i##end = (int)(r); i < i##end; ++i) #define Set(a, v) memset(a, v, sizeof(a)) #define Cpy(a, b) memcpy(a, b, sizeof(a)) #define debug(x) cout << #x << ": " << (x) << endlusing namespace std;template<typename T> inline bool chkmin(T &a, T b) { return b < a ? a = b, 1 : 0; } template<typename T> inline bool chkmax(T &a, T b) { return b > a ? a = b, 1 : 0; }inline int read() {int x(0), sgn(1); char ch(getchar());for (; !isdigit(ch); ch = getchar()) if (ch == '-') sgn = -1;for (; isdigit(ch); ch = getchar()) x = (x * 10) + (ch ^ 48);return x * sgn; }void File() { #ifdef zjp_shadowfreopen ("2313.in", "r", stdin);freopen ("2313.out", "w", stdout); #endif }const int N = 1e5 + 1e3, L = 40;vector<int> V[N]; int lef[L][N];namespace Trie {const int Maxn = N * L;int ch[Maxn][2], Node; vector<int> ver[Maxn];int Insert(int *P, int Len, int pos) {int u = 0;Rep (i, Len) {ver[u].push_back(pos);int &v = ch[u][P[i]];if (!v) v = ++ Node; u = v;}ver[u].push_back(pos);return u;}void Get(int u, int len) {if (int(ver[u].size()) <= 1) return;Rep (i, ver[u].size() - 1)lef[len][ver[u][i + 1]] = ver[u][i];Rep (id, 2) if (ch[u][id]) Get(ch[u][id], len + 1);}}int P[N], n, q, id[N]; char str[N]; struct Seg { int pos, val; } S[N];int main () {using namespace Trie;File();n = read(); q = read();scanf ("%s", str + 1);For (i, 1, n) P[i] = str[i] ^ '0';For (i, 1, n) id[i] = Insert(P + i, min(L - 1, n - i + 1), i);Get(0, 0);Rep (i, L) For (j, 1, n) chkmax(lef[i][j], lef[i][j - 1]);For (tim, 1, q) {int l = read(), r = read();Rep (i, L) S[i] = (Seg) {lef[i][r], i};sort(S, S + L, [&](Seg a, Seg b) { return a.pos != b.pos ? a.pos < b.pos : a.val > b.val; });int ans = 0, Last = l - 1;Rep (i, L) {if (S[i].pos < l) continue;if (i && S[i].pos == S[i - 1].pos) continue;ans += S[i].val * (S[i].pos - Last); Last = S[i].pos;}printf ("%d\n", ans);}return 0;}轉載于:https://www.cnblogs.com/zjp-shadow/p/10375149.html
總結
以上是生活随笔為你收集整理的HAOI2017 简要题解的全部內容,希望文章能夠幫你解決所遇到的問題。
 
                            
                        - 上一篇: SuperMemo 15.1
- 下一篇: 织梦手机站站内搜索
