P14382 [JOISC 2017] 开荒者 / Cultivation
题目大意
(根据个人习惯,将题目中的部分变量名做了修改)
有一个
问,将所有点变为
解题思路
现在有一片矩形的草块,从
- 向北,变为
(x-1,y) 到(xx,yy) - 向南,变为
(x,y) 到(xx+1,yy) - 向西,变为
(x,y-1) 到(xx,yy) - 向东,变为
(x,y) 到(xx,yy+1)
很容易发现,操作的顺序与最终的覆盖无关,也就是说,我们只需要找每个操作的次数就可以了。再随便优化一下,我们就有了一个
子任务3应该是很有启发性的,仍然可以
假设这些点的y坐标为序列
要把
但
我们观察上面的图发现,这个时候
现在还有一个
实现上来看,我们枚举的
补充说明:能覆盖到某一行的
我的实现细节诡异的多,建议不要太参考。
附代码。
#include<bits/stdc++.h>
#define ll long long
#define LL __int128
#define pb push_back
#define fi first
#define se second
#define low(x,n,k) (int)(lower_bound(x+1,x+(n+1),k)-x)
#define upp(x,n,k) (int)(upper_bound(x+1,x+(n+1),k)-x)
using namespace std;
char BEGIN;
namespace hwq{
inline int read(){int x=0;char ch=getchar();while(!isdigit(ch)) ch=getchar();while(isdigit(ch)) x=(x<<3)+(x<<1)+ch-48,ch=getchar();return x;}
const int MAXN=1e5+5;
const int INF=2e9+5;
int n,m,K;
int nxt[MAXN],pre[MAXN],head;
ll f[305][305][3],mx[3][MAXN];
struct node{
int x,y;
bool operator<(const node &a)const{return x<a.x;};
}s[MAXN];
struct deque{
int que[MAXN],ql,qr,mx[MAXN],L[MAXN],R[MAXN];
inline void clear(){ql=1,qr=0;}
inline void Add(int l,const int &r,const int &x){
// printf("Add(%d,%d,%d)\n",l,r,x);
if(l>r) return ;
while(ql<=qr&&mx[qr]<=x){
if(R[qr]+1==l) l=L[qr];
qr--;
}
mx[++qr]=x,L[qr]=l,R[qr]=r;
}
inline void Del(const int &x){while(ql<=qr&&R[ql]<=x) ql++;}
inline ll front(){return mx[ql];}
inline int gL(){return ql>qr?INF:L[ql];}
inline int gR(){return ql>qr?INF:R[ql];}
}q[3];
int main(){
n=read(),m=read(),K=read();
for(int i=1;i<=K;i++) s[i].x=read(),s[i].y=read();
sort(s+1,s+K+1);
auto add=[&](const int &x){
pre[x]=nxt[x]=0;
if(!head||s[head].y>=s[x].y) return nxt[x]=head,pre[head]=x,head=x,void();
int i=head;
for(;nxt[i];i=nxt[i]) if(s[nxt[i]].y>=s[x].y) break;
nxt[x]=nxt[i],pre[x]=i;
if(nxt[i]) pre[nxt[i]]=x;
nxt[i]=x;
};
for(int i=1;i<=K;i++){
head=0;
for(int j=i;j<=K;j++){
add(j);
int k=head;
f[i][j][0]=s[head].y-1;
for(;nxt[k];k=nxt[k]) f[i][j][1]=max(f[i][j][1],(ll)s[nxt[k]].y-s[k].y-1);
f[i][j][2]=m-s[k].y;
}
}
auto solve=[&](const int &len){
if(len<0) return (ll)INF;
ll ans=(ll)INF;
for(int p=0;p<3;p++) q[p].clear();
for(int i=1,j=1,pre=s[i].x-len;i<=K;i++){
while(s[j].x<s[i].x-len){
if(s[j].x==s[j-1].x){
j++;
continue;
}
for(int p=0;p<3;p++) q[p].Del(s[j].x-n);
pre=max(pre,s[i-1].x-len);
for(int p=0;p<3;p++) q[p].Add(pre,s[j].x,f[j][i-1][p]);
pre=max(pre,s[j].x+1);
if(max({q[0].gL(),q[1].gL(),q[2].gL()})<=s[j].x-n+1) ans=min(ans,max(q[1].front(),q[0].front()+q[2].front()));
j++;
}
if(s[i].x==s[j].x||s[i].x==s[i-1].x) continue;
for(int p=0;p<3;p++) q[p].Del(s[i].x-len-1-n);
pre=max(pre,s[j].x-len);
for(int p=0;p<3;p++) q[p].Add(pre,s[i].x-len-1,f[j][i-1][p]);
pre=max(pre,s[i].x-len);
if(max({q[0].gL(),q[1].gL(),q[2].gL()})<=s[i].x-len-1-n+1) ans=min(ans,max(q[1].front(),q[0].front()+q[2].front()));
}
return ans+len;
};
s[++K]=node{INF,0},s[0]=node{-INF,0};
ll ans=solve(0);
for(int i=1;i<K;i++) for (int j=i;j<K;j++) ans=min(ans,min({solve(s[j].x-s[i].x-1),solve(s[i].x-1+n-s[j].x),solve(s[j].x-1+n-s[i].x)}));
printf("%lld",ans);
return 0;
}
}
char END;
int main(){
#ifdef HQ
printf("Memory : %lld MB\n",(&BEGIN-&END)>>20);
// freopen("03-02.in","r",stdin);
// freopen(".out","w",stdout);
#endif
hwq::main();
return 0;
}
/*
2 4
2
1 1
1 4
4 4
3
1 4
2 2
3 3
专栏总结:https://www.luogu.com.cn/article/6030jn9i
实现框架:
1. K^2枚举最外层的a+b
2. 扫描线预处理加滑动窗口求解n行合法的c+d最小值
实现细节:
1. 无脑开大数组,反正K都很小,别爆就行
2. 离散化t序列
3. solve内不能直接统计答案,覆盖的行可能超过n,还需滑动窗口
我天,1.25h想出来了,有进步,被提c+d那一步卡了一会,总的说还行
滑动窗口:最终可能是一个x区间内的东西提供贡献,如果K^2表示的话,要满足x[j]-(x[i]-len)+1>=n
第一次觉得滑动窗口困难
f[i][j]要维护三个部分!!!0表示最前面,1表示中间,2表示最后面
*/