1. 程式人生 > >洛谷3463 [POI2007]EGZ-Driving Exam(DP)(樹狀陣列)

洛谷3463 [POI2007]EGZ-Driving Exam(DP)(樹狀陣列)

題目

成都的駕駛考試在一個有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;    
}