zoukankan      html  css  js  c++  java
  • 题解 LOJ2390 「JOISC 2017 Day 1」开荒者

    题目链接

    容易发现性质:

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

    考虑枚举向上和向下的操作次数,分别记为(u), (d)。此时会先将所有初始点,吹成若干个竖着的长条。我们来计算,此时最少需要多少向左、向右的操作。枚举每一行。记这一行里,已经有草的位置为(p_1,p_2dots,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;
    }
    
  • 相关阅读:
    并查集基础练习
    HDU1232——畅通工程
    二分答案——划分数列
    二分答案——收入计划
    动态规划练习题(2)
    动态规划程序设计2
    动态规划练习题(1)
    0/1背包
    P5024 保卫王国[倍增+dp]
    UVA11424 GCD
  • 原文地址:https://www.cnblogs.com/dysyn1314/p/13063020.html
Copyright © 2011-2022 走看看