洛谷3463 [POI2007]EGZ-Driving Exam(DP)(树状数组)

版权声明:本文为博主原创文章,未经博主允许不得转载,除非先点了赞。 https://blog.csdn.net/A_Bright_CH/article/details/83087360

题目

成都的驾驶考试在一个有n条平行的自南向北的单向的道路的场地中进行。每条道路长度为m米,并且都在同一条水平线上开始和结束。街道从西向东分别编号为1到n。同样有p条单向的自西向东或自东向西的街道垂直于上面描述的街道,每一条这样的街道链接了两个相邻的自南向北的道路。当然自西向东和自东向西的道路可以重叠,那就是一个双向的街道了。
考生选择一个自南向北的道路作为他考试的起始点和另外一个自南向北的道路作为他考试的终止点。他们的考试项目是将车从开始的道路驾驶到作为终止点的道路。考生们总是选择一个可以到达所有其他街道的起始道路作为开始点。现在,考生们总是感到十分无趣因为他们只有很少的起始道路可以选择,所以教练们决定改造先有的考试场所,由于经费的限制,他们决定添加至多K条东西向的道路,使得能够选择的起始道路尽量地多。

特性

一个点出发能到达第1和第n条路,那么它可以到达所有路。
一个点i出发能到1,那么一定有一个不下降子序列,长度为i。

题解

DP+树状数组
考虑从1到i的问题,设f[i]表示为了让i能到达第1条路,至少要加多少条路。直接求f[i]并不好求,再设f'[i]表示已有的可以一直往上走到1的路径条数,所以有f[i]=i-1-f'[i]。
再设一个s[i],来求最长不下降子序列。
把所有的路离水平线的距离看作下标,f'[x]=max( f'[x-1] , max_{i>=y}\left\{s[i]\right\}+1 ),其中y表示有一条 x,y 到 x-1,y。
看到最大值,可以树状数组维护。因为树状数组不善于维护这种以m为底这种情况,所以我们不妨把整个树状数组反过来,就是让0->m+1,1->m,...,m->1。式子转变成f'[x]=max(f'[x-1],findmax(y))
还有一个所有树状数组求极值都要注意的,就是修改和询问的先后。
同理处理出g[i]表示从i到n最少增加边数。
求答案时,若有f[j]+g[i]<=k,且j<=i,那么[j,i]中的节点既能到1,又能到n,答案为i-j+1。

代码

#include<cstdio>
#include<cstring>
#include<algorithm>
using namespace std; 
const int maxn=100010;

inline int read()
{
    int re=0;char ch=getchar();
    while(ch<'0' || ch>'9') ch=getchar();
    while(ch>='0' && ch<='9') re=re*10+(ch^48),ch=getchar();
    return re;
}

int n,m,p,k;
int f[maxn],g[maxn];

struct E{int y,w,next;}Le[maxn],Re[maxn];int Llen=0,Rlen=0,Llast[maxn],Rlast[maxn];
void Lins(int x,int y)
{
    Le[++Llen]=(E){y,0,Llast[x]};Llast[x]=Llen;
}
void Rins(int x,int y)
{
    Re[++Rlen]=(E){y,0,Rlast[x]};Rlast[x]=Rlen;
}

int mx[maxn];
void change(int x,int c)
{
    for(;x<=m;x+=x&-x) mx[x]=max(mx[x],c);
}
int findmax(int x)
{
    int re=0;
    for(;x>=1;x-=x&-x) re=max(re,mx[x]);
    return re;
}

int main()
{
    n=read();m=read();p=read();k=read();m++;
    for(int i=1;i<=p;i++)
    {
        int x=read(),y=read(),dir=read();y=m-y;//反过来建树状数组 
        if(dir==0) Rins(x,y);//x,y -> x+1,y
        else Lins(x+1,y);//x,y <- x+1,y
    }
    
    for(int i=2;i<=n;i++)
    {
        for(int j=Llast[i];j;j=Le[j].next) f[i]=max( f[i] , Le[j].w=findmax(Le[j].y)+1 );
        for(int j=Llast[i];j;j=Le[j].next) change( Le[j].y , Le[j].w );
        f[i+1]=f[i];f[i]=i-1-f[i];
    }
    
    memset(mx,0,sizeof(mx));
    for(int i=n-1;i>=1;i--)
    {
        for(int j=Rlast[i];j;j=Re[j].next) g[i]=max( g[i] , Re[j].w=findmax(Re[j].y)+1 );
        for(int j=Rlast[i];j;j=Re[j].next) change( Re[j].y , Re[j].w );
        g[i-1]=g[i];g[i]=n-i-g[i];
    }
    
    int ans=0,cnt=0;
    for(int i=1,j=1;i<=n;i++)
    {
        while(j<=n && f[j]+g[i]<=k) j++;//i枚举到n的,j枚举到1的 
        ans=max(ans,j-i);//不包含j:(j-1)-i+1=j-i
        if(f[i]==0 && g[i]==0) cnt++;//点i本来就可以直接到达任何点 
    }
    printf("%d\n",ans-cnt);
    return 0;    
}

猜你喜欢

转载自blog.csdn.net/A_Bright_CH/article/details/83087360