1. 程式人生 > 實用技巧 >【題解】[IOI2008] Teleporters 傳送器

【題解】[IOI2008] Teleporters 傳送器

題目連結

(強烈建議觀看洛谷良心題面↑不要去黑暗爆炸)

Statement

給定一條直線,上面有 \(N\) 對傳送門,每當你到達某個傳送器的兩個端點之一時,傳送器都會立即將你傳送到該傳送器的另一個端點。被傳送到另一個端點之後,必須繼續沿這條直線路線向東行進;無法避開前進路上的任何傳送器端點。記傳送次數為得分。

現在允許增加 \(M\) 對新的傳送門(同樣計分),可以在任意實數座標上,但所有端點必須唯一,且位於起點和終點之間。

求能獲得的最高分數。

自言自語

不愧是IOI,很簡單的一道題,但就是想不到。大概是我菜罷。

順便怒斥 zjjws 竟然咕咕咕了。這可是IOI啊

Solution

貌似是之前全網唯一題解/kel

考慮對整個數軸按照給定的傳送門分段,每一對傳送門把當前段分成兩段。

顯然,每個段內部都不會有其他傳送門(不然就會再次分段),按照這個來進行建圖。

建圖:

  • 到達每個段有且僅有一種方式:走這個段左端點處的傳送門。

  • 離開這個段也只有一種方式:走右端點處的傳送門(因為如果是左端點肯定是進入這段而不是出去,如果是中間那麼只能向右走)。

  • 這樣,把每個段看成一個點,那麼每個點只有唯一的出入和入度,起始點除外。

  • 也就是說,我們一定會從起點入,從終點出。

現在我們有若干個連通塊。不難發現,除了起點到終點是一條簡單路徑,其他點一定在一個環上(這個很顯然吧,都是唯一入度/出度,怎麼說也不會連沒啊)。

來考慮加傳送門。

有三種方式:

  • 連線兩個不在同一個連通塊的點。
    • 那麼這兩個點分成兩部分,所在連通塊合併。
  • 連線兩個同一連通塊的點。
    • 這樣會把一個連通塊拆成兩半,對答案沒有任何貢獻。直接忽略。
  • 放在某一段內部,成為一個獨立的點。
    • 這種情況下,可以先成點再連入答案連通塊,但是要花費兩個代價。

那麼解法就很明顯了。

將所有原圖中的連通塊(都是鏈/環)按大小降序排序,然後從大到小依次用第一種方法連到“起點-終點”的路徑上去。如果連通之後還有剩餘,採用第三種方法就能花費兩倍代價累計入答案。如果剩餘個數是奇數,就將一個連到終點前面的地方,能增加一個貢獻。

實現的時候,直接特殊處理起點到終點的路徑,然後找環即可。

Code

菜雞的程式碼又臭又長

//Author: RingweEH
const int N=1e6+10;
struct Transport        //存所有傳送門(door)
{
    int l,r;
}d[N];
struct Node
{
    int pos,typ,group;      //left:0,right:1
    bool operator < ( const Node &tmp ) const { return pos<tmp.pos; }
}a[N<<1];
int n,m,ans=0,cnt=0,siz[N<<1];
bool vis[N<<1];

int Find( int x )   //找 next door
{
    Node tmp; tmp.pos=x;
    return lower_bound( a+1,a+1+2*n,tmp )-a;
}

void Beginning()
{
    int now=1,nxt;
    while ( now!=2*n+1 )
    {
        vis[now]=1; ans++;
        if ( a[now].typ==0 ) nxt=Find( d[a[now].group].r+1 );
        else nxt=Find( d[a[now].group].l+1 );
        now=nxt;
    }
    vis[now]=1;
}

void Get_Subgraph( int x,int cnt )
{
    int now=x,nxt;
    while ( !vis[now] )
    {
        vis[now]=1; siz[cnt]++;
        if ( a[now].typ==0 ) nxt=Find( d[a[now].group].r+1 );
        else nxt=Find( d[a[now].group].l+1 );
        now=nxt;
    }
}

int main()
{
    n=read(); m=read();
    for ( int i=1; i<=n; i++ )
    {
        d[i].l=read(),d[i].r=read();
        int now=(i-1)*2+1;
        a[now].pos=d[i].l,a[now].typ=0,a[now].group=i; now++;
        a[now].pos=d[i].r,a[now].typ=1,a[now].group=i;
    }

    sort( a+1,a+1+2*n );
    Beginning();
    for ( int i=1; i<=2*n; i++ )
        if ( !vis[i] ) Get_Subgraph( i,++cnt );

    sort( siz+1,siz+1+cnt );
    for ( int i=cnt; i>=1; i-- )
    {
        ans+=2; m--; ans+=siz[i];
        if ( !m ) break;
    }
    if ( m&1 ) m--,ans++;
    ans+=2*m;

    printf( "%d\n",ans );

    return 0;
}