CF1519E Off by One

一、题目

点此看题

二、解法

看到这个题就感觉很像匹配,我们把这个题先转化成图论模型。

我是这样转化的:先把坐标上的点按两个方向都移动一下,算出 \(x,y\) 的比值,然后比值相同的点可以连边。但仔细想一想这不是一般图最大匹配吗?做得动就有鬼了

你需要知道:匹配点是不容易的,但是匹配边是容易的。我们不妨把上图点和边的意义互换一下,我们把 \(x,y\) 的比值当成点,每个坐标点就代表了连接该图两个点的边,那么每次我们可以匹配两个具有共同点的边。

可以构造给出最优解,答案的上界是每个联通块的边数除 \(2\) 下取整求和,考虑构造到这个上界。构造可以考虑 \(\tt dfs\),对于当前点 \(u\) 如果已有的边数是奇数的话我们把父边划分给当前点,否则把父边留给父亲,\(\tt dfs\) 数是不存在交叉边的,返祖边可以全部留给祖先。

时间复杂度 \(O(n)\)

#include <cstdio>
#include <vector>
#include <iostream>
#include <map>
using namespace std;
const int M = 400005;
#define make make_pair
#define ll long long
#define pll pair<ll,ll>
int read()
{
	int x=0,f=1;char c;
	while((c=getchar())<'0' || c>'9') {if(c=='-') f=-1;}
	while(c>='0' && c<='9') {x=(x<<3)+(x<<1)+(c^48);c=getchar();}
	return x*f;
}
int n,m,res,d[M];vector<int> g[M],h[M],ans[M];
map<pll,int> mp;
ll gcd(ll a,ll b)
{
	return !b?a:gcd(b,a%b);
}
pll get(ll a,ll b,ll c,ll d)
{
	ll g=gcd(a*d,c*b);
	return make(a*d/g,c*b/g);
}
void dfs(int u,int fa)
{
	d[u]=d[fa]+1;int tmp=-1;
	for(int i=0;i<g[u].size();i++)
	{
		int v=g[u][i];
		if(d[v])
		{
			if(d[v]>d[u]) continue;
			if(v==fa && tmp==-1) tmp=i;
			else ans[v].push_back(h[u][i]);
		}
		else dfs(v,u);
	}
	if(fa)
	{
		if(ans[u].size()&1) ans[u].push_back(h[u][tmp]);
		else ans[fa].push_back(h[u][tmp]);
	}
}
signed main()
{
	n=read();
	for(int i=1;i<=n;i++)
	{
		int a=read(),b=read(),c=read(),d=read();
		int &x=mp[get(a+b,b,c,d)],&y=mp[get(a,b,c+d,d)];
		if(!x) x=++m;if(!y) y=++m;
		g[x].push_back(y);g[y].push_back(x);
		h[x].push_back(i);h[y].push_back(i);
	}
	for(int i=1;i<=m;i++)
		if(!d[i]) dfs(i,0);
	for(int i=1;i<=m;i++)
		res+=ans[i].size()/2;
	printf("%d\n",res);
	for(int i=1;i<=m;i++)
		for(int j=1;j<ans[i].size();j+=2)
			printf("%d %d\n",ans[i][j-1],ans[i][j]);
}
posted @ 2021-05-03 12:27  C202044zxy  阅读(66)  评论(0编辑  收藏  举报