题解 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\))显然不可取。考虑什么时候答案会变化。其实只有如下的两种情形:
- \(u\)恰好能将某个草吹到上边界,且\(d\)恰好能将某个草吹到下边界。
- \(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;
}