「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\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)\),有三种位移操作。

  1. $ (x, y, z) \to (ax, y, z) $ 当且仅当 $ x \subseteq ax $;
  2. $ (x, y, z) \to (x, ay, z) $ 当且仅当 $ y \subseteq ay $;
  3. $ (x, y, z) \to (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\n", f[o]); return 0; }

「HAOI2017」字符串

题意

给出一个字符串 $ s $ 和 $ n $ 个字符串 $ p_i $,求每个字符串 $ p_i $ 在 $ s $ 中出现的次数。注意这里两个字符串相等的定义稍作改变。

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

  1. $ |a| = |b| $;
  2. 对于所有 $ a_i \neq b_i $ 以及 $ a_j \neq 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\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_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」供给侧改革

题意

一个长度为 $ 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) << 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; }

HAOI2017 简要题解的更多相关文章

  1. Noip 2014酱油记+简要题解

    好吧,day2T1把d默认为1也是醉了,现在只能期待数据弱然后怒卡一等线吧QAQ Day0 第一次下午出发啊真是不错,才2小时左右就到了233,在车上把sao和fate补掉就到了= = 然后到宾馆之后 ...

  2. Tsinghua 2018 DSA PA2简要题解

    反正没时间写,先把简要题解(嘴巴A题)都给他写了记录一下. upd:任务倒是完成了,我也自闭了. CST2018 2-1 Meteorites: 乘法版的石子合并,堆 + 高精度. 写起来有点烦貌似. ...

  3. Codeforces 863 简要题解

    文章目录 A题 B题 C题 D题 E题 F题 G题 传送门 简要题解?因为最后一题太毒不想写了所以其实是部分题解... A题 传送门 题意简述:给你一个数,问你能不能通过加前导000使其成为一个回文数 ...

  4. HNOI2018简要题解

    HNOI2018简要题解 D1T1 寻宝游戏 题意 某大学每年都会有一次 Mystery Hunt 的活动,玩家需要根据设置的线索解谜,找到宝藏的位置,前一年获胜的队伍可以获得这一年出题的机会. 作为 ...

  5. JXOI2018简要题解

    JXOI2018简要题解 T1 排序问题 题意 九条可怜是一个热爱思考的女孩子. 九条可怜最近正在研究各种排序的性质,她发现了一种很有趣的排序方法: Gobo sort ! Gobo sort 的算法 ...

  6. BJOI2018简要题解

    BJOI2018简要题解 D1T1 二进制 题意 pupil 发现对于一个十进制数,无论怎么将其的数字重新排列,均不影响其是不是 \(3\) 的倍数.他想研究对于二进制,是否也有类似的性质. 于是他生 ...

  7. CQOI2018简要题解

    CQOI2018简要题解 D1T1 破解 D-H 协议 题意 Diffie-Hellman 密钥交换协议是一种简单有效的密钥交换方法.它可以让通讯双方在没有事先约定密钥(密码)的情况下,通过不安全的信 ...

  8. AtCoder ExaWizards 2019 简要题解

    AtCoder ExaWizards 2019 简要题解 Tags:题解 link:https://atcoder.jp/contests/exawizards2019 很水的一场ARC啊,随随便便就 ...

  9. Comet OJ - Contest #2 简要题解

    Comet OJ - Contest #2 简要题解 cometoj A 模拟,复杂度是对数级的. code B 易知\(p\in[l,r]\),且最终的利润关于\(p\)的表达式为\(\frac{( ...

随机推荐

  1. Spring MVC普通类或工具类中调用service报空空指针的解决办法(调用service报java.lang.NullPointerException)

    当我们在非Controller类中应用service的方法是会报空指针,如图: 这是因为Spring MVC普通类或工具类中调用service报空null的解决办法(调用service报java.la ...

  2. 02-安装linux系统

    安装linux系统 需要准备的软件: 1.VMware-workstation-full-14.1.1.28517.exe 2.CentOS-6.5-x86_64-bin-DVD1.iso镜像文件 第 ...

  3. PAT L3-007 天梯地图

    https://pintia.cn/problem-sets/994805046380707840/problems/994805051153825792 本题要求你实现一个天梯赛专属在线地图,队员输 ...

  4. MySql concat与字符转义

    mysql函数之四:concat() mysql 多个字段拼接 - duanxz - 博客园https://www.cnblogs.com/duanxz/p/5098875.html mysql 多个 ...

  5. EF内容记录_EF连接Mysql版本问题

    EF连接MySQL可用版本,由于EF.MySQLConnection.mysql-for-visualstudio.VS版本.MySQL.Data.MySQL.Data.Entity版本问题较花时间, ...

  6. java8新特性:interface中的static方法和default方法

    java8中接口有两个新特性,一个是静态方法,一个是默认方法. static方法 java8中为接口新增了一项功能:定义一个或者多个静态方法. 定义用法和普通的static方法一样: public i ...

  7. php 生成订单号201807205598981

    php版 /** * 生成唯一订单号 */ public function build_order_no() { $no = date('Ymd').substr(implode(NULL, arra ...

  8. python爬虫之Phantomjs安装和使用

    phantomjs: PhantomJS是一个无界面的,可脚本编程的WebKit浏览器引擎.它原生支持多种web 标准:DOM 操作,CSS选择器,JSON,Canvas 以及SVG. phantom ...

  9. 读懂掌握 Python logging 模块源码 (附带一些 example)

    搜了一下自己的 Blog 一直缺乏一篇 Python logging 模块的深度使用的文章.其实这个模块非常常用,也有非常多的滥用.所以看看源码来详细记录一篇属于 logging 模块的文章. 整个 ...

  10. Spring拦截器(学习笔记)

    SpringMVC 中的Interceptor 拦截请求是通过HandlerInterceptor 来实现的 在SpringMVC 中定义一个Interceptor 非常简单,主要有两种方式 第一种方 ...