Codeforces Round #509 (Div. 2) 解题报告(C - F)

目前掉rating最多的一场(多半以后就要破纪录了),C题出现了失误,想出一个xjb算法居然以为是对的,还跑过了8个小数据…
(这令我深刻地意识到小数据什么算法都可以过)


比赛链接:http://codeforces.com/contest/1041
题面粘贴格式不大好又不大会弄,请自行前往比赛页面查看吧
官方也有题解,但不一定和本博客相同,请自行查阅

C. Coffee Break

题意:给你一组数字,希望让你把这些数分配到一些箱子里,且箱子里按升序排列后,相邻数字的间隔必须≥d + 1,求最少需要的箱子数,以及每个数字所对应的箱子的序号(答案不唯一,SPJ)
在这里插入图片描述

恨不能遗忘!

史上WA的最多的题之一,一开始想了个SB算法,居然赛后一直以为是对的(当然赛中挂在了Pretest 9上),发现问题后又想了个辣鸡算法,最后才搞出正解…
其实思路很简单,我们想让每一天能尽可能多的能容纳时间节点,所以我们希望用优先队列维护当前各天最大时间MIN,也就是说我们将手中的节点插入到最合适的位置,如果发现都不能插入,我们就新建节点插入即可,输出第一行ans当然是所有建立的节点数,而各点答案我们给所有节点按先后插入顺序编号即可。
AC Code:

#include<bits/stdc++.h>
#define rg register
#define il inline
#define ll long long
#define maxn 500005
using namespace std;
il int read(){rg int x = 0 , w = 1 ;rg char ch = getchar();while (ch < '0' || ch > '9'){if (ch == '-') w = -1;ch = getchar();}while (ch >= '0' && ch <= '9'){x = (x << 3) + (x << 1) + ch - '0'; ch = getchar();}return x * w;}
struct node{
	int val , res;
	int id;	
}t[maxn];
struct cmp{

    bool operator ()(const node &a, const node &b)
    {
        return a.val > b.val;
    }
};
bool cmp1(node a,node b){
	return a.val < b.val;	
}
bool cmp2(node a,node b){
	return a.id < b.id;	
}
int p[maxn] , head[maxn];
int main(){
	priority_queue<node , vector<node> , cmp>  q;
	int ans = 0 , cnt = 0;
	int n = read() , m = read() , d = read();
	++d;
	for (rg int i = 1 ; i <= n ; ++i)
		t[i].val = read() , t[i].id = i;
	sort(t + 1 , t + 1 + n , cmp1);
	t[1].res = ++cnt;
	q.push(t[1]);
	for (int i = 2;  i <= n ; ++i){
		if (t[i].val - q.top().val < d) {
			t[i].res = ++cnt;
			q.push(t[i]);
			//if (t[i].val - head[last + 1  > cnt?1:last + 1] >= d ) {if (last + 1 > cnt) last = 1;else ++last;++p[last] , head[last] = t[i].val , t[i].res = last;}
			//else ++p[++cnt] , head[cnt] = t[i].val , t[i].res = cnt;
		}
		else{
			node new_node = q.top();
			t[i].res = new_node.res;
			q.pop();
			new_node.val = t[i].val;
			q.push(new_node);
		}
	}
	printf("%d" , cnt);
	putchar('\n');
	sort(t + 1 , t + 1 + n , cmp2);
	for (rg int i = 1 ; i <= n ; ++i) printf("%d ",t[i].res);
	return 0;
}

D. Glider

题意:给你一个初始值h,现在数轴上(int范围内)只有0 和 -1两种数字,且0 和 - 1的区间一定是连续的(如:…-1 -1 0 0 - 1 -1 0 0 0 - 1 - 1 -1 0 0 -1 -1…),现在告诉你所有0区间的左端点和右端点,求使该区间和 + h >= 0的最长区间
暴力n方显然不可行,像这种优化我们通常都是定义一些新的奇妙的数组来帮助我们优化计算,这里我们定义d[i] = 这个0区间和上一个0区间所间隔的区间长度(即通过这个间隔区间所需花费的代价),然后我们使用前缀和统计d。我们知道从每个0区间的左端点出发是最优的,因此,我们二分查找当前出发区间右边的d [MAX](此时已是前缀和形式) 且 d [MAX ]<= h - d[now]即将n ^ 2降到了n * log n
AC Code:

#include<bits/stdc++.h>
#define rg register
#define il inline
#define ll long long
#define maxn 500005
using namespace std;
il int read(){rg int x = 0 , w = 1 ;rg char ch = getchar();while (ch < '0' || ch > '9'){if (ch == '-') w = -1;ch = getchar();}while (ch >= '0' && ch <= '9'){x = (x << 3) + (x << 1) + ch - '0'; ch = getchar();}return x * w;}
int l[maxn] , r[maxn];
int d[maxn];
int main(){
	rg int n = read() , h = read();
	int ans = 0;
	for (rg int i = 1 ; i <= n ; ++i){
		l[i] = read();
		r[i] = read();
		d[i] = l[i] - r[i - 1];
		if (i == 1) d[i] = 0;	
	}
	for (rg int i = 1 ; i <= n ; ++i) d[i] += d[i - 1];
	for (int i = 1 ; i <= n ; ++i){
		rg int hh = h + d[i];
		rg int x = lower_bound(d + i + 1, d + 1 + n , hh) - (d + 1 );
		ans = max (ans , hh - d[x] + r[x] - l[i]);
	}
	cout << ans;
	return 0;
}	

E. Tree Reconstruction

题意(可能稍微有点绕):给你一个正整数n,下面n - 1行有两个数xi , yi(xi < yi)代表的是删除一条边后剩下的两棵子树所含节点编号中最大的那个编号,如果能构造出这棵树,请输出"YES"和这棵树,否则输出"NO"
(提供下样例便于理解)
Examples
inputCopy
4
3 4
1 4
3 4
outputCopy
YES
1 3
3 2
2 4
inputCopy
3
1 3
1 3
outputCopy
NO
inputCopy
3
1 2
2 3
outputCopy
NO
Note
Possible tree from the first example. Dotted lines show edges you need to remove to get appropriate pairs.

对于构造这种题型,个人感觉一般都是找一种特殊或者极端的情况来输出,这样最为简便
此题我们首先明确,所有yi必须为n,否则非法,这个无需证明吧。
由于这是一棵树,且yi始终为n,我们干脆就以n为root建树,对于每个xi,我们统计它在输入里出现的次数,那么就为root到此xi的路径长度,因此我们共需要路径长度 - 1个节点来搭建这条路径,否则无法建树输出NO
一开始我以为这样处理后可能还有其他的未处理节点,后来发现我想多了…因为xi共有n - 1个,那么每组xi 、yi就需要新增一个节点(即使它们的叶子节点相同亦是如此),加上root就刚好n个,所以不会有未处理的节点
AC Code:

#include<bits/stdc++.h>
#define rg register
#define il inline
#define maxn 500005
using namespace std;
il int read(){rg int x = 0 , w = 1;rg char ch = getchar();while (ch < '0' || ch > '9'){if (ch == '-') w = -1;ch = getchar();}while (ch >= '0' && ch <= '9'){x = (x << 3) + (x << 1) + ch - '0';ch = getchar();}return x * w;}
struct edge{
	int to , next;	
}e[maxn << 1];
struct node{
	int x , y;	
}p[maxn];
int cnt , head[maxn];
int q[maxn] , num[maxn] , x[maxn] , y[maxn];
bool vis[maxn];
void add(int u,int v){
	e[++cnt].to = v;
	e[cnt].next = head[u];
	head[u] = cnt;
}	
int main(){
	bool ok1 = 1;
	rg int n = read();
	rg int tot = 0;
	for (rg int i = 1 ; i < n ; ++i){
		x[i] = read() , y[i] = read();	
		if (y[i] != n)
			{cout << "NO";return 0;}
		if (x[i] == n - 1 || y[i] == n - 1)
			ok1 = 1;
		if (!num[x[i]]) q[++tot] = x[i];
		++num[x[i]];
		vis[x[i]] = 1;
	}
	if (!ok1){
		cout << "NO";return 0; 	
	}
	sort (q + 1 , q + tot + 1);
	rg int now = 1 , res = 0;
	for (rg int i = 1 ; i <= tot ; ++i){
		rg int k = num[q[i]] - 1 , last = n;
		while (num[q[i]] > 1){
			while (now <= n && vis[now]) ++now;
			vis[now] = 1;
			if (now >= q[i] || now > n) {cout << "NO";return 0;}
			p[++res] = (node){last , now};
			last = now;
			--num[q[i]];
		}
		p[++res] = (node){last , q[i]};
	}
	cout << "YES";
	putchar('\n');
	for (rg int i = 1 ; i < n ; ++i){
		printf("%d %d\n" , p[i].x , p[i].y);	
	}
	return 0;	
}

F. Ray in the tube

先上一张图

好吧,一个毒(miao)瘤(zai)数据点不知道FST了几何程序…

题意:现在有一条由平行于x轴的两条直线组成的管道(即它们的中间部分),给出它们的纵坐标,在两直线上有一些传感器,给出它们的横坐标,现在你可以从管道的任何一个位置向任意一个方向发射一道激光,这道激光会在管道壁上反射,求一道激光能最多经过的传感器个数

本题要推一个结论:我们设某道激光上,从一条管道的一个点射到另一条管道的一个点所经过的位移在x轴上的投影d(如图)
在这里插入图片描述

我们可以证明d = 2 ^ k (k >= 0)时一定会取到最优解(此处我们甚至可以说是所有解(当然你这么想就会WA Test 79了)),如何证明呢?我们就设一个不是2的幂的数为 2 ^ k * m,自然m为奇,因为m为奇,我们的2 ^ k *m所取到的点就一定是2 ^ k 的子集
这玩意儿第一次看的确不好理解,因此我们还是用图形来辅佐证明
在这里插入图片描述
这里我们d取的是1,如果我们的投影变为1 * 3 = 3的话,就会取到下1、上4、下7,而这些都是已包含的,其他情况类比即可。
那么为什么我们非要用2 ^ k不用其他3 ^ k的呢,我们希望保证m为奇,而一旦m为偶,那么2 ^ k就可能取不到2 ^ k *m的一些点,比如上图我们的投影变为1 * 2 = 2的话,那么我们就会取下1 、上3、下5…而上3这是1所不包含的。
复杂度((n + m) *log(1e9))
当然,Test 79给了一个大惊喜,有一种特殊情况,即上面只有一个点,下面也只有一个点,按理说答案是2,因为我们射线的方向是任意的,可是我们上面的做法就没有考虑这种情况(毕竟在一般情况下竖直发射是一种低效的情况,它最多只取得到2个点),所以我们要判断一下有没有相等的情况,有就将答案的初始值设为2
AC Code:

#include<bits/stdc++.h>
#define rg register
#define il inline
#define maxn 500005
using namespace std;
il int read(){rg int x = 0 , w = 1;rg char ch = getchar();while (ch < '0' || ch > '9'){if (ch == '-') w = -1;ch = getchar();}while (ch >= '0' && ch <= '9'){x = (x << 3) + (x << 1) + ch - '0';ch = getchar();}return x * w;}
int a[maxn] , b[maxn];
int main(){
	map<int , bool>vis;
	bool ok1 = 1;	
	rg int maxnum = -1;
	rg int n = read() , y1 = read();
	for (rg int i = 1 ; i <= n ; ++i)
		a[i] = read() , vis[a[i]] = 1;
	rg int m = read() , y2 = read();	
	for (rg int i = 1 ; i <= m ; ++i){
		b[i] = read();
		if (vis[b[i]]) maxnum = 2;
	}
	for (rg int i = 1 ; i <= 1e9 / 2; i <<= 1){
		map<int , int> mp;
		map<int , int> mp2;
		rg int len = i << 1;
		for (rg int i = 1 ; i <= n ; ++i){
			rg int maxp = 0;
			rg int p = a[i] % len;
			++mp[p];
			maxnum = max(mp[p] , maxnum);
		}
		for (rg int i = 1 ; i <= m ; ++i){
			rg int p = b[i] % len;
			++mp2[p];
			maxnum = max(mp2[p] + mp[(p + len / 2) % len] , maxnum);
		}
	}
	cout << maxnum;
	return 0;	
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值