顛倒原理 / reverse
時間限制:1000ms
空間限制:512MB
題目描述
\(GreenDuck\)想學習轉置原理,但由於它太難了,因此他轉而學習更為簡單的和圖的染色有密切聯絡的“顛倒原理”\((reverse principle)\)。
顛倒原理中有個重要的操作叫做“顛倒操作”。對於一個無向連通圖\(G\),其節點要麼是黑色要麼是白色。“顛倒操作”每次會選擇\(G\)的一條無向邊\((u, v)\),將\(u, v\)這兩個點的顏色顛倒。也就是說,如果\(u\)是白色的,那麼將\(u\)變為黑色的;如果\(u\)是黑色的,那麼將u變成白色的。對\(v\)也同樣處理。如果能透過有限次操作使得這張圖所有點都變為黑色,那麼這張圖便是“可顛倒”的。
現在\(GreenDuck\)有一個具有\(n\)個點,\(m\)條邊的無向連通圖,一開始所有點均為白色。他想知道
這個圖是否是“可顛倒”的。請你告訴他是否是“可顛倒”的,如果是,那麼輸出一種方案。
輸入格式
第一行兩個正整數\(n, m\),表示圖的點數和邊數。
接下來m行,每行兩個正整數\(x, y\),表示一條邊的兩個端點。
輸出格式
如果圖不是“可顛倒”的,輸出一行一個字串("\(No\)")(不要雙引號)。
否則,先輸出一行一個字串("\(Yes\)"),再在第二行輸出進行“顛倒操作”的邊的個數\(k\),最後\(k\)行,每行輸出一條邊的兩個端點。
注意,請嚴格按照格式輸出。同時,如果你輸出的邊不存在,或者出現不止一遍,那麼該測試點將不予評分。一條邊兩個端點的順序沒有要求。邊的輸出順序也沒有要求。
樣例1輸入
4 3
1 2
2 3
2 4
樣例1輸出
Yes
3
2 1
2 4
2 3
樣例1解釋
操作\((2, 1)\)會將點\(1, 2\)染成黑色,操作\((2, 4)\)會將點\(2\)染成白色,將點\(4\)染成黑色,操作\((2, 3)\)會將點\(2, 3\)染成黑色。
樣例2輸入
3 3
1 2
2 3
3 1
樣例2輸出
No
資料範圍
對於所有測試點,不存在重邊或自環
對於\(100%\)的資料,\(n, m <= 300000\)。
solution
30pts
我們可以發現每條邊最多染色一次。
所以\(O(2 ^ m)\)列舉每一條邊是否染色
50pts
當圖是一條邊或一條鏈時,結論比較顯然,如果點的個數是單數時,輸出\(No\),否則輸出\(Yes\)。
70pts
我們考慮\(m == n - 1\)這個特殊性質。
引理:n為奇數時一定不可能全變黑
證明
當我們進行顛倒操作時,可以分為以下幾種情況:
1.兩點均為白/黑 => 黑+2,白-2 or 白+2,黑+2。 奇偶不變
2.兩點一白一黑 => 相當於兩點交換顏色,奇偶不變
所以奇偶不變,也就不可能全變黑。
所以當圖是樹時,\(n\)是奇數時無解,考慮\(n\)是偶數時,用\(w_i\)記錄\(i\)點的顏色,跑一遍\(dfs\),在回溯時更新。
my code
考場上想出來的70分做法,其實已經很接近正解了。
#include <bits/stdc++.h>
using namespace std;
const int N = 3e5 + 5;
int n, m;
bool flag, v[N];
vector <int> g[N];
int d[N], t[N], cnt1, cnt2, s, c[N], tot, w[N], idx;
struct node{
int u, v;
}f[N], ans[N];
bool check(int sum)
{
for(int i = 1;i <= n;i++) d[i] = 0;
for(int i = 1;i <= sum;i++)
{
d[ans[i].u]++;
d[ans[i].v]++;
}
for(int i = 1;i <= n;i++)
{
if(d[i] % 2 == 0) return 0;
}
return 1;
}
void dfs(int u, int f)
{
v[u] = 1;
c[++tot] = u;
for(int i = 0;i < g[u].size();i++)
{
int j = g[u][i];
if(v[j]) continue;
dfs(j, u);
}
}
void dg(int dep, int sum)
{
if(dep > m)
{
if(flag == 0 && check(sum) == true)
{
flag = 1;
cout<<"Yes"<<"\n";
cout<<sum<<"\n";
for(int i = 1;i <= sum;i++)
{
cout<<ans[i].u<<" "<<ans[i].v<<"\n";
}
}
}
else
{
dg(dep + 1, sum);
ans[sum + 1] = f[dep];
dg(dep + 1, sum + 1);
}
}
void dfs1(int u, int f)
{
for(int i = 0;i < g[u].size();i++)
{
int j = g[u][i];
if(j == f) continue;
dfs1(j, u);
if(w[j] == 0)
{
w[j] ^= 1, w[u] ^= 1;
ans[++idx] = {j, u};
}
}
}
int main()
{
freopen("reverse.in", "r", stdin);
freopen("reverse.out", "w", stdout);
ios :: sync_with_stdio(0);
cin.tie(0);
cout.tie(0);
cin >> n >> m;
for(int i = 1;i <= m;i++)
{
int x, y;
cin >> x >> y;
f[i] = {x, y};
t[x]++;
t[y]++;
g[x].push_back(y);
g[y].push_back(x);
}
if(m <= 20)
{
dg(1, 0);
if(flag == 0) cout<<"No"<<"\n";
return 0;
}
for(int i = 1;i <= n;i++)
{
if(t[i] == 1)
{
s = i;
cnt1++;
}
if(t[i] == 2) cnt2++;
}
if(cnt1 + cnt2 == n && cnt1 == 2)
{
if(n % 2 == 0)
{
dfs(s, -1);
cout<<"Yes"<<"\n";
cout<<n / 2<<"\n";
for(int i = 1;i <= n;i+=2)
{
cout<<c[i]<<" "<<c[i + 1]<<"\n";
}
}
else cout<<"No"<<"\n";
return 0;
}
if(cnt2 == n)
{
if(n % 2 == 0)
{
dfs(1, -1);
cout<<"Yes"<<"\n";
cout<<n / 2<<"\n";
for(int i = 1;i <= n;i+=2)
{
cout<<c[i]<<" "<<c[i + 1]<<"\n";
}
}
else cout<<"No"<<"\n";
return 0;
}
if(m == n - 1)
{
if(n % 2 == 0)
{
dfs1(1, -1);
cout<<"Yes"<<"\n";
cout<<idx<<"\n";
for(int i = 1;i <= idx;i++)
{
cout<<ans[i].u<<" "<<ans[i].v<<"\n";
}
}
else cout<<"No"<<"\n";
return 0;
}
cout<<"No"<<"\n";
return 0;
}
100pts
和樹判定的方法一樣,不過求一顆生成樹即可。
std code
#include <bits/stdc++.h>
using namespace std;
const int N = 3e5 + 5;
int h[N], e[N << 1], ne[N << 1], idx;
int st[N], color[N];
void add(int a, int b)
{
e[idx] = b, ne[idx] = h[a], h[a] = idx++;
}
int n, m;
vector <pair<int, int>> res;
void dfs(int u, int f)
{
st[u] = 1;
for(int i = h[u]; ~i;i = ne[i])
{
int j = e[i];
if(st[j]) continue;
dfs(j, u);
}
if(!color[u] && f != -1)
{
res.push_back({u, f});
color[f] = !color[f];
}
}
int main()
{
memset(h, -1, sizeof h);
scanf("%d%d", &n, &m);
while(m--)
{
int a, b;
scanf("%d%d", &a, &b);
add(a, b);
add(b, a);
}
if(n % 2)
{
puts("No");
return 0;
}
dfs(1, -1);
printf("Yes\n%d\n", res.size());
for(int i = 0;i < res.size();i++)
printf("%d %d\n",res[i].first, res[i].second);
}