[CF1519E]Off by One

壹、题目描述 ¶

传送门 to CF

贰、题解 ¶

对于一些点 \((x_i,y_i)\),如果它们的斜率相同,即说明 \(({x_i\over k},{y_i\over k})\) 也是相同的(其中 \(k\overset{\Delta}=\gcd(x_i,y_i)\))我们可以考虑将一个点 \((x,y)\) 能够到达的两个点连起来,这条边的编号为这个点的编号,即我们将相同的斜率当成点,而将原来的点当成边,那么我们的问题就转化为了:

给一个无向图(或许有很多连通块),一个匹配为一对共用至少一个顶点的边,现在你要找出这个图的最大匹配数量。

贪心地想,对于一个有 \(m\) 条边的无向图,我们期望地匹配数量应该是 \(\lfloor{m\over 2}\rfloor\),并且一定能够达到。

简要证明一下:对于这张图,遍历出一个 \(\tt dfs\) 树,对于每一条边(不论是树边还是非树边),强制将其看作为从深度小的连向深度大的(即往下走),那么,对于一个度数为 \(d\) 的点,,我们分情况讨论:

  • \(d=2k\),那么它自己内部就可以搞定;
  • \(d=2k+1\),那么它自己内部搞定之后,让它剩下的边和它父亲的一条边再构成一个匹配让你父亲给你收尾?
    比较特别的是 gu (er),因为它没有父亲,所以它如果剩下一条边,就没人给他收尾了。

在代码实现时,定义 \(\tt match[i]\) 表示 \(i\) 点尚未被匹配的边的编号是多少,特别的,若为 \(0\) 即没有剩余的边。

对于 \(({x_i\over k},{y_i\over k})\) 的离散,有更好的做法,考虑 \(({a\over b},{c\over d})\) 的斜率实际上和 \((ad,cb)\) 是一样的,那么,对于 \(({a\over b}+1,{c\over d})\) 或者 \(({a\over b},{c\over d}+1)\) 也是一样,只需要同时乘上 \(bd\) 就行了。

时间复杂度 \(\mathcal O(n\log n)\),瓶颈在 \(\tt hash\).

叁、参考代码 ¶

#include<cstdio>
#include<vector>
#include<map>
#include<cstring>
#include<algorithm>
using namespace std;

#define Endl putchar('\n')
#define mp(a, b) make_pair(a, b)
#define rep(i, l, r) for(int i=(l), i##_end_=(r); i<=i##_end_; ++i)
#define fep(i, l, r) for(int i=(l), i##_end_=(r); i>=i##_end_; --i)
#define fi first
#define se second
typedef long long ll;
typedef pair<int, int> pii;
typedef pair<long long, long long> pll;
template<class T>inline T fab(T x){ return x<0? -x: x; }
template<class T>T gcd(T x, T y){ return y? gcd(y, x%y): x; }
template<class T>inline T readin(T x){
	x=0; int f=0; char c;
	while((c=getchar())<'0' || '9'<c) if(c=='-') f=1;
	for(x=(c^48); '0'<=(c=getchar()) && c<='9'; x=(x<<1)+(x<<3)+(c^48));
	return f? -x: x;
}

const int maxn=200000<<1;

struct edge{
    int to, nxt, id;
    edge(){}
    edge(int T, int N, int I): to(T), nxt(N), id(I){}
}e[maxn<<1|1];
int tail[maxn+5], ecnt;
inline void add_edge(int u, int v, int id){
    // printf("add_edge :> u == %d, v == %d, id == %d\n", u, v, id);
    e[ecnt]=edge(v, tail[u], id); tail[u]=ecnt++;
    e[ecnt]=edge(u, tail[v], id); tail[v]=ecnt++;
}

int n;
map<pll, int>htable;
int ncnt;
inline int getid(ll x, ll y){
    ll d=gcd(x, y);
    pll cur=mp(x/d, y/d);
    if(!htable[cur]) htable[cur]=++ncnt;
    return htable[cur];
}

inline void input(){
    memset(tail, -1, sizeof tail);
    n=readin(1);
    int a, b, c, d, u, v;
    rep(i, 1, n){
        a=readin(1), b=readin(1), c=readin(1), d=readin(1);
        u=getid(1ll*(a+b)*d, 1ll*c*b);
        v=getid(1ll*a*d, 1ll*(c+d)*b);
        add_edge(u, v, i);
    }
}

vector<pii>ans;

int vis[maxn+5], match[maxn+5];
void dfs(int u){
    vis[u]=1;
    for(int i=tail[u], v, nde; ~i; i=e[i].nxt){
        v=e[i].to, nde=e[i].id;
        e[i].to=e[i^1].to=0;
        if(v){
            if(!vis[v]) dfs(v);
            if(match[v]){
                ans.push_back(mp(nde, match[v]));
                match[v]=0;
            }
            else if(match[u]){
                ans.push_back(mp(nde, match[u]));
                match[u]=0;
            }
            else match[u]=nde;
        }
    }
}

signed main(){
    input();
    rep(i, 1, ncnt) if(!vis[i])
        dfs(i);
    printf("%d\n", (int)ans.size());
    for(int i=0, siz=ans.size(); i<siz; ++i)
        printf("%d %d\n", ans[i].fi, ans[i].se);
	return 0;
}

肆、用到 の Trick

这是 “最大边匹配”,可以使用这种贪心思路来构造。

另外,有个思想:有些子节点欠下的债,可以留到父节点来还。但并不是所有的债都可以子债父还罢?

posted @ 2021-05-03 21:31  Arextre  阅读(72)  评论(0编辑  收藏  举报