[CF1519E]Off by One

Off by One

题解

由于它每次可以共同删掉两个与0连线斜率一样的点,我们可以考虑将原图转化成一个图论模型。
由于一个点通过移动会对应两个斜率,我们可以将这两个斜率看作两个点,而这个询问可以当做连接两个点的一条边。
那么,每个操作就相当于删去连在同一个点上的两条边,求最多可以删掉多少条边。
首先对于一个连通块,它的答案肯定是独立的,我们可以单独对一个连通块看,假设它的边数是 m m m,那么它的答案是不会超过 ⌊ m 2 ⌋ \left\lfloor\frac{m}{2}\right\rfloor 2m的。

我们可以得到一种构造方案,使得它的答案刚好为 ⌊ m 2 ⌋ \left\lfloor\frac{m}{2}\right\rfloor 2m
首先我们可以通过对原图进行 d f s dfs dfs遍历,建出一棵 d f s dfs dfs树。
d f s dfs dfs树上有两种边,一种是树边与一种是非树边。
对于非树边,我们就强行将其给它的祖先。
对于树边,如果已经给它的儿子的边还有一条未匹配,那么就将其给它儿子,否则就给它父亲。
这样可以保证每个非根节点都可以做到完全匹配,因为它有它与它父亲的连边使得它未完全匹配的边可以被中和掉,所以最多只会这根节点处剩一条边。

时间复杂度 O ( n l o g   n ) O\left(nlog\,n\right) O(nlogn) l o g   n log\,n logn是将斜率转化时用 m a p map map的花费,hash如果使用基排的话还可以优化。

源码

#include<bits/stdc++.h>
using namespace std;
#define MAXN 400005
#define lowbit(x) (x&-x)
#define reg register
#define fir first
#define sec second
typedef long long LL;
typedef unsigned long long uLL;
const int INF=0x3f3f3f3f;
const LL jzm=2333;
const double Pi=acos(-1.0);
typedef pair<LL,LL> pii;
const double PI=acos(-1.0);
template<typename _T>
_T Fabs(_T x){return x<0?-x:x;}
template<typename _T>
void read(_T &x){
	_T f=1;x=0;char s=getchar();
	while(s>'9'||s<'0'){if(s=='-')f=-1;s=getchar();}
	while('0'<=s&&s<='9'){x=(x<<3)+(x<<1)+(s^48);s=getchar();}
	x*=f;
}
template<typename _T>
void print(_T x){if(x<0){x=(~x)+1;putchar('-');}if(x>9)print(x/10);putchar(x%10+'0');}
int n,tot,head[MAXN],dep[MAXN],ans,cnt;
bool vis[MAXN],used[MAXN];
vector<int>match[MAXN];
struct ming{LL a,b,c,d;}s[MAXN];
struct edge{int to,nxt,paid;}e[MAXN<<1];
map<pii,int> mp;
void addEdge(int u,int v,int w){e[++tot]=(edge){v,head[u],w};head[u]=tot;}
LL gcd(LL a,LL b){return !b?a:gcd(b,a%b);}
void dosaka(int u,int fa,int fi){
	vis[u]=1;dep[u]=dep[fa]+1;
	for(int i=head[u];i;i=e[i].nxt){
		int v=e[i].to;if(v==fa)continue;
		if(!vis[v])dosaka(v,u,e[i].paid);
		if(dep[v]>dep[u]&&!used[e[i].paid])
			used[e[i].paid]=1,match[u].push_back(e[i].paid);
	}
	if(fi&&(match[u].size()&1))used[fi]=1,match[u].push_back(fi);
	//printf("dosaka %d:%d\n",u,match[u].size());
}
signed main(){
	read(n);
	for(int i=1;i<=n;i++){
		int u,v;pii tmp;LL d,x,y;read(s[i].a),read(s[i].b),read(s[i].c),read(s[i].d);
		x=(s[i].a+s[i].b)*s[i].d;y=s[i].c*s[i].b;d=gcd(x,y);x/=d;y/=d;
		tmp=make_pair(x,y);if(!mp[tmp])mp[tmp]=++cnt;u=mp[tmp];
		x=s[i].a*s[i].d;y=(s[i].c+s[i].d)*s[i].b;d=gcd(x,y);x/=d;y/=d;
		tmp=make_pair(x,y);if(!mp[tmp])mp[tmp]=++cnt;v=mp[tmp];
		addEdge(u,v,i);addEdge(v,u,i);
	}
	for(int i=1;i<=tot;i++){if(!vis[i])dosaka(i,0,0);ans+=match[i].size()/2;}printf("%d\n",ans);
	for(int i=1;i<=tot;i++)
		for(int j=1;j<(int)match[i].size();j+=2)
			printf("%d %d\n",match[i][j],match[i][j-1]);
	return 0;
}

谢谢!!!

  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值