【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;
}