[CF1519E]Off by One

122 篇文章 0 订阅
89 篇文章 0 订阅

题目

传送门 to CF

思路

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;
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值