ICode9

精准搜索请尝试: 精确搜索
首页 > 其他分享> 文章详细

【突破训练】CF1519E Off by One

2021-05-02 09:36:01  阅读:196  来源: 互联网

标签:map Off 训练 fa CF1519E 斜率 一个点 移动 我们


我与这道题的故事还挺多的。
整场比赛中,只有前50多位做出了E,甚至有人前半个小时就做出了D剩下的时间依然却无法做出E。因此我觉得E是不属于前面题目难度的题目,也就是能让我突破的题目。
而我现在很想突破。前天和why大佬聊了几句,他和zxy是能“单人拿银”的人,组队后尚且需要运气和突破才能拿金。我只是一个水平一般的普通选手,所以一定要有突破。
因此我要开启突破训练,做以前没做过的难度和知识点。我要尝试突破。

题目解析

题意:在一个二维坐标系中,有n个点,坐标用分数表示,横坐标x=a/b,纵坐标y=c/d。每个点必须移动一次,移动是指由(x,y)移动到(x+1,y)或(x,y+1)。若存在两个点i和j移动后i,j,(0,0)在同一条直线上,那么我们的计数器+1并把i和j删掉。请问计数器最多可以是几,并给出每对选择的i,j分别是几。
1<=n<=2*105, 1<=a_i<=b_i<=c_i<=d_i<=109

这道题有很长的转化过程。

容易想到,两个点在同一条直线上即原点到两个点的斜率相同,对于每个斜率,我们要让它有唯一的表示方法。若点i坐标为(x=a/b,y=c/d),向右移动移动后则为(a/b+1,c/d),同分后为(d*(a+b),b*c),令g=gcd(d*(a+b),b*c),那么斜率表示成(d*(a+b)/g,b*c/g)。向上移动的同理,为(a*d/g,b*(c+d)/g)。

接下来似乎变成了一道匹配题。如果两个点i和j移动后斜率相同,我们将他们之间连一条边,这样就组成了一张图,我们要保证一个点最多连一条边,求选择的边数。然而这张图点数为105级别,边数可能达到1010级别,是不可能实现的。这就是我的思维所限。

恰当的做法是,把每个斜率看作一个点,如果一个点i能达到斜率k_1和k_2,那么就在k_1和k_2之间连一条边。这张无向图点的个数为4*105,边个数为2*105。我们要找到“两条边有一个公共点”这样的边对的最大数量,当然,每条边只能被选择一次。

我们考虑答案的上限。下面说的都是对新图的处理。我们考虑一个点,如果它连有m条边,那么它参与的边对数目最多为m/2。我们要尽可能达到这个上限。

现在考虑dfs树。我们从树的叶子出发,叶子结点为u,它的父亲为fa。如果不包括(u,fa)在内,u连有偶数条边,那么结果一定是把这些边都算到u点上是最优的;如果u连有奇数条边,那么我们把(u,fa)也算到u上去。这样这几条边的归属就决定好了,我们把这几条边删掉,现在fa成为了新的叶子节点,就这样递归考虑。

思路顺理成章,但是实现难度还是有的。这就考验代码能力了,也提醒着我们要熟练掌握STL。有思路却不会写就太憋屈了吧。

#include<bits/stdc++.h>
using namespace std;

typedef long long LL;
const int N=2e5+10;

int n;
pair<pair<LL,LL>,pair<LL,LL>> p[N];
vector<pair<LL,LL>> all;
vector<pair<int,int>> gra[N<<1];
vector<int> can[N<<1];
vector<pair<int,int>> res;
bool vis[N<<1], used[N];

bool dfs(int u, int pre=-1){
	vis[u]=true;
	for(auto &e: gra[u]){
		int v=e.first, i=e.second;
		if(!vis[v]){
			used[i]=true;
			dfs(v,u);
			if(can[v].size()&1){
				can[v].push_back(i);
			}else{
				can[u].push_back(i);
			}
		}else if(v!=pre){
			if(used[i]) continue; 
			used[i]=true;
			can[u].push_back(i);
		}
		
	}
}

int main(){
	scanf("%d", &n);
	for(LL i=1, a, b, c, d, g, x, y; i<=n; ++i){
		scanf("%lld%lld%lld%lld", &a, &b, &c, &d);
		x=d*(a+b); y=b*c;
		g=__gcd(x,y);
		p[i].first={x/g,y/g};
		x=a*d; y=b*(c+d);
		g=__gcd(x,y);
		p[i].second={x/g,y/g};
		all.push_back(p[i].first);
		all.push_back(p[i].second);
	}
	sort(all.begin(),all.end());//关于auto和unique的用法  
	auto pos=unique(all.begin(),all.end());
	all.erase(pos,all.end());
	for(int i=1, u, v; i<=n; ++i){//用下标代替map 
		u=lower_bound(all.begin(),all.end(),p[i].first)-all.begin();
		v=lower_bound(all.begin(),all.end(),p[i].second)-all.begin();
		gra[u].push_back({v,i});
		gra[v].push_back({u,i});
	}
	
	for(int i=all.size()-1; ~i; --i){
		if(!vis[i]) dfs(i);
	}
	
	for(int i=all.size()-1; ~i; --i){
		int sz=can[i].size();
		for(int j=0; j+1<sz; j+=2){
			res.push_back({can[i][j],can[i][j+1]});
		}
	}
	printf("%d\n", res.size());
	for(auto &i: res){
		printf("%d %d\n", i.first, i.second);
	}
	
}

补充

这份代码有很多值得学习的地方。

首先是STL的应用。我对vector、pair、set、map等的认知还停留在高中时期,比如上面的erase(pos1,pos2)我之前是不了解的。
然后是一些函数的应用。比如unique我一直不太会使,__gcd我也是刚知道,虽然手写也很简单。
接下来是c++11的特性,如auto,如{first,second}等,这些都是方便码题的重要工具。
再然后是一些技巧,如用下标代替map的技巧。

最后,我也应该了解一下hash_map(可能也叫unordered_map?),据说是查找会更快一些。

啊,还有好多事需要做啊。

标签:map,Off,训练,fa,CF1519E,斜率,一个点,移动,我们
来源: https://www.cnblogs.com/white514/p/14725277.html

本站声明: 1. iCode9 技术分享网(下文简称本站)提供的所有内容,仅供技术学习、探讨和分享;
2. 关于本站的所有留言、评论、转载及引用,纯属内容发起人的个人观点,与本站观点和立场无关;
3. 关于本站的所有言论和文字,纯属内容发起人的个人观点,与本站观点和立场无关;
4. 本站文章均是网友提供,不完全保证技术分享内容的完整性、准确性、时效性、风险性和版权归属;如您发现该文章侵犯了您的权益,可联系我们第一时间进行删除;
5. 本站为非盈利性的个人网站,所有内容不会用来进行牟利,也不会利用任何形式的广告来间接获益,纯粹是为了广大技术爱好者提供技术内容和技术思想的分享性交流网站。

专注分享技术,共同学习,共同进步。侵权联系[81616952@qq.com]

Copyright (C)ICode9.com, All Rights Reserved.

ICode9版权所有