今天主要在学习二分图入门
3道题
1:CF19E Fairy
传送门
题面一大串经过人性 翻译后便是:
给定 n 个点,m 条边的无向图,可以从图中删除一条边,问删除哪条边可以使图变成一个二分图。
因为要从普通的图到二分图,所以第一步考虑二分图与普通图区别即其特性:
二分图中所有环的长度均是偶数。
所以我们现在目标是:找出图中所有奇环的交,也就是说,所有奇环的交就是答案。因为虽然答案不止一条边,但要求是只删这一条便能满足,所以必须是所有奇环的交。如果没有奇环就好办了,那每条边都可以删。
实现方式:
用dfs找环,用tag[u]数组记录u点在多少奇环上,每找到u点到v点之间有一个奇环,便将u点到v点之间更新tag值,很显然用树上差分便能实现,统计奇环个数,最后再dfs查找tag值与统计值zt相同的点,更新答案。
上代码
#include<iostream>
#include<cstdio>
#include<cstdlib>
#include<cmath>
#include<cstring>
#include<string>
#include<algorithm>
#include<vector>
#include<map>
#include<set>
#include<list>
#include<queue>
#include<stack>
#include<bitset>
#include<deque>
using namespace std;
#define ll long long
#define inf 0x3f3f3f3f
#define ri register int
#define il inline
#define fi first
#define se second
#define mp make_pair
#define pi pair<int,int>
#define mem0(x) memset((x),0,sizeof (x))
#define mem1(x) memset((x),0x3f,sizeof (x))
#define gc getchar
#define pb push_back
template<class T>void in(T &x)
{
x = 0; bool f = 0; char c = gc();
while (c < '0' || c > '9') {if (c == '-') f = 1; c = gc();}
while ('0' <= c && c <= '9') {x = (x << 3) + (x << 1) + (c ^ 48); c = gc();}
if (f) x = -x;
}
#undef gc
void out(int x) {
if (x < 0) putchar('-'), x = -x;
if (x > 9) out(x / 10);
putchar(x % 10 + '0');
}
#define N 10010
#define M N<<2
int n, m;
int v[M], u[M], nx[M], k[M];
int cnt = 1, head[N];
il void add(int uu, int vv) {
u[++cnt] = uu, v[cnt] = vv, nx[cnt] = head[uu];
head[uu] = cnt;
k[cnt] = (cnt >> 1);
}
int dep[N];
bool vis[N];
int tag[N];
int zt;
vector<int>ans;
int lst;
void dfs(int x, int f, int d) {
//printf("V %d\n",x);
dep[x] = d;
vis[x] = 1;
for (ri i = head[x]; i; i = nx[i]) {
if (i == f) continue;
if (!vis[v[i]]) {
dfs(v[i], i ^ 1, d + 1);
}
else {
if (dep[v[i]] < dep[x]) continue;
if ((dep[v[i]] - dep[x] + 1) & 1) {
tag[x]--; // in fact +1 -2
tag[v[i]]++;
zt++;
lst=k[i];
}
else {
tag[x]++;
tag[v[i]]--;
}
}
}
}
int dfs2(int x, int f) {
vis[x] = 1;
int res = tag[x];
for (ri i = head[x]; i; i = nx[i]) {
if (vis[v[i]]) continue;
res += dfs2(v[i], i);
}
if (res == zt) ans.pb(k[f]);
//printf("T %d %d %d\n",x,res,tag[x]);
return res;
}
signed main() {
in(n), in(m);
for (ri i = 1, a, b; i <= m; ++i) {
in(a), in(b);
add(a, b);
add(b, a);
}
for (ri i = 1; i <= n; ++i) {
if (!vis[i]) dfs(i, 0, 0);
}
if (zt == 0) {
//cout << "A";
printf("%d\n", m);
for (ri i = 1; i <= m; ++i) printf("%d ", i);
return 0;
}
if(zt==1) ans.pb(lst);
//cout<<zt;
mem0(vis);
for (ri i = 1; i <= n; ++i) {
if (!vis[i]) dfs2(i, 0);
}
sort(ans.begin(), ans.end());
printf("%d\n", ans.size());
for (ri i = 0; i < ans.size(); ++i) {
printf("%d ", ans[i]);
}
return 0;
}
ps:根据树上差分的方法,应该在两个端点+1,LCA-2,而DFS树无横叉边,所以LCA就是深度较浅的点,将它+1-2即可;
2题3题都是二分图匹配模板题,用的匈牙利算法,代码如下。
Luogu p3386
#include<bits/stdc++.h>
using namespace std;
int n,m,e,first[1001],next[1001],to[1001],tot,match[1001],ans,x,y,whe[1001];
void add(int x,int y)
{
tot++;
next[tot]=first[x];
first[x]=tot;
to[tot]=y;
}
bool dfs(int pos)
{
for(int i=first[pos];i;i=next[i])
{
int v=to[i];
if(!whe[v])
{
whe[v]=1;
if(!match[v]||dfs(match[v]))
{
match[v]=pos;
return true;
}
}
}
return false;
}
int main()
{
scanf("%d%d%d",&n,&m,&e);
for(int i=1;i<=e;i++)
{
scanf("%d%d",&x,&y);
if(x>n||y>m)
continue;
add(x,y);
}
for(int i=1;i<=n;i++)
{
memset(whe,0,sizeof(whe));
if(dfs(i))
ans++;
}
cout<<ans;
}
#include<bits/stdc++.h>
using namespace std;
int tot,n,m,way[101][201],match[201],whe[201],ans,x,y;
bool dfs(int pos)
{
for(int i=n+1;i<=n+m;i++)
{
if(!whe[i]&&way[pos][i])
{
whe[i]=1;
if(!match[i]||dfs(match[i]))
{
match[i]=pos;
return true;
}
}
}
return false;
}
struct Ans
{
int l;
int r;
}q[101];
bool comp(Ans a,Ans b)
{
return a.l<b.l;
}
int main()
{
scanf("%d%d",&n,&m);
while(scanf("%d%d",&x,&y))
{
if(x==-1)
break;
way[x][y]=1;
}
for(int i=1;i<=n;i++)
{
memset(whe,0,sizeof(whe));
if(dfs(i))
ans++;
}
cout<<ans<<endl;
tot=0;
for(int i=n+1;i<=n+m;i++)
{
if(match[i])
{
tot++;
q[tot].l=match[i];
q[tot].r=i;
}
}
sort(q+1,q+1+tot,comp);
for(int i=1;i<=tot;i++)
{
cout<<q[i].l<<" "<<q[i].r<<endl;
}
return 0;
}