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) ightarrow (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)),那么连一条无向边
现在问题转化为对于无向边定向,使得最少的点入度为奇数
对于任意一个连通块,若其包含奇数条边,那么至少有一个点入度为奇数
否则一定可以完成匹配
具体的,随便选择一个点作为根,然后只对于祖先向子孙的边考虑关系
从子孙向上考虑所有的边,优先让子孙的入度为偶数
那么只有包含奇数条边时,根的入度为奇数,其他节点入度永远是偶数
即达到最优解
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
",ans);
rep(i,1,m) {
rep(j,0,G[i].size()/2-1)
printf("%d %d
",G[i][j*2],G[i][j*2+1]);
}
}