HAOI2017 简要题解

「HAOI2017」新型城市化

题意

有一个 n 个点的无向图,其中只有 m 对点之间没有连边,保证这张图可以被分为至多两个团。

对于 m 对未连边的点对,判断有哪些点对满足将他们连边后最大团的大小增加。

n104,m1.5×105

题解

脑洞图论题我真的一道都不会。

考虑原图的话,有 n2 条边,显然是不行的。可以考虑补图,那么只有它给出的 m 条边,那么这个图一定是个二分图。

因为题目保证了原图可以被至多两个团覆盖,也就是意味着剩下的 m 条边两个端点各属于两个团中的一个。

原图上的最大团 = 反图上的最大独立集 = 二分图的最大独立集 = 点数减去最大匹配数。

那么题目就是问去掉哪些边后最大匹配数减少,也就是哪些边一定在二分图最大匹配上。这题中 n,m 较大,需要用 Dinic 算二分图匹配。接下来就只需要判断哪些边在最大匹配上啦。

显然它们一定要满流,其次边上的两个点在残量网络上不能在同一个强连通分量中。

因为如果他们在同一个环中,就可以将环上未匹配的边设为匹配边,匹配边设为未匹配边,最大匹配显然不变。

最后复杂度是 O(mn) 的,瓶颈在网络流上。

代码

注意一开始给的是无向边,不能直接二分图上连边,先二分图染色后,左边向右边连边。

#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 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_shadow freopen ("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」方案数

题意

考虑定义非负整数间的 “” ,如果 ab,那么 ab=a,其中 表示二进制下的“与”操作。

考虑现在有一个无限大的空间,现在你在 (0,0,0),有三种位移操作。

  1. (x,y,z)(ax,y,z) 当且仅当 xax
  2. (x,y,z)(x,ay,z) 当且仅当 yay
  3. (x,y,z)(x,y,az) 当且仅当 zaz

o 个点不能经过了。现在问你到某个点 (n,m,r) 的方案数,答案对 998244353 取模。

n,m,r1018,o104

题解

首先考虑没有障碍的时候怎么做,不难发现答案只与 n,m,r 二进制下 1 的个数有关。

为什么呢?考虑操作的实质,其实就是个 x,y,z 中其中一个数在二进制下的一些 0 变成 1

那么就令 gi,j,k 为三维分别有 i,j,k1 的方案数,这部分是 O(log4max{n,m,r}) 的。

那么预处理这个后就比较好做了,此时变成一个经典容斥模型。

fi 为第一次碰到的关键点为 i 个点的方案数,那么直接做 O(o2) 容斥即可。

这样常数其实挺小的,可以跑过。但不知道有什么更高妙的做法 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_shadow freopen ("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」字符串

题意

给出一个字符串 sn 个字符串 pi,求每个字符串 pis 中出现的次数。注意这里两个字符串相等的定义稍作改变。

给定一个常数 k,对于两个字符串 a,b,如果 a=b,那么满足:

  1. |a|=|b|
  2. 对于所有 aibi 以及 ajbj,满足 |ij|<k

特别地,如果 |a|=|b|k,那么认为 a=b

|s|,|pi|2105

题解

神仙题 QAQ 还是对字符串不太熟

考虑把所有 pi 的正串和反串一起建一个 AC 自动机。

然后原串在上面跑,考虑一个自动机上一个节点 u 假设深度为 i ,如果他的下一位不匹配,那么我们只需要让 i+k+1 之后的都匹配就可以了。

我们现在需要统计的就是对于这个节点 u 来说 s 有多少个位置 恰好 匹配了前 i 个位置,然后隔着 i+k+1 后面都能匹配上。

其实就是所有满足 ufail 树内 s 匹配到的位置 j 满足 j+k+1u 对应节点的字符串的反串的第 i+k+1 位对应节点的 fail 树内的个数(注意此处所有提到位置都以正串为准)。

我们其实就是要统计这样一个东西,把每个字符串询问都挂在对应每一位的 AC 自动机上的节点。

s 匹配的位置产生贡献同样挂到 AC 自动机上的节点上。

然后显然是可以用线段树合并统计贡献的,但是没有必要。由于是加减,满足差分,那么我们进子树的时候减掉,出子树的时候加上就行了。

但是这样是会算重复的,记得前面我们提到的恰好吗?此处对于 i+k 也匹配上的方案也是会加上来的。

我们多挂个询问把重复算的贡献减掉就行啦。

最后复杂度是 O((|s|+|p|)log(|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 second using 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_shadow freopen ("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 号点结束的最大异或和路径。不强制在线。

n500,m500,Q1000,len1000

len 为边权的二进制位长度。

题解

如果没有修改怎么做呢?知道一个结论就好啦。

任意一条 1n 的路径的异或和,都可以由任意一条 1n 路径的异或和与图中的一些环的异或和来组合得到。

为什么?

如果我们走一条路径的话,如果路径上存在一个环,那么这个环的总异或值就可以下放到线性基。因为把这个环走两遍就等于没走这个环,同样如果是由一条路径到的这个环,沿原路返回,那等于那条路径没走,只走了环。

在这种条件下,我们可以考虑把环储存为一个线性基的元素。因为这个元素是随意选不选的。

由于一开始的边是不会删除的,所以我们可以对一开始读入的边用并查集找环,然后搞出一棵原图的生成树,这样之后的插入就会构造出一个唯一的环,然后这个环的权值也可以方便地算出。

因为线性基不好撤销,我们考虑进行线段树分治。这样可以直接把线性基存在分治结构里,最多只会同时存在 O(log) 个。

我们先记录好每条环边的存在区间,一开始的环边直接就是 [0,q] 。注意每次对环边进行权值修改时,我们也要划分成两个存在区间。
做成这样后直接把这些边插到线段树里,然后直接线段树分治就可以了。

复杂度是 O(nα(n)+len2ω(qlogq+(q+mn))) 的,可以跑过。

代码

#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_shadow freopen ("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, r Info 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); else T.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」供给侧改革

题意

一个长度为 n01 字符串 S ,令 data(l,r) 表示:在字符串 S 中,起始位置在 [l,r] 之间的这些后缀之中,具有最长公共前缀的两个后缀的最长公共前缀的长度。

Q 次询问。对于每一个询问 LR。求

ans=Li<Rdata(i,R)

S 随机生成

n100000,Q100000

题解

首先是随机,答案长度肯定不会太大,我们设它为 L ,大概不超过 40

那么就有一个很显然的暴力了,把 n 个位置向后延伸的 L 个字符的串插入到 Trie 中。

每次从 RL 扫,然后在树上把这个点到根的路径打标记,然后把当前的 ans 与之前打过标记且在这条路径上的最深点深度取 max ,最后求和就是答案了。

复杂度是 O(nQL) 的。

考虑优化,由于答案不超过 L ,且答案是单调不降的。我们可以考虑对于答案相同的一段连续算。

这个我们在 Trie 预处理出每一层对于每个 r 左边最靠右的满足条件的 l 即可。然后最后排次序,算贡献即可。

复杂度优化到了 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) << endl 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_shadow freopen ("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; }

__EOF__

本文作者zjp_shadow
本文链接https://www.cnblogs.com/zjp-shadow/p/10375149.html
关于博主:评论和私信会在第一时间回复。或者直接私信我。
版权声明:本博客所有文章除特别声明外,均采用 BY-NC-SA 许可协议。转载请注明出处!
声援博主:如果您觉得文章对您有帮助,可以点击文章右下角推荐一下。您的鼓励是博主的最大动力!
posted @   zjp_shadow  阅读(809)  评论(0编辑  收藏  举报
编辑推荐:
· 如何编写易于单元测试的代码
· 10年+ .NET Coder 心语,封装的思维:从隐藏、稳定开始理解其本质意义
· .NET Core 中如何实现缓存的预热?
· 从 HTTP 原因短语缺失研究 HTTP/2 和 HTTP/3 的设计差异
· AI与.NET技术实操系列:向量存储与相似性搜索在 .NET 中的实现
阅读排行:
· 地球OL攻略 —— 某应届生求职总结
· 周边上新:园子的第一款马克杯温暖上架
· Open-Sora 2.0 重磅开源!
· 提示词工程——AI应用必不可少的技术
· .NET周刊【3月第1期 2025-03-02】
点击右上角即可分享
微信分享提示