【bzoj2811】APIO2012 守卫 guard

       题目,点击就送

           这道题竟然不是差分?!(虽然看着很像,但是注意他有一个K的限制,而且差分求出的只是一个合理的方案,也不满足这里的必须。。)       在网上搜到了一个说用差分A了的人,然而他说为了不带坏小朋友,就不说这种骗分的做法了(%%%)。

           标准解法是贪心,首先删除那些一定没有的区间,zro hzwer orz 是把区间重新编号,我懒得编,就每次去二分位置,然后去掉那些区间长度为1的区间,这是必定有的。对于剩下的,这里要判一下是否剩下的和可能点个数相等,若相等,则全都是啦。首先在区间右边端点贪心可以得到解,然后我们考虑由右端点向左走,如果这时算出来的答案已经大于了我点的限制,那么前面那个一定是一个解了,这里可以用动归处理,细节处理解释在代码里面有。。

           

#include<iostream>
#include<cstdio>
#include<algorithm>
#include<cstdlib>
#include<cstring>
#include<queue>
#define N 200005
using namespace std;
int n,m,u,tot=0;
struct eee{
   int a,b,c;
   eee(int _a=0,int _b=0,int _c=0):a(_a),b(_b),c(_c){}
   bool operator<(const eee &o)const{
      return a<o.a||(a==o.a&&b>o.b);
   }
}a[N],b[N];
int c[N],res[N],sum[N],sum2[N],f[N],rp[N],la[N];
void add(int x,int v){
	while(x<=n){
		c[x]+=v;
		x+=(x&(-x));
	}
}
int ask(int x){
	int ret=0;
	while(x){
		ret+=c[x];
		x-=(x&(-x));
	}
	return ret;
}
int ans[N];
int main(){
	//freopen("","r",stdin);
	//freopen("","w",stdout);
	int i,j;
	scanf("%d%d%d",&n,&u,&m);
	for(i=1;i<=m;i++){
		scanf("%d%d%d",&a[i].a,&a[i].b,&a[i].c);
		if(!a[i].c){add(a[i].a,1);add(a[i].b+1,-1);}
	}
	for(i=1;i<=n;i++){
		if(ask(i))res[i]=0;//一定没有标记为0 
		else res[i]=1;//反之 
		sum[i]=sum[i-1]+res[i];
	}
	for(i=1;i<=m;i++)
	    if(a[i].c&&sum[a[i].b]==sum[a[i].a-1]+1){
	    	int wei=upper_bound(sum+a[i].a,sum+a[i].b+1,sum[a[i].a-1])-sum;
	    	if(res[wei]!=2){//不确定是否一定有人 
	    		ans[++ans[0]]=wei;
	    		res[wei]=2;
	    		u--;
			}
		}
		else if(a[i].c&&!(sum[a[i].b]-sum[a[i].a-1])){puts("-1");return 0;}
	for(i=1;i<=n;i++){
		if(res[i]==2)sum2[i]=sum2[i-1]+1;
		else sum2[i]=sum2[i-1];
	}
	for(i=n;i>=1;i--){
		if(res[i]==1)sum[i]=sum[i+1]+1;
		else sum[i]=sum[i+1];
	}
	if(u==sum[1]&&u){
		for(i=1;i<=n;i++)
		  if(res[i])printf("%d\n",i);
		return 0;
	}
	else if(sum[1]<u){
		puts("-1");
		return 0;
	}
//	for(i=1;i<=ans[0];i++)cout<<"()()()()"<<ans[i]<<endl;
	sort(a+1,a+m+1);
//	for(i=1;i<=m;i++)cout<<a[i].a<<" "<< a[i].b<<" "<<a[i].c<<endl;
	for(i=1;i<=m;i++)
	   if(a[i].c){
		 if(sum2[a[i].b]>sum2[a[i].a-1])continue;//此区间已满足, 不要了。 
		 while(tot&&a[i].b<=b[tot].b)tot--;//若b把a完全包含,则不要b,因为若a满足了b一定满足 
		 b[++tot]=a[i];
	}
//	cout<<"***"<<tot<<endl;
	f[tot+1]=0;
	for(i=tot;i>=1;i--){
		int ll=b[i].a,rr=b[i].b;
		int L=ll,R=rr;
		while(L<R){
			int mid=(L+R+1)/2;
			if(sum[mid]-sum[rr+1]>0)L=mid;
			else R=mid-1;
		}
		rp[i]=L;//找到此区间真正的右端点 
		la[i]=upper_bound(b+i+1,b+tot+1,eee(rp[i],-1))-b;//找到左端点在我这里的那个 
		f[i]=f[la[i]]+1;//DP 
	}
	int xz=1;
	while(xz<=tot){
		int ll=b[xz].a,rr=b[xz].b;
		int L=ll,R=rr;
		while(L<R){
			int mid=(L+R+1)/2;
			if(sum[mid]-sum[rr+1]>0)L=mid;
			else R=mid-1;
		}
		int wei=L;//找到真正的右端点 
		L=ll;R=rr;
		while(L<R){
			int mid=(L+R+1)/2;
			if(sum[mid]-sum[rr+1]>1)L=mid;
			else R=mid-1;
		}
		int wei2=L;//找到真正的左端点,即wei~wei2中间必定有一个  
		j=upper_bound(b+xz+1,b+tot+1,eee(wei2,-1))-b;//找到左端点在wei2的区间
		if(f[j]+1>u)ans[++ans[0]]=wei;//一定要放一个更优 
		u--;
		xz=upper_bound(b+xz+1,b+tot+1,eee(wei,-1))-b;
	}
	if(!ans[0])puts("-1");
	else{
		sort(ans+1,ans+ans[0]+1);
		for(i=1;i<=ans[0];i++)printf("%d\n",ans[i]);
	}
	//fclose(stdin);
	//fclose(stdout);
    return 0;
}
这题怎么会写这么长我去QAQ


  • 2
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值