【CF1519E】Off by One

题目

题目链接:http://codeforces.com/problemset/problem/1519/E
平面上有 \(n\) 个点,且坐标不一定是整数,第 \(i\) 个点会给出 \(a_i,b_i,c_i,d_i\),表示坐标为 \((\frac{a_i}{b_i},\frac{c_i}{d_i})\)
每次操作你需要选择两个点 \(i,j\)

  • \((x_i,y_i)\) 移动到 \((x_i+1,y_i)\)\((x_i,y_i+1)\)
  • \((x_j,y_j)\) 移动到 \((x_j+1,y_j)\)\((x_j,y_j+1)\)
  • 要求移动后的两个点所在直线经过 \((0,0)\)
  • 将这两个点删除,并得到 \(1\) 的贡献。

求贡献最大值并给出方案。
\(n\leq 2\times 10^5;1\leq a_i,b_i,c_i,d_i\leq 10^9\)

思路

一开始的想法是把移动后的每一个点与 \((0,0)\) 的连线所在直线看作一个点,然后将原图中的点和直线的点互相连边,然后跑费用流。但是显然这样复杂度是爆炸的。
观察到每一个点恰好是会连接到两个直线所对应的点的,所以我们没必要把原图中的点再看作新图中的点,直接看成两条直线所对应的点之间的边即可。
这样问题就转化为给定一张图,每次选择两条边且要求两条边存在一端点相同,再删去这两条边,要求最大化删的边的数量。
之前有听说过这类问题,然后就不难了。
随便跑出一棵 dfs 树,然后考虑当前处理到的点 \(x\),假设它有 \(k\) 个连边没有删去的儿子,那么将这些儿子两两匹配,最后只会剩下一个儿子或者不剩。如果剩余一个儿子,那么就把这个儿子与 \(x\) 的父亲匹配,并且把 \(x\) 父亲到 \(x\) 的边标记为删去。注意对于返祖边,依然需要看作祖先有这个儿子。显然这样贪心是最优的,因为对于每一个连通块最多剩余一条边没有匹配。
时间复杂度 \(O(n\log n)\)。为了避免精度问题,直接把移动后的点的分子分母算出来,约分,然后用 map 记录二元组的编号即可。

代码

#include <bits/stdc++.h>
#define mp make_pair
#define ST first
#define ND second
using namespace std;
typedef long long ll;

const int N=800010;
int n,m,tot,head[N],dep[N];
map<pair<ll,ll>,int> id;
queue<pair<int,int> > q1;

ll gcd(ll x,ll y)
{
	if (!y) return x;
	return gcd(y,x%y);
}

struct edge
{
	int next,to,id;
	bool used;
}e[N];

void add(int from,int to,int k)
{
	e[++tot]=(edge){head[from],to,k,0};
	head[from]=tot;
}

void dfs(int x,int num)
{
	queue<int> q2;
	for (int i=head[x];~i;i=e[i].next)
	{
		int v=e[i].to;
		if (!dep[v])
		{
			dep[v]=dep[x]+1; dfs(v,i);
			if (!e[i].used) q2.push(e[i].id);
		}
		else if (dep[v]>dep[x]) q2.push(e[i].id);
	}
	if ((q2.size()&1) && num)
		q2.push(e[num].id),e[num].used=1;
	while (q2.size()>=2)
	{
		int p=q2.front(); q2.pop();
		q1.push(mp(p,q2.front())); q2.pop(); 
	}
}

int main()
{
	memset(head,-1,sizeof(head));
	scanf("%d",&n);
	for (int i=1;i<=n;i++)
	{
		ll a,b,c,d,aa,bb,cc,dd;
		scanf("%lld%lld%lld%lld",&a,&b,&c,&d);
		aa=(a+b)*d; bb=b*c; cc=a*d; dd=(c+d)*b;
		a=gcd(aa,bb); aa/=a; bb/=a;
		a=gcd(cc,dd); cc/=a; dd/=a;
		if (!id[mp(aa,bb)]) id[mp(aa,bb)]=++m;
		if (!id[mp(cc,dd)]) id[mp(cc,dd)]=++m;
		add(id[mp(aa,bb)],id[mp(cc,dd)],i);
		add(id[mp(cc,dd)],id[mp(aa,bb)],i);
	}
	for (int i=1;i<=m;i++)
		if (!dep[i]) dep[i]=1,dfs(i,0);
	cout<<q1.size()<<endl;
	for (;q1.size();q1.pop())
		printf("%d %d\n",q1.front().ST,q1.front().ND);
	return 0;
}
posted @ 2021-05-07 12:50  stoorz  阅读(104)  评论(0编辑  收藏  举报