• IOI2021集训队作业 297AK Surveillance


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

    (n,kle 10^6)


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

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

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

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

    口胡一下其正确性:显然,如果得到了最优的选择弧的集合(S),那么从任意弧(xin 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
    ");
    			return 0;
    		}
    	}
    	for (int i=1;i<=n;++i){
    		c[i]+=c[i-1];
    		if (!c[i]){
    			printf("impossible
    ");
    			return 0;
    		}
    	}
    	build();
    	ans=k+1;
    	for (int i=1;i<=k;++i){
    		if (!vis[i])
    			dfs(i);
    	}
    	printf("%d
    ",ans);
    	return 0;
    }
    
  • 相关阅读:
    (字典树)Revenge of Fibonacci -- HDU -- 4099
    (字符串 KMP)Blue Jeans -- POJ -- 3080:
    (广搜)聪明的打字员 -- POJ --1184
    (线段树 点更新 区间求和)lightoj1112
    Jquery弹窗插件Lhgdialog的用法
    SQL Server数据库大型应用解决方案总结
    C# 使用XmlDocument类对XML文档进行操作
    反射实例【转】
    如何使用dynamic
    [C#]DataTable常用操作总结
  • 原文地址:https://www.cnblogs.com/jz-597/p/13965418.html
Copyright © 2020-2023  润新知