Welcome to Lewis|

LEWISAK

园龄:10个月粉丝:13关注:10

【NOIP2017练习】 怎样更有力气

题面描述

长者的住宅中有一堵长度为n的墙。每天抖儿起床修行,会选择一段长度为x的区间染成白色。长者的住宅附近有一群记者,为了借助抖儿拜访长者,第i天记者会将区间[li,ri]染成白色来讨好抖儿(也就是说,每天墙会被抖儿和记者各染一次)。现在抖儿已经预先知道了记者的动向,他想知道他最少几天就能把墙全部染白,完成修行。

10pts

输出PoorDouer!

点击查看代码
#include<bits/stdc+.h>
using namespace std;
signed main(){
	cout<<"Poor Douer!"<<endl;
}

20pts(n<10)

n才10?

考虑状态压缩dp!

0,1表示放还是不放即可(WA是溢出了)

点击查看代码
#include<bits/stdc++.h>
#define int long long
using namespace std;
int ans=1e9,n,m,X,ll[100100],rr[100100],mapp;
signed main(){
	cin>>n>>m>>X;
	for(int i=1;i<=m;i++){
		int tt=0;
		cin>>ll[i]>>rr[i];
		for(int j=ll[i];j<=rr[i];j++){
			mapp|=(1<<(j-1));
		}
		for(int j=0;j<(1<<n)-1;j++){
			int t=mapp;
			for(int k=0;k<X;k++){
				t|=((j<<k));
			}
			t=t%(1<<n);
			if(t!=(1<<n)-1){
				continue;
			}
			int tt=0;
			for(int k=j;k;k-=(k&(-k))){
				tt++;
			}
			if(tt<=i){
				cout<<i;
				return 0; 
			}
		}
	}
	cout<<"Poor Douer!"<<endl;
}

20pts(x=0)

x=0的话就代表只靠记者来染了...

很容易发现m<=105可以直接枚举,每次枚举时修改的复杂度在 log 级别以内

等等,log+区间修改???

线段树!!!(超开心的大喊)(扭曲爬行)(变成点烤烤)

注:20pts是为后面的70pts做铺垫

点击查看代码
#include<bits/stdc++.h>
#define int long long
using namespace std;
int n,m,x,ll[1000100],rr[1000100];
struct zxcdasshabiyinggaichedidebaoling_zhehuiwojueduibudiandigantanhao{
//不要试图看懂↑
	int l,r,sum,lazy;
}tree[5000100];
#define ls (id<<1)
#define rs (id<<1|1)
#define mid ((tree[id].l+tree[id].r)>>1)
#define up tree[id].sum=tree[ls].sum+tree[rs].sum
//个人习惯(define狂魔)
//下面的线段树挺板的,不多说了
void down(int id){
	if(tree[id].lazy){
		tree[ls].sum=(tree[ls].r-tree[ls].l+1);
		tree[rs].sum=(tree[rs].r-tree[rs].l+1);
		tree[ls].lazy=1;
		tree[rs].lazy=1;
		tree[id].lazy=0; 
	}
}
void build(int l,int r,int id){
	tree[id].l=l;
	tree[id].r=r;
	tree[id].lazy=0;
	tree[id].sum=0;
	if(l==r){
		tree[id].sum=0;
		return;
	}
	build(l,mid,ls);
	build(mid+1,r,rs);
	up;
}
void add(int l,int r,int id){
	if(tree[id].l>=l&&tree[id].r<=r){
		tree[id].sum=(tree[id].r-tree[id].l+1);
		tree[id].lazy=1;
		return;
	}
	down(id);
	if(l<=mid){
		add(l,r,ls);
	}
	if(r>mid){
		add(l,r,rs);
	}
	up;
}
int sum2(int l,int r,int id){
	if(tree[id].l>=l&&tree[id].r<=r){
		return tree[id].sum;
	}
	down(id);
	int ans=0;
	if(x<=mid){
		ans=sum2(l,r,ls);
	}
	else{
		ans+=sum2(l,r,rs);
	}
	return ans;
}
signed main(){
	cin>>n>>m>>x;
	for(int i=1;i<=m;i++){
		cin>>ll[i]>>rr[i];
	}
	build(1,n,1);
	for(int i=1;i<=m;i++){
		add(ll[i],rr[i],1);
		if(sum2(1,n,1)==n){
			cout<<i;
			return 0;
		}
	}
	cout<<"Poor Douer!"<<endl;
}

60pts

发现可以只记录涂色的起点,那么我们不妨从左到右遍历,如果未涂色,则必须从这里为起点涂色

正确性显然因为前面没涂到,后面影响不到,不从你开始从谁开始?

写个循环跑就行了不是为啥分这么高

点击查看代码
#include<bits/stdc++.h>
#define int long long
using namespace std;
int ans=1e9,n,m,X,ll[100100],rr[100100],mapp[100100];
signed main(){
	cin>>n>>m>>X;
	for(int i=1;i<=m;i++){
		int tt=0;
		cin>>ll[i]>>rr[i];
		for(int j=ll[i];j<=rr[i];j++){
			mapp[j]=1;
		}
		if(!X){
			for(int j=1;j<=n;j++){
				if(!mapp[j]){
					tt=1e9;
				}
			}
		}
		else{
			for(int j=1;j<=n;j++){
				if(!mapp[j]){
					tt++;
					j=j+X-1;//直接跳过省去修改
				}
			}
		}
		if(tt<=i){
			cout<<i;
			return 0;
		}
	}
	cout<<"Poor Douer!"<<endl;
}

70pts

经过分析,发现记者绝对不会帮倒忙,就是说从某一天之后,以后都能涂完。

难道说?

二分!!!

很好,按照m二分。

那么check函数呢?

可以使用之前的贪心

问题来了

怎么O(log)维护记者涂成什么样子呢???

记者会区间修改。。。

🤨总不能是?

🤩没错!!

😑别!!想想50+行代码

😆使用线段树!!!

😨NO!!!

时间辅助度:O(nlog(m)log(n))

实际上卡一卡能AC

点击查看代码
#include<bits/stdc++.h>
#define int long long
using namespace std;
int n,m,x,ll[100100],rr[100100];
struct zxcdasshabiyinggaichedidebaoling_zhehuiwojueduibudiandigantanhao{
	int l,r,sum,lazy;
}tree[500100];
#define ls (id<<1)
#define rs (id<<1|1)
#define mid ((tree[id].l+tree[id].r)>>1)
#define up tree[id].sum=tree[ls].sum+tree[rs].sum
void down(int id){
	if(tree[id].lazy){
		tree[ls].sum=(tree[ls].r-tree[ls].l+1);
		tree[rs].sum=(tree[rs].r-tree[rs].l+1);
		tree[ls].lazy=1;
		tree[rs].lazy=1;
		tree[id].lazy=0; 
	}
}
void build(int l,int r,int id){
	tree[id].l=l;
	tree[id].r=r;
	tree[id].lazy=0;
	tree[id].sum=0;
	if(l==r){
		tree[id].sum=0;
		return;
	}
	build(l,mid,ls);
	build(mid+1,r,rs);
	up;
}
void add(int l,int r,int id){
	if(tree[id].l>=l&&tree[id].r<=r){
		tree[id].sum=(tree[id].r-tree[id].l+1);
		tree[id].lazy=1;
		return;
	}
	down(id);
	if(l<=mid){
		add(l,r,ls);
	}
	if(r>mid){
		add(l,r,rs);
	}
	up;
}
int sum(int x,int id){
	if(tree[id].l==tree[id].r){
		return tree[id].sum;
	}
	down(id);
	if(x<=mid){
		return sum(x,ls);
	}
	else{
		return sum(x,rs);
	}
}
int sum2(int l,int r,int id){
	if(tree[id].l>=l&&tree[id].r<=r){
		return tree[id].sum;
	}
	down(id);
	int ans=0;
	if(x<=mid){
		ans=sum2(l,r,ls);
	}
	else{
		ans+=sum2(l,r,rs);
	}
	return ans;
}
bool check(int mi){
	build(1,n,1);
	for(int i=1;i<=mi;i++){
		add(ll[i],rr[i],1);
	}
	if(x==0){//不特判会卡死
		return sum2(1,n,1)==n;
	}
	int cnt=0;
	for(int i=1;i<=n;i++){
		if(!sum(i,1)){
			i=i+x-1;//直接跳过省去add(相当于小优化吧)(-1是因为i下一个循环还要+1)(x=0是会因为这里卡死)
			cnt++;
		}
	}
	return cnt<=mi;
}
signed main(){
	cin>>n>>m>>x;
	for(int i=1;i<=m;i++){
		cin>>ll[i]>>rr[i];
	}
	int l=1,r=m;
	while(l<=r){
		int mi=(l+r)/2;
		if(check(mi)){
			r=mi-1;
		}
		else{
			l=mi+1;
		}
	}
	if(l>m){
		cout<<"Poor Douer!"<<endl;
		return 0;
	}
	cout<<l<<endl;
}

100pts

at last~~

考虑优化check函数!

发现朵儿涂一段区间要的天数实际上可以直接用(rl+1)/x计算!

然后就想到可以将记者染的区间对左端点进行一个排的序,然后记录目前涂到的最右边的端点,这样就可以直接计算朵儿涂墙要的天数力!

结果正解代码比60pts还短

点击查看代码
#include<bits/stdc++.h>
#define int long long
using namespace std;
int n,m,x;
struct zxc{
	int l,r;
}a[100100],b[100100];
bool cmp(zxc x,zxc y){
	return x.l<y.l;
}
bool check(int mi){
	for(int i=1;i<=m;i++){
		b[i]=a[i];
	}
	sort(b+1,b+1+mi,cmp);
	int R=0;
	if(x==0){
		for(int i=1;i<=mi;i++){
			if(b[i].l>R+1){
				return 0;
			}
			R=max(R,b[i].r);
		}
		if(R!=n){
			return 0;
		}
		return 1;
	}
	int sum=0,ans=0;
	for(int i=1;i<=mi;i++){
		if(b[i].l>R+1){
			sum=(b[i].l-R-1)/x;
			if((b[i].l-R-1)%x!=0){
				sum++;
			}
		}
		else{
			sum=0;
		}
		R=max(b[i].r,R+sum*x);
//这里R要加上sum*x是因为如果朵儿多染了的话,就会比b[i].r大了
		ans+=sum;
	}
	if(n>R){
		sum=(n-R)/x;
		if((n-R)%x!=0){
			sum++;
		}
	}
	else{
		sum=0;
	}
	ans+=sum;
	return ans<=mi;
}
signed main(){
	cin>>n>>m>>x;
	for(int i=1;i<=m;i++){
		cin>>a[i].l>>a[i].r;
	}
	int l=0,r=m;
	while(l<=r){
		int mi=(l+r)/2;
		if(check(mi)){
			r=mi-1;
		}
		else{
			l=mi+1;
		}
	}
	if(l>m){
		cout<<"Poor Douer!"<<endl;
		return 0;
	}
	cout<<l<<endl;
}

hack原理

可以看到楼下是一个hack我甚至嚣张的让他hack我

原理是有人(wzh)处理x=0的时候特判了以下内容:

if(b[mid].r!=n){
    return false;
}

问题在于b[mi].r代表的是左端点最右的,而不是右端点最右的

画个图:

image

可以发现,此时排序后1号在前,那么b[mid].r就等于8,代码就被hack

本文作者:LEWISAK

本文链接:https://www.cnblogs.com/lewisak/p/18270584

版权声明:本作品采用知识共享署名-非商业性使用-禁止演绎 2.5 中国大陆许可协议进行许可。

posted @   LEWISAK  阅读(24)  评论(0编辑  收藏  举报
/
点击右上角即可分享
微信分享提示
评论
收藏
关注
推荐
深色
回顶
收起