AT_agc004_f 题解

· · 题解

更好的阅读体验?

感谢 @Oier_szc 的建议。

以下将把某个点的颜色变为相反的颜色,称之为把颜色取反

1 树

先看树的部分分,直接做不那么直观,因为树是二分图,可以考虑把树黑白染色,还是看官解里的图:

这是转化之前样例一的染色方案。

这是转化之后样例一的染色方案。简单来说,就是按二分图的染色方式,将二分图被染成黑点的点的初始颜色取反,变成黑色,这样目标状态中,二分图被染成黑点的点的颜色也应被取反,变成白色。此时,限制条件由“两个点颜色相同”,变成了“两个点颜色不同”,由于条件的转换,我们可以把“将两点颜色取反”,变为“交换两点的颜色”。

梳理一下转化后的题意:

现在我们就可以直接得到,一棵树无解的充要条件是:将这棵树二分图黑白染色后,黑白点个数不相等。

进一步的,考虑对于一棵子树,假设其白点数量为 a,黑点数量为 b,那么,因为其目标状态为,其白点数量为 b,黑点数量为 a,且一次操作最多可以是黑点与白点的差加 2 或减 2,因此,发生在这棵子树的根连向其父亲的边上的交换操作至少为 |a - b| 次。

因此答案的下界为一棵树所有子树的黑白点数量之差,我们来大致证明一下操作数能达到这个下界。

我们考虑对于一条边 e = (u, v),如下图所示:

假设 u 点一侧的子树与 v 点一侧的子树,均满足黑白点数量已经与目标的黑白点数量相同,我们称此时边 e 处于平衡状态。

如果黑色的 A 点要按红色路径转移到白色的 B 点,因为这样打破了边 e 的平衡状态,必然存在黑色的 C 点要按蓝色路径转移到白色的 D 点。

那么这样显然劣于黑色的 A 点按黄色路径转移到白色的 D 点,黑色的 C 点按绿色路径转移到白色的 B 点。

所以,我们得到结论,若 e 处于平衡状态,那么可以直接断开 e,将原树划分成两颗子树,递归的解决问题,因此,这种处于平衡状态的边对答案没有影响。

再考虑不处于平衡状态的边,我们发现,对于一个边 e = (u, v) 上的交换操作,假设 u 侧缺黑点,v 侧多出了一些黑点。那么只有将黑点从 v 点转移到 u 才是不劣的,因此我们可以尝试对每一条边定向,表示黑点怎样转移才是优的,如下图所示(用双向边表示处于平衡状态的边)。

我们若想达到操作次数的下界,那么一定是将黑点向箭头方向移动,直到所有边都是平衡状态。

但是,假设出现了以下两种情况(B, C, D, ... 处表示的是一棵子树,而不是一个结点):

![](https://cdn.luogu.com.cn/upload/image_hosting/qzikf1vt.png) $A$ 处为黑点,且以 $A$ 点为端点的边都指向 $A$ 点,那么从 $B, C, D, ...$ 处的黑点就转移不到其他地方了(相当于 $A$ 处的黑点把路“堵住”了)。 这样看起来都达不到下界,但是我们可以证明两种情况均是不存在的。 > 证明:第一种情况中 $B, C, D, ...$ 处都缺黑点,即黑点个数小于白点个数,而 $A$ 点为白点,如果它的目标状态是黑点,那它也可以视为缺黑点,反之它不缺点,所以整棵树缺黑点,即黑点个数小于白点个数,无解,第二种情况同理。 通过以上结论,我们可以发现,边被定向后,整棵树大致构成一个 DAG,且 DAG 的若干个源点处一定是黑点,汇点处一定是白点,所以黑点只要沿 DAG 的方向流动,操作数一定能达到下界。 因此,我们大致证明了,总存在一种合法的方案使得答案能达到下界。 对每条边进行计数即可,具体实现中,可以将黑点权值设为 $1$,白点权值设为 $-1$,再统计每个子树权值和的绝对值之和。 ## 2 基环树且环为偶环 注意到图仍是二分图。所以这种情况下无解的情况与树相同。 通过上面的结论,我们也可以把所有不在环上的边定向,同时,我们对环上的点赋一个权值 $val$,表示将黑点权值设为 $1$,白点权值设为 $-1$,该子树所有结点权值和。 如果对树的做法理解透彻,你会发现每个子树的权值和,其实是由子树的根结点连向其父亲结点的边的有向权值。 这里“有向”的意思是,由子树的根结点的父亲结点连向它本身的边的有向权值,是由子树的根结点连向其父亲结点的边的有向权值的相反数,具体的,我们可以定义,由 $u$ 连向 $v$ 的有向权值 $val$,表示必须要由 $u$ 向 $v$ 输送 $val$ 个黑点($val > 0$),或是必须要由 $v$ 向 $u$ 输送 $-val$ 个黑点($val < 0$),才能使这条边达到平衡状态。 这样,我们可以假设,环上的点依次是 $p_1, p_2, \dots, p_k$,$p_i$ 的权值为 $a_i$,由 $p_i$ 连向 $p_{i \bmod k + 1}$ 的边的有向权值为 $y_i$。 这样,我们可以得到: $$a_i + y_{(i - 2 + k) \bmod k + 1} - y_i = 0.$$ 令 $x_i = -y_{(i - 2 + k) \bmod k + 1}$,得到: $$a_i - x_i + x_{i + 1} = 0$$ 也即: $$\begin{cases} x_1 - x_2 = a_1 \\ x_2 - x_3 = a_2 \\ \cdots \\ x_k - x_1 = a_k \end{cases}$$ 显然这个方程没有唯一解(例如,可以令 $x_i' = x_i + t$ 得到一组新解)。 我们的目标是最小化 $\sum_{i = 1}^k |x_i|$。注意到: $$ \begin{cases} x_1 = x_1 - 0 \\ x_2 = x_1 - a_1 \\ x_3 = x_2 - a_1 = x_1 - (a_1 + a_2) \\ \cdots \\ x_k = x_1 - \sum_{i = 1}^{k - 1} a_i \end{cases} $$ 因此: $$\sum_{i = 1}^k |x_i| = \sum_{i = 0}^{k - 1} |x_1 - \sum_{j = 1}^i a_j|$$ 问题转化为,在数轴上标出 $0$,$a_1$,$a_1 + a_2$,$\sum_{i = 0}^{k - 1} a_i$ 这 $k$ 个点,我们现在要找到一个点 $x_1$,使得 $x_1$ 到其它 $k$ 个点的距离和最短。这是一个经典问题,当 $x_1$ 取所有数的中位数时,总距离最小。 因此在求出环外各边权值的绝对值之和后,计算环上各边的权值,按上述步骤求处环上边的最小权值和即可。 ## 3 基环树且环为奇环 对于奇环的情况,考虑断掉环上的一条边后,整个图变成了一棵树,我们仍然对这棵树黑白染色,则该断掉的边的两个端点同色。 因此,这一条边的作用即为,当该边的两个端点颜色**相同**时,将两个端点的颜色取反。 这样,我们可以不断生成 $2$ 个黑点或白点,这种情况下,黑点与白点的差值只会加 $2$ 或减 $2$,同时,我们的目标是让原图中所有点的颜色被取反。 所以,我们得到了这种情况下,无解的充要条件是:在断掉环上的一条边并对这棵树黑白染色后,黑白点个数奇偶性相同,当然,这与“$N$ 为偶数”这一条件是等价的。 由树的情况,我们可以证明,这一条边只能不断生成黑点或不断生成白点(具体的,考虑这种情况是答案的下界,可以先把答案加上生成次数,再在这个结点上叠若干个黑点或白点,发现叠黑点的情况可以使树中的两个结论都成立)。 因此令 $x$ 为黑点个数减白点个数,则整棵树的权值和为 $x$,先将答案加上 $\frac{|x|}{2}$,然后叠黑点或白点,即将两个点的权值减去 $\frac{x}{2}$(注意整棵基环树的权值和为 $0$ 时,我们才能恰好补齐黑点或白点并把边割掉转化为树,原因在于,注意到我们每次加黑点或者白点,不仅加上了两个被生成的点的权值,还减去了两个被替换的异色点的权值,而在原树中我们并没有减去这些被替换点的贡献,这与被生成的点的权值恰好相抵消),再跑树的做法即可。 (想不到更好的讲法,若有请告知 QwQ) ```cpp #include <bits/stdc++.h> #define ll long long using namespace std; ll Read() { int sig = 1; ll num = 0; char c = getchar(); while(!isdigit(c)) { if(c == '-') { sig = -1; } c = getchar(); } while(isdigit(c)) { num = (num << 3) + (num << 1) + (c ^ 48); c = getchar(); } return num * sig; } void Write(ll x) { if(x < 0) { putchar('-'); x = -x; } if(x >= 10) { Write(x / 10); } putchar((x % 10) ^ 48); } const int N = 100005; int n; bool col[N]; vector<int> e[N]; ll ans = 0, val[N]; void Dfs(int u, int fa, bool pcol) { col[u] = !pcol; for(auto v : e[u]) { if(v == fa) { continue; } Dfs(v, u, col[u]); } } ll Dfs2(int u, int fa) { ll res = val[u]; for(auto v : e[u]) { if(v == fa) { continue; } res += Dfs2(v, u); } ans += abs(res); return res; } vector<int> st; bool vis[N], c[N], suc = false; int cnt = 0; vector<int> cir; void Dfs3(int u, int fa) { st.emplace_back(u); vis[u] = true; for(auto v : e[u]) { if(v == fa) { continue; } if(vis[v]) { if(!suc) { suc = true; bool b = false; for(auto x : st) { if(x == v) { b = true; } c[x] = b, cnt += b; if(b) { cir.emplace_back(x); } } } continue; } Dfs3(v, u); } st.pop_back(); } void Dfs4(int u, bool pcol) { col[u] = !pcol, vis[u] = true; for(auto v : e[u]) { if(vis[v]) { continue; } Dfs4(v, col[u]); } } ll Dfs5(int u, int fa) { ll res = col[u] ? 1 : -1; for(auto v : e[u]) { if(v == fa || c[v]) { continue; } res += Dfs5(v, u); } ans += abs(res); return res; } int main() { int i, m; n = Read(), m = Read(); for(i = 1; i <= m; i++) { int u = Read(), v = Read(); e[u].emplace_back(v), e[v].emplace_back(u); } if(m == n - 1) { Dfs(1, 0, false); int sum = 0, i; for(i = 1; i <= n; i++) { sum += col[i]; val[i] = col[i] ? 1 : -1; } if(sum * 2 != n) { printf("-1"); return 0; } Dfs2(1, 0); Write(ans); return 0; } Dfs3(1, 0); if(cnt & 1) { int u, v; for(i = 1; i <= n; i++) { if(c[i]) { u = i; break; } } for(auto x : e[u]) { if(c[x]) { v = x; break; } } for(i = 0; i < e[u].size(); i++) { if(e[u][i] == v) { break; } } e[u].erase(e[u].begin() + i); for(i = 0; i < e[v].size(); i++) { if(e[v][i] == u) { break; } } e[v].erase(e[v].begin() + i); Dfs(1, 0, false); int sum = 0, i; for(i = 1; i <= n; i++) { sum += col[i]; val[i] = col[i] ? 1 : -1; } if(n & 1) { printf("-1"); return 0; } ll x = n / 2 - sum; val[u] += x, val[v] += x; Dfs2(1, 0); Write(ans + abs(x)); } else { memset(vis, 0, sizeof(vis)); Dfs4(1, false); int sum = 0; for(i = 1; i <= n; i++) { sum += col[i]; val[i] = col[i] ? 1 : -1; } if(sum * 2 != n) { printf("-1"); return 0; } memset(vis, 0, sizeof(vis)); vector<ll> vec; for(auto v : cir) { ll x = Dfs5(v, 0); vec.emplace_back(x), ans -= abs(x); } for(i = 1; i < vec.size(); i++) { // \sum val_i = 0 vec[i] += vec[i - 1]; } sort(vec.begin(), vec.end()); ll x = vec[vec.size() / 2]; for(auto v : vec) { ans += abs(x - v); } Write(ans); } return 0; } ```