闲话

我不理解为什么我能现在写完所有题的题解。
我们仍然回忆数据结专题

话说回来 我说“蒜”能有几个人知道我在说什么呢(
就是那个 《石蒜物语》(
我反正不是很习惯这个译名 什么诡异的中世纪风格啊这是
这种译名果然是直接音译会好听 不尬 点吧

所以我切完了!
不写代码了就(

图论

这就是图论吗?
这边建议组题人加几道黑题(
alex_wei这有讲解

然后写点题目看法(
感觉题解会比题面短



CF76A

一张 \(n\) 个点 \(m\) 条边的图,每条边有两个属性 \((g_i, s_i)\)。给定 \(G, S\),求一棵图的生成树 \(T\),使得 \(G \times \max(g_i) + S \times \max (s_i)\) 最小。(\(i\in T\))注意:图可能包含重边和自环。

\(n\le 200, m\le 10^5\)

考虑 \(n\) 比较小。

对于这种 pair 型信息考虑先维护一维升序再维护另一维。
我们首先按 \(g_i\) 排序,然后维护 \(g_i \le g_{now}\) 的所有边形成的图。然后对着 \(s_i\) 建最小生成树。然后 \(O(m^2)\) 完了。
注意到每次只需要选择原先最小生成树中的边以及新加入的一条边。这样边数是 \(O(n)\) 的。归并能得到 \(O(nm)\) 的复杂度。

没实现。

\(n,m\le 10^6\)

更新最小生成树时用 LCT 维护子树的 \(\max\{s_i\}\)\(\max\{g_i\}\)。实子树扔到 splay 上,虚子树扔到 set 里面。
然后能得到 \(O(m\log n)\)

不会,没贺。



[模板]严格次小生成树

对。给图求严格次小生成树。

每条不在 MST 上的边都可以覆盖两端点在 MST 上的路径。
对于这路径求出最大值,然后用本条边替换就能得到一个非次小生成树。对每条边这么做一遍取 \(\min\) 得到的就是非严格次小生成树。
在最大值与本条边相同时记录次大值替换。对每条边这么做一遍取 \(\min\) 得到的就是严格次小生成树。

先拿到 MST,然后枚举边。可以倍增求一条链上的最大值。注意维护次大值时是否存在次大值。总时间复杂度 \(O(m\log m + m\log n)\)

code
const int N = 1e5 + 10;
const int inf = 0x3f3f3f3f3f3f3f3fll;
const ll infll = 0x3f3f3f3f3f3f3f3fll;

int n, m, t1, t2, ans = inf, tot;
vector <pair<int,int> > g[N];
struct edge {
	int u, v, w;
	bool operator < (const edge & b) const { return w < b.w; }
} e[N * 3];

int fa[N];
int find(int u) { return u == fa[u] ? u : fa[u] = find(fa[u]); }

int f[N][20], fir[N][20], sec[N][20], res[5], dep[N], lgv[N];
void dfs(int u, int fat) {
	sec[u][0] = -inf; f[u][0] = fat; dep[u] = dep[fat] + 1;
	int cnt = 0;
	for (int i = 1; i <= lgv[dep[u]] + 1; ++ i) {
		f[u][i] = f[f[u][i - 1]][i - 1];
		cnt = 0; res[++cnt] = fir[u][i - 1]; res[++cnt] = sec[u][i - 1],
		res[++cnt] = fir[f[u][i - 1]][i - 1], res[++cnt] = sec[f[u][i - 1]][i - 1];
		sort(res + 1, res + cnt + 1);
		fir[u][i] = res[cnt];
		int ptr = cnt;
		while (ptr > 0 and res[ptr] == res[cnt]) -- ptr;
		sec[u][i] = (ptr == 0 ? -inf : res[ptr]);
	}
	for (auto [v, w] : g[u]) if (v != fat) {
		fir[v][0] = w;
		dfs(v, u);
	}
}

int lca(int u, int v) {
	if (dep[u] < dep[v]) swap(u, v);
	pre(i,19,0) if (dep[f[u][i]] >= dep[v]) u = f[u][i];
	if (u == v) return u;
	pre(i,19,0) if (f[u][i] != f[v][i]) u = f[u][i], v = f[v][i];
	return f[u][0];
}

int qry(int u, int prt, int val) {
	int ret = -inf;
	pre(i,19,0) if (dep[f[u][i]] >= dep[prt]) {
		if (fir[u][i] == val) ret = max(ret, sec[u][i]);
		else ret = max(ret, fir[u][i]);
		u = f[u][i];
	} assert(u == prt);
	return ret;
}

signed main() {
	get(n, m); rep(i,1,m) get(e[i].u, e[i].v, e[i].w);
	rep(i,1,n) fa[i] = i;
	rep(i,2,n) lgv[i] = lgv[i >> 1] + 1;
	sort(e + 1, e + 1 + m); 
	rep(i,1,m) if ((t1 = find(e[i].u)) != (t2 = find(e[i].v))) {
		fa[t1] = t2;
		g[e[i].u].emplace_back(e[i].v, e[i].w);
		g[e[i].v].emplace_back(e[i].u, e[i].w);
		tot += e[i].w; e[i].w = inf;
	} dfs(1, 0); 
	rep(i,1,m) if (e[i].w != inf and e[i].u != e[i].v) {
		int LCA = lca(e[i].u, e[i].v);
		int val = max(qry(e[i].u, LCA, e[i].w), qry(e[i].v, LCA, e[i].w));
		if (val != -inf) {
			ans = min(ans, tot - val + e[i].w);
		}
	} cout << ans << endl;
}



CF1076D

给一个 \(n\) 个点,\(m\) 条边的无向简单带权连通图,要求删边至最多剩余 \(k\) 条边。定义”好点”是指删边后, 1号节点到它的最短路长度仍然等于原图最短路长度的节点.

最大化删边后的好点个数.

\(n, m \le 3 \times 10^5\).

留最短路树上的边最优。
\(1\) 节点跑 dijkstra,记录更新每个点的节点与边 id。这个节点就是被更新节点的父亲。 在新树上做 dfs,输出 dfs 到的前 k 条边的 id 即可。

注意 \(k\) 最大 \(n-1\) 就足够了。总时间复杂度 \(O(m\log n)\)

code
int n, m, k, t1, t2, t3;
vector <pair<int,int> > g[N];
int head[N], mlc;
struct {
	int v, n, w, id;
} e[N << 1];
void adde(int u, int v, int w, int id) {
	e[++mlc] = {v, head[u], w, id};
	head[u] = mlc;
	e[++mlc] = {u, head[v], w, id};
	head[v] = mlc;
} 

ll dis[N]; bool vis[N];
pii fa[N];
priority_queue<pair<ll,int> > que;
void dij(int st) {
	rep(i,1,n) dis[i] = infll, vis[i] = false;
	dis[st] = 0; que.emplace(0, st);
	while (que.size()) {
		int u = que.top().second; que.pop();
		if (vis[u]) continue; vis[u] = 1;
		Aster(i, u) {
			int v = e[i].v;
			if (dis[v] > dis[u] + e[i].w) {
				dis[v] = dis[u] + e[i].w;
				fa[v] = {u, e[i].id};
				if (!vis[v]) que.emplace(-dis[v], v);
			}
		}
	}
}

void dfs(int u) {
	if (k == 0) exit(0);
	for (auto [v, w] : g[u]) {
		cout << w << ' '; -- k;
		dfs(v);
	}
}

int main() {
	get(n, m, k); rep(i,1,m) get(t1, t2, t3), adde(t1, t2, t3, i);
	dij(1); if (k > n - 1) k = n - 1; cout << k << '\n';
	rep(i,2,n) g[fa[i].first].emplace_back(i, fa[i].second);
	dfs(1);
}



CF1650G

给定一张不带权无向图,求出与 \((s,t)\) 之间的与最短路长度差不超过 \(1\) 的路径条数对 \(10^9+7\) 取模后的值。

听说难调?所以没写。

打个分层图。然后最短路上的边直接乘一下就行。
然后发现“与最短路长度差不超过 \(1\) ”,因此分层图上只能经过一次层内边。
枚举一下边,看走过这条边是不是非更优即可。

总时间复杂度 \(O(m)\)



给定 \(n\) 个点 \(m\) 条边的无向图,\((u_i,v_i)\)\(w_i\) 的路程,通过这条边要用 \(w_i\) 秒。

你需要从 \(1\) 号点走到 \(n\) 号点。同时你可以在走前执行若干次(可以为 \(0\) 次)如下操作:

对于任意三点 \(u,v,t\)\(u\)\(t\) 可以相同),若 \(u\)\(v\) 之间有边 \(i\),且 \(v\)\(t\) 有边 \(j\),则可以用 \(w_i\) 秒断开边 \(i\),并且在 \(u\)\(t\) 之间连一条权值为 \(w_i\) 的边。

求至少要少秒才能从 \(1\) 号点走到 \(n\) 号点(时间包括修改边的时间)。

\(2\le n\le 500 ,\ n-1\le m\le 250000\)

可以发现最优决策肯定是把一条边 \((u_i,v_i,w_i)\) 一直挪到成为 \((1,n)\) 最优。这样的代价是 \(w_i\times ([挪动次数] + 1)\) 的。
考虑给定一条边,如何最小化挪动次数。

首先我们可以发现,挪动次数与边权无关,因此我们可以首先将原图上的边权置为 \(1\),得到一张新图。
随后我们可以发现,一个端点 \(u\) 移动到 \(v\) 的最小移动次数是 \(u,v\) 在新图上的最短路长度。因此首先在新图上跑一遍 floyed 算法得到两点间最短路 \(dis(u,v)\)

然后分情况讨论 \((u,v)\) 边的挪动次数:

  1. 两点分别走到 \(1,n\) 两点。
  2. 其中一个点走到 \(1/n\) 点,随后让另一个点一步挪动到 \(1/n\) 点,自己再从 \(1/n\) 点走到 \(n/1\) 点。
  3. 枚举中转点 \(j\)。其中一个点走到 \(j\) 点,随后让另一个点一步挪动到 \(j\) 点,再分别走到 \(1,n\) 两点。

预处理复杂度 \(O(n^3)\),枚举边并判断复杂度 \(O(nm)\)

code
int T, n, m, t1, t2;
ll ans;
struct {int u, v, w;} e[N];
int g[501][502];

int main() {
	get(T);
	while (T--) {
		get(n, m); rep(i,1,n) rep(j,1,n) if (i != j) g[i][j] = inf; ans = infll;
		rep(i,1,m) get(e[i].u, e[i].v, e[i].w), g[e[i].u][e[i].v] = g[e[i].v][e[i].u] = 1;
		rep(k,1,n) rep(i,1,n) rep(j,1,n) g[i][j] = min(g[i][j], g[i][k] + g[k][j]);
		rep(i,1,m) {
			int now = min( {g[1][e[i].u] + g[e[i].v][n], g[1][e[i].v] + g[e[i].u][n], g[1][e[i].u] + 1 + g[1][n], g[1][e[i].v] + 1 + g[1][n], g[n][e[i].u] + 1 + g[1][n], g[n][e[i].v] + 1 + g[1][n]} );
			rep(j,2,n-1) now = min( {now, g[e[i].u][j] + 1 + g[1][j] + g[j][n], g[e[i].v][j] + 1 + g[1][j] + g[j][n]} );
			ans = min(ans, 1ll * e[i].w * (now + 1));
		} cout << ans << '\n';
	}
}



[SDOI2009] Elaxia的路线

给定一张 \(n\) 个点 \(m\) 条边的无向图。给定两对点,求两对点分别的最短路的最长公共路径。

\(1\le n \le 1500\)\(1 \leq m \leq 3 \times 10^5\)\(1\le w \le 10^4\)

考虑分别建出两对点的最短路 dag。
\((u,v)\) 的最短路 dag 是先得到 \(u,v\) 分别的最短路树,随后找到同时位于两棵树上的边,取 \(u\to v\) 为正方向即得。

然后定义两条边相交当且仅当这两条边端点相同方向相同。定义两个 dag 的交为两个 dag 中相交的边的集合。
容易发现两对点最短路 dag 的交肯定是一堆简单路径的集合。也就是说这个集合形成的图也是个 dag。跑拓扑排序找到最长链即为答案。

总时间复杂度 \(O(4m\log n + m)\)
没写代码。



无序字母对

sb 题。直接跑欧拉回路即可。
不知道为什么组题人在看到字符串专题的难度后有闲心开玩笑。



长脖子鹿放置

众周所知,在西洋棋中,我们有城堡、骑士、皇后、主教和长脖子鹿。

西洋棋的“长脖子鹿”,类似于中国象棋的马,但按照“目”字攻击,且没有中国象棋“别马腿”的规则。(因为长脖子鹿没有马腿)

给定一个\(n * m\) 的棋盘,有一些格子禁止放棋子。问棋盘上最多能放多少个不能互相攻击的长脖子鹿。

\(1 ≤ n,m ≤ 200\)

[模板]二分图最大匹配。

都做过这题吗?没做过有关系,因为双倍经验。但其实没什么关系,因为要改的地方不少,没做过不理解的话不是很能改出来。

现在是走 \(1\times 3\) 的格子了。然后发现无论怎么跳都会使得列奇偶性发生改变,因此奇偶列分别为左部右部点建二分图即可。两点有边当且仅当两边都能放棋子且能到达。
求这个二分图的最大独立集。就是求最大匹配然后 \(n\times m – [不能放棋子的点]\) 减去最大匹配。
最大匹配可以最大流求。

为什么我要写 \([不能放棋子的点]\) 而不是 \(k\)?因为这题 nmd 不保证禁止放置的格子互不相同。
当时调了半个小时始终不知道哪的问题。

时间复杂度 \(O(\text{Dinic}(nm,4nm))\)



生活在树上(Hard)

给定一棵 \(n\) 个点的树,点有点权 \(w_i\)。两点距离 \(dis(u,v)\)\(u\to v\) 简单路径上节点权值异或和。路径包含端点。

\(q\) 次询问形如 \((u,v,k)\),查询是否存在一个节点 \(t\) 满足 \(dis(u,t) \oplus dis(t,v) = k\)

\(1 \leq a,b \leq n\)\(a \neq b\)\(0 \leq k \leq 1\times 10^7\)

打了个树上莫队。
容易发现我们只需要查看 \(u\to v\) 路径上是否有节点 \(t\) 满足 \(dis(u,v) \oplus w_t = k\) 即可。
证明考虑讨论 \(u,v\) 子树内点与 \(LCA(u,v)\) 的祖先,然后发现这些点的答案就是 \(t = u/v\)\(t=LCA(u,v)\) 的贡献。路径上节点答案显然。

然后可以树上莫队或者树上差分或者树剖
因为莫队好写而且不容易被卡写了个莫队。跑得不慢。

时间复杂度 \(O(n\sqrt n)\)
正解求 \(LCA\) 直接离线 Tarjan,因此总复杂度 \(O(n\ \alpha(n)\)

code
int n, m, t1, t2, w[N], l[N], seq[N << 1], beg[N], ed[N], bel[N << 1], stp;
bool ans[N];
vector <int> g[N];
struct query {
	int u, v, bl, lca, k, id;
	bool operator < (const query& b) const {
		if (bl != b.bl) return bl < b.bl;
		if (bl & 1) return v > b.v;
		return v < b.v;
	}
} e[N];

int top[N], fa[N], son[N], siz[N], dep[N];
void dfs(int u, int f) {
	fa[u] = f, siz[u] = 1, dep[u] = dep[f] + 1;
	seq[++ stp] = u, beg[u] = stp; l[u] = l[f] ^ w[u];
	for (auto v : g[u]) if (v != f) { 
		dfs(v, u); siz[u] += siz[v];
		if (siz[v] > siz[son[u]]) son[u] = v;
	} seq[++ stp] = u, ed[u] = stp;
}

void dfs2(int u, int tp) {
	top[u] = tp;
	if (son[u]) dfs2(son[u], tp);
	for (auto v : g[u]) if (v != fa[u] and v != son[u]) dfs2(v, v);
}

int LCA(int u, int v) {
	while (top[u] != top[v]) {
		if (dep[top[u]] < dep[top[v]]) swap(u, v);
		u = fa[top[u]];
	} return dep[u] < dep[v] ? u : v;
}

int dis(int u, int v, int & lca) {
	lca = LCA(u, v);
	return l[u] ^ l[v] ^ l[lca] ^ l[fa[lca]];
}

int cnt[1 << 24 | 3];
bool vis[N];
#define upd(pos) \
	vis[pos] ^= 1;\
	if (vis[pos] == 1) cnt[w[pos]] ++;\
	else cnt[w[pos]] --;

int main() {
	get(n, m); 
	rep(i,1,n) get(w[i]); rep(i,2,n) get(t1, t2), g[t1].emplace_back(t2), g[t2].emplace_back(t1);
	dfs(1, 0); dfs2(1, 1); int siz = sqrt(stp);
	rep(i,1,m) {
		get(e[i].u, e[i].v, e[i].k); e[i].k ^= dis(e[i].u, e[i].v, e[i].lca);
		if (beg[e[i].u] > beg[e[i].v]) swap(e[i].u, e[i].v); 
		if (e[i].u == e[i].lca) {
			e[i].u = beg[e[i].u], e[i].v = beg[e[i].v]; e[i].lca = 0;
		} else {
			e[i].u = ed[e[i].u], e[i].v = beg[e[i].v]; 
		} e[i].id = i; e[i].bl = (e[i].u - 1) / siz + 1;
	} 
	sort(e + 1, e + 1 + m);
	for(int i(1), l(1), r(0); i <= m; ++ i) {
		while (l > e[i].u) {
			-- l;
			upd(seq[l]);
		} 
		while (r < e[i].v) {
			++ r;
			upd(seq[r]);
		} 
		while (l < e[i].u) {
			upd(seq[l]);
			++ l;
		} 
		while (r > e[i].v) {
			upd(seq[r]);
			-- r;
		} 
		if (e[i].lca) upd(e[i].lca);
		ans[e[i].id] = (cnt[e[i].k] > 0);
		if (e[i].lca) upd(e[i].lca);
	} 
	rep(i,1,m) puts(ans[i] ? "Yes" : "No");
}



CF427C

你的城市有 \(n\) 个路口。路口之间有一条单程道路。作为城市的市长,你必须确保所有路口的安全。为了确保安全,你必须建造一些警察检查站。一个检查站只能建在一个路口。 如果有一个检查站在i路口,保护j的条件是:\(i=j\) 或者警察巡逻车可以从 \(i\) 走到 \(j\),并且能回到 \(i\)

建造检查站要花一些钱。 由于城市的某些地区比其他地区更昂贵,在某些路口修建检查站可能比其他路口花费更多的钱。你必须确定以确保所有路口的安全所需的最低资金。此外,你必须找到以的最低价格和在最小数量的检查站确保安全的方案数。

如果其中任何一个路口包含其中一个检查点而不包含在另一个路口中,则两种方式是不同的。

答案模 \(10^9 + 7\)

你发现一个强连通分量里必须建一个,而且建一个足够了。
所以找到所有强连通分量,找到每个里面的最小值及最小值数量,答案就是最小值加和以及数量乘积。
加和记得开 long long

code
const int mod = 1e9 + 7;
const int inv2 = (mod + 1) >> 1;
template <typename T1, typename T2> T1 add(T1 a, T2 b) { return (a += b) >= mod ? a - mod : a; }
template <typename T1, typename ...Args> T1 add(T1 a, Args ... b) { return add(a, add(b...)); }
struct FastMod { int m; ll b; void init(int _m) { m = _m; if (m == 0) m = 1; b = ((lll)1<<64) / m; } FastMod(int _m) { init(_m); } int operator() (ll a) {ll q = ((lll)a * b) >> 64; a -= q * m; if (a >= m) a -= m; return a; } } Mod(mod);
template<typename T1, typename T2> int mul(T1 a, T2 b) { ll _a = static_cast<ll>(a), _b = static_cast<ll>(b); add(a, 0), add(b, 0); return Mod(a * b); } int mul(int a, int b) { return Mod(1ll * a * b); } template <typename T1, typename ...Args> int mul(T1 a, Args ...b) { return mul(a, mul(b...)); }

int n, m, t1, t2, w[N];
vector <int> g[N];

int stk[N], top, low[N], dfn[N], stp;
ll mlc, mn[N], cnt[N];
bool instk[N];
void tarjan(int u) {
	stk[++top] = u; instk[u] = 1; low[u] = dfn[u] = ++ stp;
	for (auto v : g[u]) {
		if (!dfn[v]) {
			tarjan(v);
			low[u] = min(low[u], low[v]);
		} else if (instk[v]) {
			low[u] = min(low[u], dfn[v]);
		}
	} if (low[u] == dfn[u]) {
		int t = -1; ++ mlc; mn[mlc] = 1e9;
		do {
			t = stk[top]; instk[t] = false; 
			if (mn[mlc] > w[t]) mn[mlc] = w[t], cnt[mlc] = 1;
			else if (mn[mlc] == w[t]) ++ cnt[mlc];
			-- top;
		} while (t != u); 
	}
}

int main() {
	get(n); rep(i,1,n) get(w[i]);
	get(m); rep(i,1,m) get(t1, t2), g[t1].emplace_back(t2);
	rep(i,1,n) if (!dfn[i]) tarjan(i);
	ll ans1 = 0, ans2 = 1;
	rep(i,1,mlc) ans1 += mn[i], ans2 = mul(ans2, cnt[i]);
	cout << ans1 << ' ' << ans2 << endl;
}



[POI2008]BLO-Blockade

B 城有 \(n\) 个城镇,\(m\) 条双向道路。每条道路连结两个不同的城镇,没有重复的道路,所有城镇连通。把城镇看作节点,把道路看作边,容易发现,整个城市构成了一个无向图。

请你对于每个节点 \(i\) 求出,把与节点 \(i\) 关联的所有边去掉以后(不去掉节点 \(i\) 本身),无向图有多少个有序点对 \((x,y)\),满足 \(x\)\(y\) 不连通。

\(n \le 10^5, m\le 5\times 10^5\)

求个割点。
非割点肯定对除自己以外的点没有影响。答案就是 \(2(n-1)\)
割点就是把他联通的那几块的大小乘起来加上 \(2(n-1)\)

这些答案的计算都可以在 tarjan 时完成。因此总复杂度 \(O(n + m)\)

代码古早,不放。

原文地址:http://www.cnblogs.com/joke3579/p/chitchat221114.html

1. 本站所有资源来源于用户上传和网络,如有侵权请邮件联系站长! 2. 分享目的仅供大家学习和交流,请务用于商业用途! 3. 如果你也有好源码或者教程,可以到用户中心发布,分享有积分奖励和额外收入! 4. 本站提供的源码、模板、插件等等其他资源,都不包含技术服务请大家谅解! 5. 如有链接无法下载、失效或广告,请联系管理员处理! 6. 本站资源售价只是赞助,收取费用仅维持本站的日常运营所需! 7. 如遇到加密压缩包,默认解压密码为"gltf",如遇到无法解压的请联系管理员! 8. 因为资源和程序源码均为可复制品,所以不支持任何理由的退款兑现,请斟酌后支付下载 声明:如果标题没有注明"已测试"或者"测试可用"等字样的资源源码均未经过站长测试.特别注意没有标注的源码不保证任何可用性