AGC004F Namori 树形DP、解方程(?)

时间:2023-03-10 02:27:26
AGC004F Namori 树形DP、解方程(?)

传送门


因为不会列方程然后只会树上的,被吊打了QAQ

不难想到从叶子节点往上计算答案。可以考虑到可能树上存在一个点,在它的儿子做完之后接着若干颜色为白色的儿子,而当前点为白色,只能帮助一个儿子变成黑色,所以需要寻求父亲的帮助,强制让父亲变为黑色若干次,然后将当前点和父亲同时反转成白色,然后将这个点和儿子一起反转成黑色。

所以设\(f_i\)表示\(i\)强制被染成黑色的次数,若\(f_i < 0\)表示要被强制染成白色\(-f_i\)次,转移:\(f_i = 1 - \sum\limits_{u \in son_i} f_u\)(因为儿子强制染成白色若干次就是其父亲强制染成黑色这么多次然后将它和儿子一起反转,反之亦然),最后若\(f_1 \neq 0\)则无解,否则答案为\(\sum\limits_{i=1}^N f_i\)。

考虑基环树上怎么做。先把树上的环搜出来,对于环上每一个点的子树做树算法,那么环上的每一个点都有强制变为黑色/白色若干次的限制。

这个时候没有“父亲”的概念了,现在所有可利用的边都是让两个点同时变黑一次或者变白一次。所以设\(a_i\)表示连接环上第\(i\)个点和第\(i+1\)个点的边两边的点同时从白色变为黑色的次数,可以得到\(l\)个方程(\(l\)为环长):

\(\begin{align*} a_1 + a_2 = f_2 & (1) \\ a_2 + a_3 = f_3 & (2) \\ ... \\ a_1 + a_l = f_l & (l) \end{align*}\)

不难知道:所有\(a_i\)在且仅在所有式子中出现\(2\)次。那么我们可以将所有式子相加可得

\(\sum\limits_{i = 1} ^ l a_i = \frac{\sum\limits_{i=1}^l f_i}{2}(*)\)

当然\(2 \not\mid \sum f_i\)时无解。

如果\(2 \not\mid l\),意味着接下来\((*) - (1) - (3) - ... - (l-2)\)可以得到\(a_l\),进而推知所有\(a_i\)的值。但是\(2 \mid l\)时就没有这个性质了,因为\((l)\)式的左边可以由\((l-1)-(l-2)+(l-3)-...- (2) + (1)\)得到,所以有一个*元。不妨设*元为\(a_l\),那么所有\(a_i\)都可以表示成\(val_i \pm a_l\)的形式。我们要最小化的是\(\sum\limits_{i=1}^N |val_i \pm a_l|\),即\(\sum\limits_{i=1}^N |a_l \pm val_i|\),由小学奥数取中位数时有最小值。

注意一个判断无解的地方:当图为基环树且\(2 \mid l\)时,\((l) = (l-1)-(l-2)+(l-3)- ... - (2) + (1)\),如果不相等也是无解。

#include<cstdio>
#include<cstdlib>
#include<cstring>
#include<iostream>
#include<cmath>
#include<algorithm>
#include<vector>
using namespace std; inline int read(){
int a = 0;
char c = getchar();
while(!isdigit(c)) c = getchar();
while(isdigit(c)){
a = a * 10 + c - 48;
c = getchar();
}
return a;
} const int MAXN = 1e5 + 7;
struct Edge{
int end , upEd;
}Ed[MAXN << 1];
int head[MAXN] , N , M , cntEd;
bool vis[MAXN]; inline void addEd(int a , int b){
Ed[++cntEd].end = b;
Ed[cntEd].upEd = head[a];
head[a] = cntEd;
} int ans; int dfs(int x){
vis[x] = 1;
int col = 0 , more = 0;
for(int i = head[x] ; i ; i = Ed[i].upEd)
if(!vis[Ed[i].end]){
int t = dfs(Ed[i].end);
if(t > 0) more += t;
else col -= t;
}
ans += more + col;
return col - more - 1;
} namespace solveTree{
void main(){
if(dfs(1))
cout << "-1";
else
cout << ans;
}
} namespace solveCir{
vector < int > cir , val;
int find(int x , int p){
vis[x] = 1;
for(int i = head[x] ; i ; i = Ed[i].upEd)
if(Ed[i].end != p)
if(vis[Ed[i].end]){
cir.push_back(x);
return Ed[i].end;
}
else{
int t = find(Ed[i].end , x);
if(t){
cir.push_back(x);
return x == t ? 0 : t;
}
}
vis[x] = 0;
return 0;
} inline int abss(int x){return x < 0 ? -x : x;} void main(){
find(1 , 0);
for(auto t : cir)
val.push_back(-dfs(t));
int sum = 0;
for(auto t : val)
sum += t;
if(sum & 1){puts("-1"); return;}
sum >>= 1;
if(cir.size() & 1){
for(int i = 0 ; i + 1 < cir.size() ; i += 2)
sum -= val[i];
ans += abss(sum);
for(int i = (int)val.size() - 2 ; i >= 0 ; --i){
sum = val[i] - sum;
ans += abss(sum);
}
}
else{
int cur = 0;
for(auto t : val)
cur = t - cur;
if(cur){puts("-1"); return;}
vector < int > now;
now.push_back(0);
cur = 0;
bool f = 0;
for(int i = (int)val.size() - 2 ; i >= 0 ; --i){
cur = val[i] - cur;
now.push_back(f ? -cur : cur);
f ^= 1;
}
sort(now.begin() , now.end());
int mid = now[((int)now.size() - 1) >> 1];
for(auto t : now)
ans += abss(mid - t);
}
cout << ans;
}
} signed main() {
N = read();
M = read();
for(int i = 1 ; i <= M ; ++i){
int a = read() , b = read();
addEd(a , b);
addEd(b , a);
}
if(M == N - 1)
solveTree::main();
else
solveCir::main();
return 0;
}