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;
}