题目
思路
I n c r e d i b l e ! \rm Incredible! Incredible!
首先做一个转换:将一个点 ( x , y ) (x,y) (x,y) 视作连接 y + 1 x , y x + 1 \frac{y+1}{x},\;{y\over x+1} xy+1,x+1y 的边,问题转化为:任意两条有公共端点的边可以匹配,求最大匹配。
据说这是经典问题。结论是,对于一个有
m
m
m 条边的连通块,我们可以匹配
⌊
m
2
⌋
\lfloor{m\over 2}\rfloor
⌊2m⌋ 组。即,最多只有一条边剩余。
其实这是很简单的。考虑在 d f s \rm dfs dfs 树上操作。那么一个点,除去 b a c k w a r d e d g e \rm backward\;edge backwardedge(返回祖先节点的边)以外,剩下的边要么与 d f s ( u ) \mathrm{dfs}(u) dfs(u) 剩余的边匹配,要么直接与其他 x x x 的出边匹配。
更形式化地说:对于节点
x
x
x,递归
u
u
u,根据归纳法,
u
u
u 的 子树
内可能会剩余一条未匹配的边,其中一个端点是
u
u
u,那就将
⟨
x
,
u
⟩
\langle x,u\rangle
⟨x,u⟩ 与这条边匹配。否则不管。
经过上面的操作,所有 x x x 的子节点都 f u l l m a t c h \rm full\;match fullmatch(完全匹配)了。那么 x x x 本身呢?由于剩余的边都有 x x x 作为公共端点,两两匹配即可。显然最多剩一条边,并且其中一个端点是 x x x,归纳法成立。
复杂度 O ( n log n ) \mathcal O(n\log n) O(nlogn),瓶颈是对斜率的哈希。
代码
#include <cstdio>
#include <iostream>
#include <cstring>
#include <vector>
#include <algorithm>
#include <map>
using namespace std;
typedef long long int_;
# define rep(i,a,b) for(int i=(a); i<=(b); ++i)
inline int readint(){
int a = 0; char c = getchar(), f = 1;
for(; c<'0'||c>'9'; c=getchar())
if(c == '-') f = -f;
for(; '0'<=c&&c<='9'; c=getchar())
a = (a<<3)+(a<<1)+(c^48);
return a*f;
}
inline void writeint(int x){
if(x > 9) writeint(x/10);
putchar((x-x/10*10)^48);
}
inline int_ _gcd_(int_ a,int_ b){
while(b) a %= b, swap(a,b);
return a;
}
const int MaxN = 200005;
struct Edge{
int to, nxt, id;
Edge(){}
Edge(int T,int N,int I){
to = T, nxt = N, id = I;
}
};
Edge e[MaxN<<1];
int head[MaxN<<1], cntEdge;
void addEdge(int a,int b,int c){
e[cntEdge] = Edge(b,head[a],c);
head[a] = cntEdge ++;
}
int heri[MaxN<<1]; // -1:unvis -2:inSta
bool inSta[MaxN<<1];
vector< pair<int,int> > ans;
void match(int a,int b){
ans.push_back(make_pair(a,b));
}
void dfs(int x){
heri[x] = -2; int cur = 0;
for(int i=head[x]; ~i; i=e[i].nxt){
if(heri[e[i].to] == -2)
continue; // backward edge
if(heri[e[i].to] == -1)
dfs(e[i].to);
if(heri[e[i].to] > 0){
match(e[i].id,heri[e[i].to]);
heri[e[i].to] = 0;
}
else if(cur)
match(cur,e[i].id), cur = 0;
else cur = e[i].id;
}
heri[x] = cur;
}
map<pair<int_,int_>,int> mp;
int main(){
int n = readint();
memset(head,-1,sizeof(head));
for(int i=1,t=0; i<=n; ++i){
int a = readint(), b = readint();
int c = readint(), d = readint();
/* (c+d)/d div (a/b) */
int_ son = int_(c+d)*b;
int_ mom = int_(d)*a;
int_ gd = _gcd_(son,mom);
son /= gd, mom /= gd;
int &up = mp[make_pair(son,mom)];
if(!up) up = ++ t; // new id
/* (c/d) div (a+b)/b */
son = int_(c)*b, mom = int_(a+b)*d;
gd = _gcd_(son,mom);
son /= gd, mom /= gd;
int &ri = mp[make_pair(son,mom)];
if(!ri) ri = ++ t;
addEdge(up,ri,i), addEdge(ri,up,i);
}
memset(heri,-1,sizeof(heri));
rep(i,1,n<<1)
if(heri[i] == -1)
dfs(i);
writeint(ans.size()), putchar('\n');
for(unsigned i=0; i!=ans.size(); ++i){
writeint(ans[i].first), putchar(' ');
writeint(ans[i].second), putchar('\n');
}
return 0;
}