图片说明
图片说明
图片说明
思路:
首先,判断极角相同可以用斜率表示。用一组最小正整数表示一个极角。
我们可以将没个极角标号,而对于一个点,我们可以看作它是把两个不同的极角连边。
那么问题就变成了:给定一张图,两条边可匹配当且仅当它们连接一个相同的点,求每条边唯一匹配的最大匹配数量。 最大边匹配

这里有个结论:对于一个无向图,使用遍历同时给图中节点打上深度标记后,除去走过的边,剩下的边连接的两个顶点它们的深度一定不相同。显然,如果存在某条边两端深度相同,那么在遍历到其中一段时,一定会走这条边遍历到另一端。因而,这样没有被遍历的边称为返祖边。

处理这一类问题可以建一颗dfs序树,从叶子节点开始考虑,先不考虑这个点连向父亲的边,如果连接其的边为偶数,那么就可以两两匹配掉,连向父亲的边还给父亲;如果不是,那么利用连向父亲的边来平衡这个奇偶性。可以保证匹配数最大,因为每个点(除了根节点)都有一条连向父亲的边平衡奇偶性,且必然没被用过,所以对于每个点(除了根节点),连接它的边都能够两两匹配不剩(除连接的父边)。

MyCode:

#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
typedef unsigned long long ull;
const int maxn=4e5+7,mod=1e9+7;

vector<pair<int,int> >g[maxn],ans;
int n,ID,vis[maxn],dep[maxn];
map<pair<ll,ll>,int>mp;
int id(ll p,ll q) {
    ll g=__gcd(p,q);
    p/=g; q/=g;
    if(mp.count({p,q})) return mp[{p,q}];
    return mp[{p,q}]=++ID;
}
bool dfs(int v) {
    vis[v]=1;
    int f(0),whoup(0),u,who;
    auto hit = [&](int x) {
        if (!f) f=x;
        else {
            ans.emplace_back(make_pair(x,f));
            f=0;
        }
    };
    for(auto i:g[v]) {
        u=i.first,who=i.second;
        if(!vis[u]) {
            dep[u]=dep[v]+1;
            if(dfs(u)) hit(who);
        }
        else {
            if(!whoup && dep[u] + 1 == dep[v]) whoup=who;
            else if(dep[u]<dep[v]) hit(who);
        }
    }
    if(f && whoup) {
        hit(whoup); return false;
    }
    return true;
}
int main() {
    ios::sync_with_stdio(false);
    cin.tie(nullptr);
    cin>>n;
    for(int i=1;i<=n;++i) {
        ll a,b,c,d;
        cin>>a>>b>>c>>d;//(a/b)横坐标 (c/d)纵坐标 
        int e1=id((c+d)*b,a*d);//向上 (c/d+1)的向量 
        int e2=id(c*b,(a+b)*d);//向右 (a/b+1)的向量 
        g[e1].emplace_back(make_pair(e2,i));
        g[e2].emplace_back(make_pair(e1,i));
    }
    for(int i=1;i<=ID;++i) if(!vis[i])
        dfs(i);
    cout<<ans.size()<<'\n';
    for(auto i:ans) cout<<i.first<<' '<<i.second<<'\n';
    return 0;
}