【題解】[IOI2008] Teleporters 傳送器
阿新 • • 發佈:2020-12-23
(強烈建議觀看洛谷良心題面↑不要去黑暗爆炸)
Statement
給定一條直線,上面有 \(N\) 對傳送門,每當你到達某個傳送器的兩個端點之一時,傳送器都會立即將你傳送到該傳送器的另一個端點。被傳送到另一個端點之後,必須繼續沿這條直線路線向東行進;無法避開前進路上的任何傳送器端點。記傳送次數為得分。
現在允許增加 \(M\) 對新的傳送門(同樣計分),可以在任意實數座標上,但所有端點必須唯一,且位於起點和終點之間。
求能獲得的最高分數。
自言自語
不愧是IOI,很簡單的一道題,但就是想不到。大概是我菜罷。
順便怒斥 zjjws
竟然咕咕咕了。這可是IOI啊
Solution
考慮對整個數軸按照給定的傳送門分段,每一對傳送門把當前段分成兩段。
顯然,每個段內部都不會有其他傳送門(不然就會再次分段),按照這個來進行建圖。
建圖:
-
到達每個段有且僅有一種方式:走這個段左端點處的傳送門。
-
離開這個段也只有一種方式:走右端點處的傳送門(因為如果是左端點肯定是進入這段而不是出去,如果是中間那麼只能向右走)。
-
這樣,把每個段看成一個點,那麼每個點只有唯一的出入和入度,起始點除外。
-
也就是說,我們一定會從起點入,從終點出。
現在我們有若干個連通塊。不難發現,除了起點到終點是一條簡單路徑,其他點一定在一個環上(這個很顯然吧,都是唯一入度/出度,怎麼說也不會連沒啊)。
來考慮加傳送門。
有三種方式:
- 連線兩個不在同一個連通塊的點。
- 那麼這兩個點分成兩部分,所在連通塊合併。
- 連線兩個同一連通塊的點。
- 這樣會把一個連通塊拆成兩半,對答案沒有任何貢獻。直接忽略。
- 放在某一段內部,成為一個獨立的點。
- 這種情況下,可以先成點再連入答案連通塊,但是要花費兩個代價。
那麼解法就很明顯了。
將所有原圖中的連通塊(都是鏈/環)按大小降序排序,然後從大到小依次用第一種方法連到“起點-終點”的路徑上去。如果連通之後還有剩餘,採用第三種方法就能花費兩倍代價累計入答案。如果剩餘個數是奇數,就將一個連到終點前面的地方,能增加一個貢獻。
實現的時候,直接特殊處理起點到終點的路徑,然後找環即可。
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;
}