XJOJ - 选信封(离散化+增广路)

题目链接:点击查看

题目大意: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;
}
发布了658 篇原创文章 · 获赞 22 · 访问量 3万+

猜你喜欢

转载自blog.csdn.net/qq_45458915/article/details/104518528