一、题目
二、解法
看到这个题就感觉很像匹配,我们把这个题先转化成图论模型。
我是这样转化的:先把坐标上的点按两个方向都移动一下,算出 \(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]);
}