bzoj1107: [POI2007]驾驶考试egz LIS+单调队列
Description
成都的驾驶考试在一个有n条平行的自南向北的单向的道路的场地中进行。每条道路长度为m米,并且都在同一
条水平线上开始和结束。街道从西向东分别编号为1到n。同样有p条单向的自西向东或自东向西的街道垂直于上面
描述的街道,每一条这样的街道链接了两个相邻的自南向北的道路。当然自西向东和自东向西的道路可以重叠,那
就是一个双向的街道了。
考生选择一个自南向北的道路作为他考试的起始点和另外一个自南向北的道路作为他考试的终止点。他们的考
试项目是将车从开始的道路驾驶到作为终止点的道路。考生们总是选择一个可以到达所有其他街道的起始道路作为
开始点。现在,考生们总是感到十分无趣因为他们只有很少的起始道路可以选择,所以教练们决定改造先有的考试
场所,由于经费的限制,他们决定添加至多K条东西向的道路,使得能够选择的起始道路尽量地多。
Input
输入第一行包含四个整数n,m,p和k(2<=n<=100000, 1<=m,k<=100000, 0<=p<=100000)。分别表示南北向的道
路数,南北向道路的长度,东西向的道路数和最多能够添加的道路数。接下来p行,每行包含三个整数ni,mi和di(1
< = ni < n,0 < = mi < m,di = 0 or 1),表示一条自西向东(di = 0)或者自东向西(di = 1)的道路。这
个道路连接了南北向的道路ni和ni+1,在第mi米的地方进行了连接。
Output
输出中仅包含一个整数,最大的能够作为起点的道路数(不包括原来就能作为起点的道路数)。注意添加的道
路不能与南北向的道路相交,并且到起始水平线的距离为整数。添加的道路可以重叠,表示双向的道路。
题解
首先前部分讲的超棒的题解:https://blog.csdn.net/wang3312362136/article/details/80017011
然后我用的单调队列维护的前面已经维护过的道路的边权,但是注意,每次指针要指向队列的最后一个,才能保证答案一定最优。
最后用\(sum\)数组记前\(i\)个数有多少个是原来就是可以作为起点的道路,\(sum[j]-sum[i-1]\)就是\(i-j\)区间内本身就是起点的个数,接着双指针统计\(i-j\)区间的答案的最大值。
代码
#include<iostream>
#include<cstdio>
#include<cstring>
#include<algorithm>
#include<vector>
using namespace std;
const int maxn=1e5+10;
const int INF=0x3f3f3f3f;
int n,m,p,k;
vector<int>ld[maxn];
vector<int>rd[maxn];
int q[maxn],sum[maxn];
int l[maxn],r[maxn];
int main(){
// freopen("1107.in","r",stdin);
// freopen("1107.out","w",stdout);
ios::sync_with_stdio(false);
cin>>n>>m>>p>>k;
for(int i=1;i<=p;i++){
int x,w,opt;
cin>>x>>w>>opt;
if(opt==1) ld[x+1].push_back(w);
else rd[x].push_back(w);
}
ld[1].push_back(INF);
rd[n].push_back(INF);
for(int i=1;i<=n;i++){
sort(ld[i].begin(),ld[i].end());
sort(rd[i].begin(),rd[i].end());
}
int cnt=0,pos=1;
for(int i=1;i<=n;i++){
for(int j=0;j<ld[i].size();j++){
if(!cnt)q[++cnt]=ld[i][j];
else{
pos=cnt;
while(pos&&q[pos]<ld[i][j])pos--;
q[++pos]=ld[i][j];
cnt=max(cnt,pos);
}
}
l[i]=i-cnt;
}
cnt=0,pos=1;
for(int i=n;i>=1;i--){
for(int j=0;j<rd[i].size();j++){
if(!cnt)q[++cnt]=rd[i][j];
else{
pos=cnt;
while(pos&&q[pos]<rd[i][j])pos--;
q[++pos]=rd[i][j];
cnt=max(cnt,pos);
}
}
r[i]=n-i+1-cnt;
}
for(int i=1;i<=n;i++){
if(l[i]+r[i]==0)sum[i]++;
sum[i]=sum[i]+sum[i-1];
}
int ans=0;
for(int i=1,j=1;i<=n&&j<=n;i++){
while(j<n&&l[j+1]+r[i]<=k)j++;
if(l[j]+r[i]>k&&i==j){j++;continue;}
ans=max(ans,j-i+1-sum[j]-sum[i-1]);
}
cout<<ans<<endl;
}