IOI2021集训队作业 297AK Surveillance

一个长度为\(n\)的环,有\(k\)条弧。选择最少的弧覆盖整个环。

\(n,k\le 10^6\)


考虑暴力:枚举第一条弧,然后贪心选下一条,选择左端点小于等于当前弧右端点加一,且右端点最大的弧。一直如此做下去直到覆盖了整个环为止。

\(x\)的下一条为\(p_x\),可以预处理出来。

网上普遍的做法是用倍增,即枚举每个初始弧,然后倍增出至少要跑多少次才能覆盖整个环。时间\(O(k\lg k)\)

我的做法是连边\((x,p_x)\),在形成的环套树森林中找出每个环,对于每个环任意选择一条初始弧跑。时间\(O(k)\)

口胡一下其正确性:显然,如果得到了最优的选择弧的集合\(S\),那么从任意弧\(x\in S\)出发跑,都可以跑出最优解;那么假如从\(x\)出发跑可以获得最优解,那么从\(p_x\)出发跑也可以获得最优解;于是可以发现,环上存在最优的初始弧,然后推出环上的每条边都是最优的初始弧。所以任意选一条初始弧即可。


using namespace std;
#include <cstdio>
#include <cstring>
#include <algorithm>
#define N 1000005
int n,k;
int l[N*2],r[N*2];
int s[N*2],t[N*2];
bool cmps(int a,int b){return l[a]<l[b];}
bool cmpt(int a,int b){return r[a]<r[b];}
int p[N*2];
void build(){
	for (int i=1;i<=k;++i) l[i+k]=l[i]+n,r[i+k]=r[i]+n;
	for (int i=1;i<=k*2;++i) s[i]=t[i]=i;
	sort(s+1,s+k*2+1,cmps),sort(t+1,t+k*2+1,cmpt);
	int mx=0;
	for (int i=1,j=1;i<=k*2;++i){
		for (;j<=k*2 && l[s[j]]<=r[t[i]]+1;++j){
			if (r[s[j]]>r[mx])
				mx=s[j];
		}
		if (r[mx]>r[t[i]])
			p[t[i]]=mx;
	}
	for (int i=1;i<=k;++i){
		int c=0;
		if (p[i]==0)
			c=p[i+k];
		else if (p[i+k]==0)
			c=p[i];
		else
			c=(r[p[i]]-r[i]>r[p[i+k]]-r[i+k]?p[i]:p[i+k]);
		if (c>k)
			c-=k;
		p[i]=c;
//		int x=p[i],y=p[i+k];
//		x=(x>k?x-k:x);
//		y=(y>k?y-k:y);
//		if (x==0) p[i]=y;
//		else if (y==0) p[i]=x;
//		else p[i]=((r[x]-r[i]+n+n)%n>(r[y]-r[i]+n+n)%n?x:y);
	}
}
int ans;
int vis[N],cnt,bz[N];
void dfs(int x){
	vis[x]=++cnt;
	bz[x]=1;
	if (bz[p[x]]){
		bz[x]=0;
		int s=1;
		for (int y=p[x];y!=x;y=p[y]){
			++s;
			int tmp=l[x]-1;
			if (tmp==0)	tmp=n;
			if (r[y]<n?(l[y]<=tmp && tmp<=r[y]):(tmp<=r[y]-n || tmp>=l[y]))
				break;
		}
		ans=min(ans,s);
		return;
	}
	if (vis[p[x]]){
		bz[x]=0;
		return;
	}
	dfs(p[x]);
	bz[x]=0;
}
int c[N];
int main(){
	freopen("in.txt","r",stdin);
	freopen("out.txt","w",stdout);
	scanf("%d%d",&n,&k);
	for (int i=1;i<=k;++i){
		scanf("%d%d",&l[i],&r[i]);
		if (l[i]>r[i]){
			c[1]++,c[r[i]+1]--;
			c[l[i]]++;
			r[i]+=n;
		}
		else
			c[l[i]]++,c[r[i]+1]--;
		if (r[i]-l[i]+1==n){
			printf("1\n");
			return 0;
		}
	}
	for (int i=1;i<=n;++i){
		c[i]+=c[i-1];
		if (!c[i]){
			printf("impossible\n");
			return 0;
		}
	}
	build();
	ans=k+1;
	for (int i=1;i<=k;++i){
		if (!vis[i])
			dfs(i);
	}
	printf("%d\n",ans);
	return 0;
}
posted @ 2020-11-12 18:26  jz_597  阅读(134)  评论(0编辑  收藏  举报