【题解】Counting Cycles ICPC 亚洲赛区 日本 K 题
https://vjudge.net/problem/Aizu-1388
考虑建立虚树后,枚举非树边子集 \(S\)。现给出一个结论
钦定一些非树边要在简单环(不能经过同一个结点多次)中,成环方案不超过 \(1\)。
证明:
考虑每一条树边是否存在于该环中,判据为去掉这条边树上分成的两个连通块中某一个连通块内 \(\sum d_i\)(度数和,此时已经去掉该树边) 的奇偶性,若为奇数,则在环中,若为偶数则不在。根据度的定义,不可能出现两个连通块内度数奇偶性不一样的情况。
如果存在简单环,显然每个点度数要么是 0 要么是 2。于是方案只有可能 \(0\) 或 \(1\)。
于是对于非树边子集,判断是否可行可在 \(O(e)\) 的时间内完成。
丑陋的代码
#include <bits/stdc++.h>
#define debug(...) fprintf(stderr ,__VA_ARGS__)
const int MX = 1e5 + 64;
int read(){
char k = getchar(); int x = 0;
while(k < '0' || k > '9') k = getchar();
while(k >= '0' && k <= '9') x = x * 10 + k - '0' , k = getchar();
return x;
}
int tree1[MX] ,tree2[MX] ,tot;
struct edge{
int node ,next;
}h[MX * 4];
void addedge(int u ,int v ,int *head = tree1){
h[++tot] = (edge){v ,head[u]};
head[u] = tot;
//debug("%d %d ok\n" ,u ,v);
}
std::vector<std::pair<int ,int> > e;
// extra edge
std::vector<int> S;
int inside[MX];
void addvertex(int x){
if(inside[x]) return;
S.push_back(x);
S.push_back(-x);
inside[x] = true;
}
int n ,m ,DFN[MX][2] ,depth[MX] ,dfncnt ,fa[MX];
void dfs(int x){
const int *head = tree1;
DFN[x][0] = ++dfncnt;
for(int i = head[x] ,d ; i ; i = h[i].next){
d = h[i].node;
if(!DFN[d][0]){
depth[d] = depth[x] + 1;
fa[d] = x;
dfs(d);
}
else if(d != fa[x] && depth[d] < depth[x]){
//debug("cum %d %d\n" ,x ,d);
addvertex(x);
addvertex(d);
e.push_back(std::make_pair(x ,d));
}
}
DFN[x][1] = ++dfncnt;
}
int getdfn(int x){
return x > 0 ? DFN[x][0] : DFN[-x][1];
}
bool cmp(int x ,int y){
return getdfn(x) < getdfn(y);
}
int LCA(int x ,int y){
while(x != y){
// debug("%d %d\n" ,x ,y);
if(depth[x] < depth[y]) std::swap(x ,y);
x = fa[x];
}
return x;
}
int tr[MX] ,idcnt;
void make_virtual_tree(){
addvertex(1);
std::sort(S.begin() ,S.end() ,cmp);
int lim = S.size();
for(int i = 1 ; i < lim ; ++i){
addvertex(LCA(std::abs(S[i]) ,std::abs(S[i - 1])));
}
memset(inside ,0 ,sizeof inside);
std::sort(S.begin() ,S.end() ,cmp);
std::stack<int> stk;
for(size_t i = 0 ; i < S.size() ; ++i){
int x = std::abs(S[i]);
//debug("visit %d\n" ,x);
if(!inside[x]){
inside[x] = true;
stk.push(x);
tr[x] = ++idcnt;
}
else{
stk.pop();
if(!stk.empty()){
addedge(tr[stk.top()] ,tr[x] ,tree2);
addedge(tr[x] ,tr[stk.top()] ,tree2);
}
inside[x] = false;
}
}
memset(inside ,0 ,sizeof inside);
for(auto &i : e){
i.first = tr[i.first];
i.second = tr[i.second];
// addedge(i.first ,i.second ,tree2);
}
}
namespace DSU{
const int MAXN = 64;
int fa[MAXN] ,size[MAXN];
void reset(){
for(int i = 0 ; i < MAXN ; ++i)
fa[i] = i ,size[i] = 1;
}
int find(int x){
return fa[x] == x ? x : find(fa[x]);
}
void link(int x ,int y){
x = find(x) ,y = find(y);
if(x == y) return;
if(size[x] < size[y]) std::swap(x ,y);
fa[y] = x ,size[x] += size[y];
}
int getsize(int x){
return size[x = find(x)];
}
}
int d[MX] ,sz[MX] ,sumd[MX];
void solve(){
int ans = 0;
for(int U = 1 ; U < (1 << e.size()) ; ++U){
DSU::reset();
for(int i = 1 ; i <= idcnt ; ++i){
d[i] = sumd[i] = 0;
}
int cat = 0 ,ecnt = 0;
for(size_t i = 0 ; i < e.size() ; ++i){
if((U >> i) & 1){
cat = e[i].first;
++ecnt;
d[e[i].first]++;
d[e[i].second]++;
DSU::link(e[i].first ,e[i].second);
}
}
std::stack<int> stk;
for(size_t i = 0 ; i < S.size() ; ++i){
int x = tr[std::abs(S[i])];
if(!inside[x]){
inside[x] = true;
stk.push(x);
// sumd[x] = d[x];
}
else{
sumd[x] += d[x];
stk.pop();
if(sumd[x] & 1){
++d[x];
++sumd[x];
++d[stk.top()];
++ecnt;
DSU::link(stk.top() ,x);
}
if(!stk.empty()){
int f = stk.top();
sumd[f] += sumd[x];
}
inside[x] = false;
}
}
int bad = 0;
for(int i = 1 ; i <= idcnt ; ++i)
if(d[i] > 2) bad = true;
if(!bad && ecnt == DSU::getsize(cat))
++ans;
}
printf("%d\n" ,ans);
}
int main(){
n = read() ,m = read();
for(int i = 1 ,u ,v ; i <= m ; ++i){
u = read() ,v = read();
addedge(u ,v);
addedge(v ,u);
}
dfs(1);
//debug("DFS over\n");
make_virtual_tree();
//debug("making vt over.\n");
solve();
return 0;
}