[SCOI2015]国旗计划

题解 [SCOI2015]国旗计划

前言

fr大佬给我们讲课中的一道例题。前面的题太基础,没认真听;后面的题太难,没认真听;从其中二分了一道对于本蒟蒻能听懂来说看起来能做的一道。( ̄▽ ̄)"

这道题个人认为还是蛮有难度的。从理论到实现细节都有坑点,要是没有题解,赛场上肯定是废废

题目

正在前往 P4155 [SCOI2015]国旗计划

题目简而言之,就是在一个环上有一些区间,询问在某些区间选择的前提下,用最少的区间覆盖整个环。

解题

基本思想

首先,看到环的题,尝试破环为链。将长度为n的环,首位拼接为长度为2*n的链。这样一来就将一个环上的问题转移成了线性问题。

由于边防卫士最大2e5位,而边防站最大可达1e9个,因此离散化是必须的。按照边防卫士来离散化,而不是按照边防站的距离一个一个+1.

然后接着思考,假如数据足够善良,所有的l都小于等于r。

ALhnPg.md.png

那么就是一个简单的贪心问题。由于每一位边防卫士的奔袭范围是不重合的,也就是它们的两个端点都不会重合,即不会出现完全相同的情况。再加上“不包含”的限制,也就是说区间的左坐标和右坐标的都是严格递增的。即l越大\(\Rightarrow\)r越大。

根据这一点性质,我们发现,从第i位边防卫士出发,他要么完成了一圈,求得了ans;要么没能完整地走完一圈,需要传递接力棒,那么一定要传给下一位区间与这一位边防战士区间有交集,且右坐标尽可能最大的一位。可以很容易想明白这样做可以保证最优感性理解下

那么从头至尾按照这种思维模拟下来即可求得左坐标全部小于右坐标的情况。

到现在为止,我们还有两方面问题没有解决:将解法由阉割版本升级为完整版本;优化算法复杂度

本来按照顺序,应该先讲解解法的拓展,但是按照难度顺序来,先讲优化算法复杂度更好些。

倍增优化

可以发现,从一个战士转移到下一位战士是固定的,可以记i的转移对象为f[i],那么此时f[i]也有它的下一个转移对象f[f[i]]...发现其实从源点到汇点的路径已经规划好了,那么怎么加速转移?用倍增

原来是一步一步向下跳,那么这次就倍增的跳。设skip[i][j]为从第i位战士转移\(2^j\)次后的下一位战士。

为了满足答案最优,应该经可能节省着转移。而转移目标则应该是i+m的lowerbound,这样才能保证最优。

将指数从大到小枚举一下,确保下一次跳到的战士覆盖范围小于i+M,记录ans。最后再来一次转移,使得区间能够覆盖i+m,输出ans+1即可

解法拓展

上述解法只限于l严格小于r的情况下,那怎么处理l大于r呢?

注意在这道题的特殊题意限定下,开头是已知的

AL4wtS.md.png

  1. 如果是l小于r的(如绿色边),那么就在前一半建一条边,后一半建一条边
  2. 如果是l大于r的(如蓝色边),那么就在两端中间建边

由于开端被限制在了前一半中,所以从前一半的某一点i到后一半与之对应的一个点i+m的路径上,所有信息存储完整,只要按照弱化版本一样处理边可以求出以所有干员为开端的最少干员数

动手推一下就能理解吧

但是,你认为这样就天衣无缝了嘛?错!而且会错的丧心病狂

AL5E4S.md.png

酱紫呢?从蓝色箭头开始是不是就无法环绕一圈了?那错在哪里了呢?

AL5Kun.md.png

解决方法也是异常简单:只需要为l大于r的边(形如绿色边)也建两条即可。

由于看完文字题解,第一张图的形象已经深刻的刻印在我的脑海里,就忽略了的第二张图的特例。导致第二个数据点的340个输出数字中,第274个输出怎么也无法和答案对应上。假如在你90分即将ac时,发现原因是因为340个数字中你错了一个,这个时候你也就不会有什么心态好好挑错改代码了。好在评论区有一位大神指出了图二反例,才让我顺利ac。否则没有题解和评论区,这道题我是肯定无法ac的。

代码

code:

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

const int MAX=4e5+5,INF=0x3f3f3f3f;
int n,m;
int cnt; pair <int,int> size[MAX];
int skip[MAX][25];
int order[MAX]; map<int,int> output;

bool cmp(pair<int,int>,pair<int,int>);
int lowerbound(int);

int main(){
	freopen("test.in","r",stdin);
	freopen("test.out","w",stdout);

	scanf("%d%d",&n,&m);
	for(int i=1;i<=n;++i){
		int l,r; scanf("%d%d",&l,&r);
		order[++order[0]]=l;
		if(r<l){
			cnt++;
			size[cnt].first=l; size[cnt].second=r+m;
			cnt++;
			size[cnt].first=l+m; size[cnt].second=r+m+m;
		}
		else{
			cnt++;
			size[cnt].first=l; size[cnt].second=r;
			cnt++;
			size[cnt].first=l+m; size[cnt].second=r+m;
		}
	}
	sort(size+1,size+1+cnt,cmp);

	for(int i=1;i<=cnt-1;++i){
		int ch=lowerbound(size[i].second);
		if(size[ch].first>size[i].first) skip[i][0]=ch;
		else skip[i][0]=INF;
	}
	skip[cnt][0]=INF;

	for(int i=1;i<=20;++i){
		for(int j=1;j<=cnt;++j){
			if(skip[j][i-1]==INF){
				skip[j][i]=INF; continue;
			}
			skip[j][i]=skip[skip[j][i-1]][i-1];
		}
	}

	for(int p=1;p<=n;++p){
		int ans=1,tar=size[p].first+m,u=p;
		for(int i=20;i>=0;--i){
			int v=skip[u][i];
			if(v>cnt||size[v].second>=tar) continue;
			else u=v,ans+=(1<<i);
		}
		// printf("%d ",ans+1);
		output[size[p].first]=ans+1;
	}

	for(int i=1;i<=n;++i){
		printf("%d ",output[order[i]]);
	}

	return 0;
}

bool cmp(pair<int,int> a,pair<int,int> b){
	return a.first<b.first;
}

int lowerbound(int k){
	int l=1,r=cnt;
	while(l<=r){
		int mid=(l+r)>>1;
		if(size[mid].first<=k) l=mid+1;
		else r=mid-1;
	}
	return r;
}

后记

个人认为这道题假如不限制开头,也就是题干修改为“无任何强制性限制,求最少数目的卫士,其奔袭区间能够覆盖环”的话,转而把枚举设为一个思维难点,题目难度还会再增加一个数量级。

刚刚从省选的试炼中回过神来,再次向省选伸出试探性的步子。结果再次感受到省选扑面而来的恐怖如斯的炼狱难度。

果然还是自己太菜了。争取明年省选进队,痛打辣鸡的脸!(ノ`Д)ノ

posted @ 2020-06-29 22:35  ticmis  阅读(146)  评论(0编辑  收藏  举报