题解 LOJ2390 「JOISC 2017 Day 1」开荒者

题目链接

容易发现性质:

单独一个点,假如固定每个操作的数目,则得到的草呈矩形,且形状不会应操作顺序变化而变化。所以最后的结果与操作顺序无关。同时发现当向上、下次数总和一定时,若无上下边界,则草地形状一样。

考虑枚举向上和向下的操作次数,分别记为\(u\), \(d\)。此时会先将所有初始点,吹成若干个竖着的长条。我们来计算,此时最少需要多少向左、向右的操作。枚举每一行。记这一行里,已经有草的位置为\(p_1,p_2\dots,p_k\)。那么,向左的操作次数必须至少\(p_1-1\)次;向右的操作次数必须至少\(C-p_k\)次;向左、向右的操作次数之和必须至少\(\max_{i=2}^{k}(p_i-p_{i-1}-1)\)次。对所有行的三种限制分别取最大值,得向左至少\(x\)次,向右至少\(y\)次,向左向右之和必须至少\(z\)次。那么,此时向左向右的总操作次数必须至少为\(\max(x+y,z)\)。用\(\max(x+y,z)+u+d\)更新答案。

暴力枚举\(u\), \(d\)\(10^9\))显然不可取。考虑什么时候答案会变化。其实只有如下的两种情形:

  1. \(u\)恰好能将某个草吹到上边界,且\(d\)恰好能将某个草吹到下边界。
  2. \(u\), \(d\)之一恰好能将某个草吹到对应的边界,且\(u\), \(d\)之和等于某两个点行坐标之差\(-1\)

暴力枚举\(u\), \(d\),复杂度是\(O(n^3)\)的。前面描述的枚举行,求向左、向右操作次数的方法,可以对行“离散化”,复杂度是\(O(n^2)\)的。总时间复杂度\(O(n^5)\)

继续优化。我们考虑到:向上、下次数总和一定时,若无上下边界,则草地形状一样。我们只枚举\(u+d\)之和,记为\(s\)。此时,原来的每个点\((x,y)\),变成一个从\((x-s,y)\)\((x,y)\)的长条。这样,原图的纵向被拉地很长。发现知道\(s\)以后,每种\(u\), \(d\)的取值方案,相当于在这张很长的图上,截取长度为\(R\)的一段,作为真正的地图。例如,\(u=0,d=s\),就相当于取图中最靠上的一段;\(u=s,d=0\),就相当于截取最靠下的一段。于是,问题转化为,对每个长度为\(R\)的“滑动的窗口”,求区间里【向左】、【向右】以及【向左向右之和】三者的最大值(前文中的\(x,y,z\)),然后更新答案。可以直接扫描,用单调队列维护。同时,由于原本的每个点,只被拆成了两个端点,所以行坐标离散化后还是\(O(n)\)个。扫描一次的时间复杂度\(O(n^2)\)。而\(s=u+d\)的取值只有\(O(n^2)\)种,所以总时间复杂度优化为\(O(n^4)\)

考虑从小到大枚举\(s\)。图里的每个“长条”,\((x,y)\)这头不会动,\((x-s,y)\)这头的行坐标会继续减小。发现这\(2n\)个点的位置关系只会变化\(n^2\)次。而每变化一次(相当于交换相邻的两行),可以\(O(n)\)重构这两行。所以总时间复杂度\(O(n^3)\)

参考代码(在LOJ查看):

//problem:LOJ2390
#include <bits/stdc++.h>
using namespace std;

#define pb push_back
#define mk make_pair
#define lob lower_bound
#define upb upper_bound
#define fi first
#define se second
#define SZ(x) ((int)(x).size())

typedef unsigned int uint;
typedef long long ll;
typedef unsigned long long ull;
typedef pair<int,int> pii;

template<typename T>inline void ckmin(T& x,T y){x=(x<y?x:y);}
template<typename T>inline void ckmax(T& x,T y){x=(x>y?x:y);}

const int MAXN=300;
const int INF=2e9;
int R,C,n;
struct Point_t{
	int x,y;// x->行 y->列
	Point_t(){}
	Point_t(int _x,int _y){x=_x;y=_y;}
}a[MAXN+5];
bool cmp_x(Point_t a,Point_t b){return a.x<b.x;}
bool cmp_y(Point_t a,Point_t b){return a.y<b.y;}
void Unique(vector<int>& v){
	sort(v.begin(),v.end());
	v.erase(unique(v.begin(),v.end()),v.end());
}

struct Event_t{
	int pos,op,id;
	Event_t(){}
	Event_t(int _pos,int _op,int _id){pos=_pos;op=_op;id=_id;}
}b[MAXN*2+5];
bool cmp(Event_t a,Event_t b){
	if(a.pos==b.pos)
		return a.op>b.op;//先加入后删除!!!
	return a.pos<b.pos;
}
int cnt_event,len[MAXN*2+5],min_left[MAXN*2+5],min_right[MAXN*2+5],min_sum[MAXN*2+5];
bool exist[MAXN*2+5][MAXN+5];

void update(int i){
	for(int j=1;j<=n;++j)
		exist[i][j]=exist[i-1][j];
	if(b[i].op==1)
		exist[i][b[i].id]=1;
	else
		exist[i][b[i].id]=0;
	int lst=0;
	min_sum[i]=0;
	for(int j=1;j<=n;++j){
		if(exist[i][j]){
			if(!lst)
				min_left[i]=a[j].y-1;
			else
				ckmax(min_sum[i],a[j].y-a[lst].y-1);
			lst=j;
		}
	}
	assert(lst!=0);
	min_right[i]=C-a[lst].y;
	ckmax(min_sum[i],min_left[i]+min_right[i]);
}
void init(int sum){
	sort(a+1,a+n+1,cmp_y);//a的id,此时从小到大就代表了y坐标
	cnt_event=0;
	for(int i=1;i<=n;++i){
		b[++cnt_event]=Event_t(a[i].x-sum,1,i);
		b[++cnt_event]=Event_t(a[i].x+1,-1,i);
	}
	sort(b+1,b+cnt_event+1,cmp);
	for(int i=1;i<=cnt_event-1;++i){
		len[i]=b[i+1].pos-b[i].pos;
		update(i);
	}
}

void Swap(int p,int q){
	assert(q==p+1);
	swap(b[p],b[q]);
	for(int i=p;i<=q;++i){
		update(i);
	}
}
int calc(int delta){
	for(int i=1;i<=cnt_event;++i){
		if(b[i].op==1)b[i].pos-=delta;
	}
	while(true){
		bool flag=false;
		for(int i=1;i<=cnt_event-2;++i){
			if(b[i].pos>b[i+1].pos){
				flag=true;
				Swap(i,i+1);
			}
		}
		if(!flag)break;
	}//冒泡排序
	for(int i=1;i<=cnt_event-1;++i){
		len[i]=b[i+1].pos-b[i].pos;
	}
	deque<int>q_left,q_right,q_sum;
	int ans=INF;
	for(int i=1,j=1;i<=cnt_event-1;++i){
		while(j<=cnt_event-1 && b[j].pos-b[i].pos<R){
			if(len[j]){
				#define POPBACK(q,arr) do{\
					while(!(q).empty() && (arr)[(q).back()]<=(arr)[j])\
						(q).pop_back();\
				}while(0)
				POPBACK(q_left,min_left);  q_left.push_back(j);
				POPBACK(q_right,min_right);q_right.push_back(j);
				POPBACK(q_sum,min_sum);    q_sum.push_back(j);
				#undef POPBACK
			}
			++j;
		}
		if(b[j].pos-b[i].pos<R)return ans;
		ans=min(ans,max(min_left[q_left.front()]+min_right[q_right.front()],min_sum[q_sum.front()]));
		#define POPFRONT(q) do{\
			while(!(q).empty() && (q).front()==i)\
				(q).pop_front();\
		}while(0)
		POPFRONT(q_left);
		POPFRONT(q_right);
		POPFRONT(q_sum);
		#undef POPFRONT
	}
	return ans;
}

int main() {
	cin>>R>>C>>n;
	for(int i=1;i<=n;++i)cin>>a[i].x>>a[i].y;
	sort(a+1,a+n+1,cmp_x);
	int min_updown_sum=a[1].x-1+R-a[n].x;
	for(int i=2;i<=n;++i)
		ckmax(min_updown_sum,a[i].x-a[i-1].x-1);
	
	vector<int>v_up,v_down,v_sum;
	for(int i=1;i<=n;++i){
		v_up.pb(a[i].x-1);
		v_down.pb(R-a[i].x);
		for(int j=i+1;j<=n;++j){
			if(a[j].x-a[i].x-1>=min_updown_sum){
				v_sum.pb(a[j].x-a[i].x-1);
			}
		}
	}
	Unique(v_up);
	Unique(v_down);
	for(int i=0;i<SZ(v_up);++i){
		for(int j=0;j<SZ(v_down);++j){
			if(v_up[i]+v_down[j]>=min_updown_sum){
				v_sum.pb(v_up[i]+v_down[j]);
			}
		}
	}
	Unique(v_sum);
	assert(SZ(v_sum)>0);
	init(v_sum[0]);
	ll ans=R+C-2;
	for(int i=0;i<SZ(v_sum);++i){
		ckmin(ans,(ll)v_sum[i]+calc(!i?0:v_sum[i]-v_sum[i-1]));
	}
	cout<<ans<<endl;
	return 0;
}
posted @ 2020-06-07 23:29  duyiblue  阅读(733)  评论(0编辑  收藏  举报