「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) << 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
", int(ans.size()));
for (auto it : ans)
printf ("%d %d
", it.first, it.second);
return 0;
}
「HAOI2017」方案数
题意
考虑定义非负整数间的 “$ subseteq $” ,如果 $ a subseteq b $,那么 $ a land b = a $,其中 $ land $ 表示二进制下的“与”操作。
考虑现在有一个无限大的空间,现在你在 ((0, 0, 0)),有三种位移操作。
- $ (x, y, z) o (ax, y, z) $ 当且仅当 $ x subseteq ax $;
- $ (x, y, z) o (x, ay, z) $ 当且仅当 $ y subseteq ay $;
- $ (x, y, z) o (x, y, az) $ 当且仅当 $ z subseteq az $。
有 (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_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
", f[o]);
return 0;
}
「HAOI2017」字符串
题意
给出一个字符串 $ s $ 和 $ n $ 个字符串 $ p_i $,求每个字符串 $ p_i $ 在 $ s $ 中出现的次数。注意这里两个字符串相等的定义稍作改变。
给定一个常数 $ k $,对于两个字符串 $ a, b $,如果 $ a = b $,那么满足:
- $ |a| = |b| $;
- 对于所有 $ a_i eq b_i $ 以及 $ a_j eq b_j $,满足 $ |i-j| < k $。
特别地,如果 $ |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 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
", 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_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 ('
');
}
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」供给侧改革
题意
一个长度为 $ n $ 的 $ 01 $ 字符串 (S) ,令 (operatorname{data}(l,r)) 表示:在字符串 (S) 中,起始位置在 ([l,r]) 之间的这些后缀之中,具有最长公共前缀的两个后缀的最长公共前缀的长度。
(Q) 次询问。对于每一个询问 (L),(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) << 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
", ans);
}
return 0;
}