CF1519E - Off by One

题目大意

给定$n$个点$(x_i,y_i)=(\frac{a_i} {b_i},\frac{c_i} {d_i})$,求一个最大的匹配

满足匹配的点对$(x_i,y_i),(x_j,y_j)$每个点经过如下操作

$(x,y)\rightarrow (x+1,y) or (x,y+1)$

之后可能满足$\frac{y_i’} {x_i’}=\frac{y’_j} {x_j’}$

模型简化

按照$\frac{y’} {x’}$对于每个点经过两种可能变换的值分类,建立节点

我们需要决策每个$P_i$选的变换种类

显然每个斜率对应的个数为偶数时,都可以完成匹配

对于每一个$P_i$,设其两种变换之后变成的斜率对应节点为$(u,v)$,那么连一条无向边

现在问题转化为对于无向边定向,使得最少的点入度为奇数

对于任意一个连通块,若其包含奇数条边,那么至少有一个点入度为奇数

否则一定可以完成匹配

具体的,随便选择一个点作为根,然后只对于祖先向子孙的边考虑关系

从子孙向上考虑所有的边,优先让子孙的入度为偶数

那么只有包含奇数条边时,根的入度为奇数,其他节点入度永远是偶数

即达到最优解

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55

const int N=4e5+10,P=998244353;

int n,m;
struct Edge{
int to,nxt;
} e[N*2];
int head[N];

struct Node{
ll a,b;
bool operator < (const Node __) const { return a<__.a || (a==__.a && b<__.b); }
};
vector <int> G[N];

map <Node,int> M;
ll gcd(ll a,ll b){ return b==0?a:gcd(b,a%b); }
int Div(int a,int b,int c,int d){
ll x=1ll*a*d,y=1ll*b*c,g=gcd(x,y);
x/=g,y/=g;
Node t=(Node){x,y};
int &u=M[t];
if(!u) u=++m;
return u;
}

int vis[N],s[N],dfn;
void dfs(int u){
vis[u]=++dfn,s[u]=0;
for(int i=head[u];i;i=e[i].nxt) {
int v=e[i].to;
if(vis[v] && vis[v]<vis[u]) continue;
if(!vis[v]) dfs(v);
if(!s[v]) s[u]^=1,G[u].pb(i/2);
else s[v]^=1,G[v].pb(i/2);
}
}

int main(){
n=rd();
rep(i,1,n) {
int a=rd(),b=rd(),c=rd(),d=rd();
int u=Div(a+b,b,c,d),v=Div(a,b,c+d,d);
e[i*2]=(Edge){u,head[v]},head[v]=i*2;
e[i*2+1]=(Edge){v,head[u]},head[u]=i*2+1;
}
rep(i,1,m) if(!vis[i]) dfs(i);
int ans=0;
rep(i,1,m) ans+=G[i].size()/2;
printf("%d\n",ans);
rep(i,1,m) {
rep(j,0,G[i].size()/2-1)
printf("%d %d\n",G[i][j*2],G[i][j*2+1]);
}
}