• 【CF1519E】Off by One


    题目

    题目链接:http://codeforces.com/problemset/problem/1519/E
    平面上有 (n) 个点,且坐标不一定是整数,第 (i) 个点会给出 (a_i,b_i,c_i,d_i),表示坐标为 ((frac{a_i}{b_i},frac{c_i}{d_i}))
    每次操作你需要选择两个点 (i,j)

    • ((x_i,y_i)) 移动到 ((x_i+1,y_i))((x_i,y_i+1))
    • ((x_j,y_j)) 移动到 ((x_j+1,y_j))((x_j,y_j+1))
    • 要求移动后的两个点所在直线经过 ((0,0))
    • 将这两个点删除,并得到 (1) 的贡献。

    求贡献最大值并给出方案。
    (nleq 2 imes 10^5;1leq a_i,b_i,c_i,d_ileq 10^9)

    思路

    一开始的想法是把移动后的每一个点与 ((0,0)) 的连线所在直线看作一个点,然后将原图中的点和直线的点互相连边,然后跑费用流。但是显然这样复杂度是爆炸的。
    观察到每一个点恰好是会连接到两个直线所对应的点的,所以我们没必要把原图中的点再看作新图中的点,直接看成两条直线所对应的点之间的边即可。
    这样问题就转化为给定一张图,每次选择两条边且要求两条边存在一端点相同,再删去这两条边,要求最大化删的边的数量。
    之前有听说过这类问题,然后就不难了。
    随便跑出一棵 dfs 树,然后考虑当前处理到的点 (x),假设它有 (k) 个连边没有删去的儿子,那么将这些儿子两两匹配,最后只会剩下一个儿子或者不剩。如果剩余一个儿子,那么就把这个儿子与 (x) 的父亲匹配,并且把 (x) 父亲到 (x) 的边标记为删去。注意对于返祖边,依然需要看作祖先有这个儿子。显然这样贪心是最优的,因为对于每一个连通块最多剩余一条边没有匹配。
    时间复杂度 (O(nlog n))。为了避免精度问题,直接把移动后的点的分子分母算出来,约分,然后用 map 记录二元组的编号即可。

    代码

    #include <bits/stdc++.h>
    #define mp make_pair
    #define ST first
    #define ND second
    using namespace std;
    typedef long long ll;
    
    const int N=800010;
    int n,m,tot,head[N],dep[N];
    map<pair<ll,ll>,int> id;
    queue<pair<int,int> > q1;
    
    ll gcd(ll x,ll y)
    {
    	if (!y) return x;
    	return gcd(y,x%y);
    }
    
    struct edge
    {
    	int next,to,id;
    	bool used;
    }e[N];
    
    void add(int from,int to,int k)
    {
    	e[++tot]=(edge){head[from],to,k,0};
    	head[from]=tot;
    }
    
    void dfs(int x,int num)
    {
    	queue<int> q2;
    	for (int i=head[x];~i;i=e[i].next)
    	{
    		int v=e[i].to;
    		if (!dep[v])
    		{
    			dep[v]=dep[x]+1; dfs(v,i);
    			if (!e[i].used) q2.push(e[i].id);
    		}
    		else if (dep[v]>dep[x]) q2.push(e[i].id);
    	}
    	if ((q2.size()&1) && num)
    		q2.push(e[num].id),e[num].used=1;
    	while (q2.size()>=2)
    	{
    		int p=q2.front(); q2.pop();
    		q1.push(mp(p,q2.front())); q2.pop(); 
    	}
    }
    
    int main()
    {
    	memset(head,-1,sizeof(head));
    	scanf("%d",&n);
    	for (int i=1;i<=n;i++)
    	{
    		ll a,b,c,d,aa,bb,cc,dd;
    		scanf("%lld%lld%lld%lld",&a,&b,&c,&d);
    		aa=(a+b)*d; bb=b*c; cc=a*d; dd=(c+d)*b;
    		a=gcd(aa,bb); aa/=a; bb/=a;
    		a=gcd(cc,dd); cc/=a; dd/=a;
    		if (!id[mp(aa,bb)]) id[mp(aa,bb)]=++m;
    		if (!id[mp(cc,dd)]) id[mp(cc,dd)]=++m;
    		add(id[mp(aa,bb)],id[mp(cc,dd)],i);
    		add(id[mp(cc,dd)],id[mp(aa,bb)],i);
    	}
    	for (int i=1;i<=m;i++)
    		if (!dep[i]) dep[i]=1,dfs(i,0);
    	cout<<q1.size()<<endl;
    	for (;q1.size();q1.pop())
    		printf("%d %d
    ",q1.front().ST,q1.front().ND);
    	return 0;
    }
    
  • 相关阅读:
    《实例化需求》
    《编写有效用例》
    Alpha版总结会议
    团队开发冲刺第二阶段11
    团队开发冲刺第二阶段10
    团队开发冲刺第二阶段9
    团队开发冲刺第二阶段8
    团队开发冲刺第二阶段7
    团队开发冲刺第二阶段6
    团队开发冲刺第二阶段5
  • 原文地址:https://www.cnblogs.com/stoorz/p/14738716.html
Copyright © 2020-2023  润新知