[P9167] [省选联考 2023] 城市建造
Posted JCY_std
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了[P9167] [省选联考 2023] 城市建造相关的知识,希望对你有一定的参考价值。
原题等价于计算有多少个点集 \\(V\\),满足删去 \\(V\\) 的导出子图中的边后,原图形成了 \\(|V|\\) 个连通块,且连通块大小的极差 \\(\\le k\\)。形成 \\(|V|\\) 个连通块又等价于 \\(V\\) 中的每个点都分属不同的连通块,我们称这样的 \\(V\\) 为合法的。
考虑在同一个点双连通分量中的三点 \\(u, v, w\\)。若 \\(u, v \\in V\\) 而 \\(w \\not \\in V\\),根据点双连通的性质,我们可以找到一条从 \\(u\\) 到 \\(v\\) 经过 \\(w\\) 的简单路径,此时必有两个 \\(V\\) 中的点属于同一个连通块。因此对于合法的 \\(V\\),同一个点双连通分量中的点,要么全在 \\(V\\) 中,要么只有一个在 \\(V\\) 中,要么全不在 \\(V\\) 中。
我们还可以发现,若 \\(V\\) 的导出子图不连通,则必有两个 \\(V\\) 中的点属于同一个连通块。因此对于合法的 \\(V\\),其导出子图必须连通。
上面两条性质启发我们建出原图的圆方树。不难发现,我们选择的 \\(V\\) 一定是在圆方树上“连通”的一些点双连通分量。具体地,用一个方点在 \\(S\\) 中代表该点双连通分量中的所有点都在 \\(V\\) 中,合法的 \\(V\\) 对应的 \\(S\\) 需要满足:若方点 \\(u, v \\in S\\),则所有在圆方树上 \\(u, v\\) 路径上的方点都在 \\(S\\) 中。我们称这样的 \\(S\\) 为合法的。
删去 \\(V\\) 的导出子图中的边后原图的连通块,和删去 \\(S\\) 中的方点后圆方树的连通块一一对应。定义圆方树上一个连通块的大小为连通块的圆点数,问题转化为计算有多少个圆方树上的合法方点点集 \\(S\\),满足删去 \\(S\\) 中的点后连通块大小的极差 \\(\\le k\\)。我们称这样的 \\(S\\) 为符合题意的。
考虑 \\(k \\le 1\\) 有什么用。不难猜想 \\(S\\) 一定包含重心,因为连通块的划分看起来十分平均。
证明:
令圆点的点权为 \\(1\\),方点的点权为 \\(0\\),找出圆方树的带权重心 \\(rt\\)。\\(rt\\) 的所有儿子子树内的点权和都 \\(\\le \\fracn2\\)。
如果 \\(rt\\) 为方点,则符合题意的 \\(S\\) 必然包含 \\(rt\\),因为若不包含则 \\(rt\\) 所在的连通块点权和 \\(\\ge \\fracn2 + 1\\),而 \\(rt\\) 不在的连通块点权和都 \\(\\le \\fracn2 - 1\\),无论 \\(k\\) 取多少都不符合题意。
如果 \\(rt\\) 为圆点,对于符合题意的 \\(S\\),\\(\\forall u \\in S\\),以 \\(rt\\) 为根时 \\(u\\) 的所有祖先方点都应属于 \\(S\\),证明同理。
证毕。
当 \\(rt\\) 为方点时以 \\(rt\\) 的任意相邻圆点为根,当 \\(rt\\) 为圆点时以 \\(rt\\) 为根,将圆方树定根。这样,对于符合题意的 \\(S\\),\\(\\forall u \\in S\\),\\(u\\) 的所有祖先方点都应属于 \\(S\\)。
先考虑 \\(k = 0\\) 时怎么做。
枚举连通块大小 \\(x\\),记 \\(sz_i\\) 表示圆方树定根后 \\(i\\) 的子树内的权值和。对于方点 \\(u\\),可以发现其是否属于 \\(S\\) 可以直接通过 \\(sz_u\\) 确定。若 \\(sz_u < x\\) 则 \\(u \\not \\in S\\)。若 \\(sz_u > x\\) 则 \\(u \\in S\\)。若 \\(sz_u = x\\),如果定根后 \\(u\\) 的儿子数 \\(\\ge 2\\) 则 \\(u \\not \\in S\\),否则 \\(u \\in S\\)。证明考虑反证,若不满足上述任一条件则 \\(S\\) 必然不符合题意,读者可自证。
因此可以直接从小到大枚举 \\(x\\),用并查集维护连通性,再开一个桶 \\(cnt_i\\) 记录连通块大小为 \\(i\\) 的连通块数量即可。
再考虑 \\(k = 1\\)。
容易想到一个容斥,枚举连通块大小的集合 \\(\\x, x + 1\\\\),钦定所有连通块大小属于该集合,计算方案数,然后减去连通块大小全为 \\(x\\) 或全为 \\(x + 1\\) 的方案数。类似 \\(k = 0\\),若 \\(sz_u < x\\) 则 \\(u \\not \\in S\\)。若 \\(sz_u > S\\) 则 \\(u \\in S\\)。若 \\(sz_u = x\\),如果定根后 \\(u\\) 的儿子数 \\(\\ge 2\\) 则 \\(u \\not \\in S\\),否则特殊处理。
特殊处理是 \\(k = 1\\) 的一个小难点。
先考虑 \\(x > 1\\) 的情况。
记 \\(fa_i\\) 为定根后点 \\(i\\) 的父亲。不难发现,对于需要特殊处理的 \\(u\\),若 \\(fa_u\\) 有若干个需要特殊处理的儿子,其中只能有至多一个 \\(\\not \\in S\\)。
我们先钦定所有需要特殊处理的点属于 \\(S\\),此时若 \\(fa_u\\) 所在连通块大小 \\(> 1\\),则其所有需要特殊处理的儿子均 \\(\\in S\\)。否则,\\(fa_u\\) 应有恰好一个儿子 \\(\\not \\in S\\)。即对于所有存在需要特殊处理的儿子,且当前所在连通块大小 \\(= 1\\) 的 \\(v\\),给答案乘上 点 \\(v\\) 需要特殊处理的儿子数量。
当 \\(x = 1\\) 时,我们应给答案乘上 点 \\(v\\) 需要特殊处理的儿子数量 加一。这是因为点 \\(v\\) 存在需要特殊处理的儿子,所以若 \\(v \\neq rt\\) 则 \\(fa_v \\in S\\),因此点 \\(v\\) 需要特殊处理的儿子可以都 \\(\\in S\\)。
同样这个过程可以用并查集加桶维护。
总时间复杂度 \\(O(n \\alpha(n))\\),瓶颈在并查集,但是精细实现可以不用并查集。
#include <bits/stdc++.h>
using namespace std;
using ll = long long;
using ull = unsigned long long;
using ld = long double;
template <typename T>
void chkmax(T &x, const T &y)
if (x < y) x = y;
template <typename T>
void chkmin(T &x, const T &y)
if (y < x) x = y;
constexpr int MAXN = 1e5 + 10, MOD = 998244353, INF = 0x3f3f3f3f;
void inc(int &x, int y) x += y, x >= MOD && (x -= MOD);
void dec(int &x, int y) x -= y, x < 0 && (x += MOD);
int n, k, dfn[MAXN], low[MAXN], dfc, stk[MAXN], tp, num, sz[MAXN * 2];
int mini, rt, ans1[MAXN], ans2[MAXN], fa[MAXN * 2], rec[MAXN];
bool vis[MAXN * 2];
vector<int> og[MAXN], g[MAXN * 2], buc[MAXN];
namespace dsu
int fa[MAXN], sz[MAXN], cnt[MAXN];
void init()
iota(fa + 1, fa + n + 1, 1);
fill(sz + 1, sz + n + 1, 1);
cnt[1] = n;
int find(int x) return x == fa[x] ? x : fa[x] = find(fa[x]);
void merge(int x, int y)
x = find(x);
y = find(y);
--cnt[sz[x]];
--cnt[sz[y]];
fa[x] = y;
sz[y] += sz[x];
++cnt[sz[y]];
// namespace dsu
void tarjan(int u)
dfn[u] = low[u] = ++dfc;
stk[++tp] = u;
for (auto v : og[u])
if (!dfn[v])
tarjan(v);
chkmin(low[u], low[v]);
if (low[v] == dfn[u])
g[u].emplace_back(++num);
g[num].emplace_back(u);
while (true)
int t = stk[tp--];
g[num].emplace_back(t);
g[t].emplace_back(num);
if (t == v) break;
else
chkmin(low[u], dfn[v]);
void get_root(int u, int pre)
sz[u] = (u <= n);
int tmp = 0;
for (auto v : g[u])
if (v == pre) continue;
get_root(v, u);
sz[u] += sz[v];
chkmax(tmp, sz[v]);
chkmax(tmp, n - sz[u]);
if (tmp < mini)
mini = tmp;
rt = u;
void dfs(int u, int pre)
fa[u] = pre;
sz[u] = (u <= n);
for (auto v : g[u])
if (v == pre) continue;
dfs(v, u);
sz[u] += sz[v];
if (u > n) buc[sz[u]].emplace_back(u);
int main()
freopen("cities.in", "r", stdin);
freopen("cities.out", "w", stdout);
ios::sync_with_stdio(false);
cin.tie(nullptr);
int m;
cin >> n >> m >> k;
while (m--)
int u, v;
cin >> u >> v;
og[u].emplace_back(v);
og[v].emplace_back(u);
num = n;
tarjan(1);
mini = INF;
get_root(1, 0);
if (rt > n) rt = g[rt][0];
dfs(rt, 0);
dsu::init();
for (int i = 1; i < n; ++i)
for (auto u : buc[i])
if (g[u].size() > 2)
for (int j = 1; j < (int)g[u].size(); ++j)
dsu::merge(g[u][j - 1], g[u][j]);
ans1[i] = (dsu::cnt[i] * i == n);
vector<int> vec;
for (auto u : buc[i])
if (g[u].size() == 2)
if (dsu::sz[dsu::find(fa[u])] == 1)
vis[u] = vis[fa[u]] = true;
dsu::merge(fa[u], g[u][g[u][0] == fa[u]]);
vec.emplace_back(fa[u]);
rec[fa[u]] = 1;
else if (vis[fa[u]])
++rec[fa[u]];
if (dsu::cnt[i] * i + dsu::cnt[i + 1] * (i + 1) == n)
ans2[i] = 1;
for (auto u : vec) ans2[i] = (ll)ans2[i] * (rec[u] + (i == 1)) % MOD;
for (auto u : buc[i])
if (g[u].size() == 2 && !vis[u])
for (int j = 1; j < (int)g[u].size(); ++j)
dsu::merge(g[u][j - 1], g[u][j]);
if (!k)
cout << accumulate(ans1 + 1, ans1 + n, 0) << "\\n";
else
int ans = MOD - 1;
for (int i = 1; i < n; ++i) inc(ans, ans2[i]);
for (int i = 2; i < n; ++i) dec(ans, ans1[i]);
cout << ans << "\\n";
return 0;
P6627 [省选联考 2020 B 卷] 幸运数字
P6627 [省选联考 2020 B 卷] 幸运数字
这道题乍一看的确挺简单,但是我就是写挂了(统计答案的锅)。100->35,省队无。
看完后就会发现:你只需要维护一个区间异或的操作,并且不需要在线修改。
那么,差分不就完事了吗。
//在[l,r]上对a[i]进行区间异或
a[l] ^= w, a[r] ^= w;
//最后统计答案前
for(int i = 2; i <= n; i++) a[i] ^= a[i-1];
那么,只要离散化数据,很轻松地就可以完成这个操作。由于要离散化,时复O(nlogn)
那么,统计答案怎么办呢?
我们来找找答案在哪里把。
首先,我们肯定要把那些询问的边界(A, B, L, R)(姑且称为边界点)保存下来,这些东西当然可能成为答案。
其次,我们发现,两个相邻的边界点(比如叫做(A)和(B))之间的一段区间([A+1,B-1])的答案必定是相同的。那么答案可能在这个区间上。那么问题来了:到底在哪里呢?根据这个鬼畜的答案统计规则,我们可以发现,它必定在区间的两端(即(A+1)和(B-1))或(0)处。
所以这样统计就好了。
记得数组空间开大!!!
#include<cstring>
#include<cstdio>
#include<algorithm>
using namespace std;
typedef long long ll;
ll read() {
ll x = 0, f = 1; char ch = getchar();
for(; ch < ‘0‘ || ch > ‘9‘; ch = getchar()) if(ch == ‘-‘) f = -1;
for(; ch >= ‘0‘ && ch <= ‘9‘; ch = getchar()) x = x * 10 + ch - ‘0‘;
return x * f;
}
const int MAXN = 1e5 + 5;
int n;
struct Node {
int t, x, y, w;
}q[MAXN];
int lsh[MAXN * 6], tot;
void LSH() {
sort(lsh + 1, lsh + 1 + tot);
tot = unique(lsh + 1, lsh + 1 + tot) - lsh - 1;
}
int LSH(int x) {
return lower_bound(lsh + 1, lsh + 1 + tot, x) - lsh;
}
int a[MAXN * 6];
int maxans, pos;
int main() {
n = read();
for(int i = 1; i <= n; i++) {
Node& now = q[i];
now.t = read();
if(now.t == 1)
now.x = read(), now.y = read();
else if(now.t == 2)
now.x = read();
else now.y = read();
lsh[++tot] = now.x;
lsh[++tot] = now.x - 1;
lsh[++tot] = now.x + 1;
lsh[++tot] = now.y;
lsh[++tot] = now.y - 1;
lsh[++tot] = now.y + 1;
now.w = read();
}
lsh[++tot] = 0;
LSH();
for(int i = 1; i <= n; i++) {
Node& now = q[i];
int tx = LSH(now.x), ty = LSH(now.y);
if(now.t == 1) a[tx] ^= now.w, a[ty + 1] ^= now.w;
else if(now.t == 2) a[tx] ^= now.w, a[tx + 1] ^= now.w;
else a[1] ^= now.w, a[ty] ^= now.w, a[ty + 1] ^= now.w;
}
for(int i = 2; i <= tot; i++) a[i] ^= a[i - 1];
maxans = a[1]; pos = 1;
for(int i = 2; i <= tot; i++) {
if(maxans < a[i]) maxans = a[i], pos = i;
else if(maxans == a[i]) {
if(abs(lsh[i]) < abs(lsh[pos])) maxans = a[i], pos = i;
else if(abs(lsh[i]) == abs(lsh[pos]) && lsh[i] > lsh[pos]) maxans = a[i], pos = i;
}
}
printf("%d %d
", maxans, lsh[pos]);
return 0;
}
以上是关于[P9167] [省选联考 2023] 城市建造的主要内容,如果未能解决你的问题,请参考以下文章