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) << 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;

}
posted @ 2019-02-14 16:07  zjp_shadow  阅读(789)  评论(0编辑  收藏  举报