题目链接:点击查看
题目大意:Dumbulidone和Euphemia玩一个挑卡片游戏.
Dumbulidone会给出N对信封,每个信封里有两张不同颜色的卡片,她会让Euphemia从中挑选任意个信封,但是一对信封中最多只能挑选一个,(信封是透明的,可以看到里面卡片颜色)。等Euphemia挑好后,Dumbulidone会尝试从Euphemia挑出的信封中再选出若干个(不可以不取),把其中的卡片取出,若存在一种方案使得取出的卡片中,每种颜色的卡片都有偶数张,那么Dumbulidone就赢了。Euphemia想知道在自己赢的前提下,她最多能选出多少信封。
输入格式:
第一行一个整数N。
接下来N行,每行4个整数,描述一对信封中卡片颜色,前两个是一个信封中的,后两个是一个信封中的。
输出格式:
一个整数,表示答案。
样例输入:
样例输入一 4 0 1 0 5 5 1 0 5 1 2 0 1 1 5 2 0 样例输入二 6 1 4 1 4 2 4 2 4 0 3 0 3 0 4 0 4 4 3 4 3 1 3 1 3
样例输出:
样例输出一 3 样例输出二 4
数据范围:
对于30%的数据满足N<=10
对于100%的数据满足N<=300,所有数<=10^7.
时间限制:
1S
空间限制:
256M
题目分析:感觉挺好的一道题目,断断续续想了三天差不多想明白了该怎么做,首先题目的意思比较抽象,我们需要转换一下,可以将颜色视为点,信封视为边,问题就转换为了,最多可以选择多少条边,使得组成的图中不存在环,因为前面的选择会影响到全局的结果,所以不能用dp转移状态,在图论中可以寻找增广路来使得答案最优,每次不断尝试寻找增广路更新答案就好了,因为数据比较小,我们可以直接枚举每条边,尝试加入到图中,如果可以直接加入的话,也就是加入某条边后图中仍然无环,那么这条边就可以直接加入,相反如果加入后会出现环,那么需要寻找增广路,这里寻找增广路的方法和匈牙利算法的很像,也是类似于二分图,这里二分图的一个部分代表着已经用过的边,另一部分代表着没有用过的边,如果加入某条边后会让图中出现环,那么显然这条边和环上的其他边是不能同时存在于一个部分的,我们可以建边,这里的建边是对于信封建边,也就是代表着这两个信封不能同时选择,对信封建好边后,尝试能否增广就好了,可以增广的条件是找到一条信封连成的路,使得第一个信封和最后一个信封都属于二分图中没有用过的边
最后就是因为颜色比较多,但我们实际上用不到其绝对大小,所以离散化一下得到其相对大小,方便后续操作
代码:
#include<iostream>
#include<cstdio>
#include<string>
#include<ctime>
#include<cmath>
#include<cstring>
#include<algorithm>
#include<stack>
#include<climits>
#include<queue>
#include<map>
#include<set>
#include<sstream>
using namespace std;
typedef long long LL;
typedef unsigned long long ull;
const int inf=0x3f3f3f3f;
const int N=610;
vector<int>v;
int f[N],n;
struct Edge
{
int u,v,vis;
Edge()
{
vis=0;
}
}Edge[N];
void discreate()//离散化
{
sort(v.begin(),v.end());
v.erase(unique(v.begin(),v.end()),v.end());
for(int i=0;i<2*n;i++)
{
Edge[i].u=lower_bound(v.begin(),v.end(),Edge[i].u)-v.begin();
Edge[i].v=lower_bound(v.begin(),v.end(),Edge[i].v)-v.begin();
}
}
int find(int x)
{
return f[x]==x?x:f[x]=find(f[x]);
}
bool merge(int x,int y)
{
int xx=find(x);
int yy=find(y);
if(xx!=yy)
{
f[xx]=yy;
return true;
}
return false;
}
struct Node
{
int to,id;
Node(int to,int id):to(to),id(id){}
};
vector<Node>point[N<<1];//颜色连边
vector<int>edge[N];//信封连边
void init_point()
{
for(int i=0;i<v.size();i++)
f[i]=i;
for(int i=0;i<v.size();i++)
point[i].clear();
}
void build_point(int now)//建立点之间的联系
{
init_point();
for(int i=0;i<now;i++)
if(Edge[i].vis)
{
int u=Edge[i].u;
int v=Edge[i].v;
point[u].push_back(Node(v,i));
point[v].push_back(Node(u,i));
merge(u,v);
}
}
bool ok[N];//标记哪条边可以作为增广路的终边
int vis[N];
bool dfs(int u,int fa,int id)//找环
{
if(u==fa)//有环
return true;
vis[u]=id;
for(int i=0;i<point[u].size();i++)
{
int v=point[u][i].to;
if(vis[v]==id)
continue;
if(dfs(v,fa,id))//将环上的所有信封与id信封连边
{
edge[id].push_back(point[u][i].id);
edge[point[u][i].id].push_back(id);
return true;
}
}
return false;
}
void init_edge(int now)
{
for(int i=0;i<=(now^1);i++)
edge[i].clear();
memset(ok,false,sizeof(ok));
memset(vis,-1,sizeof(vis));
}
void build_edge(int now)//建立信封之间的联系
{
init_edge(now);
for(int i=0;i<=now;i++)
edge[i].push_back(i^1);
for(int i=0;i<=now;i++)
{
if(!Edge[i].vis)//如果这条边没被用过
{
if(dfs(Edge[i].u,Edge[i].v,i))//加入后有环
ok[i]=false;
else//无环
ok[i]=true;
}
}
}
bool dfs(int u)
{
vis[u]=true;
for(int i=0;i<edge[u].size();i++)
{
int v=edge[u][i];
if(vis[v])
continue;
if((Edge[u].vis^Edge[v].vis)==1)
{
if(ok[v]||dfs(v))
{
Edge[v].vis^=1;
return true;
}
}
}
return false;
}
int main()
{
#ifndef ONLINE_JUDGE
// freopen("input.txt","r",stdin);
// freopen("output.txt","w",stdout);
#endif
// ios::sync_with_stdio(false);
scanf("%d",&n);
for(int i=0;i<2*n;i++)
{
scanf("%d%d",&Edge[i].u,&Edge[i].v);
v.push_back(Edge[i].u);
v.push_back(Edge[i].v);
}
discreate();
int ans=0;
for(int i=0;i<2*n;i++)//遍历每个信封
{
build_point(i);//对于颜色建边
if(merge(Edge[i].u,Edge[i].v))//加入后无环,则直接加入
{
Edge[i].vis=true;
ans++;
continue;
}
build_edge(i);//对于信封建边
memset(vis,false,sizeof(vis));
if(dfs(i))//可以找到增广路,则说明可以加入这条边
{
Edge[i].vis=true;
ans++;
}
}
printf("%d\n",ans);
return 0;
}