【题解】[IOI2008] Teleporters 传送器

题目链接

(强烈建议观看洛谷良心题面↑不要去黑暗爆炸)

Statement

给定一条直线,上面有 \(N\) 对传送门,每当你到达某个传送器的两个端点之一时,传送器都会立即将你传送到该传送器的另一个端点。被传送到另一个端点之后,必须继续沿这条直线路线向东行进;无法避开前进路上的任何传送器端点。记传送次数为得分。

现在允许增加 \(M\) 对新的传送门(同样计分),可以在任意实数坐标上,但所有端点必须唯一,且位于起点和终点之间。

求能获得的最高分数。

Solution

貌似是全网唯一题解/kel

不愧是IOI,很简单的一道题,但就是想不到。大概是我菜罢。

考虑对整个数轴按照给定的传送门分段,每一个传送门把当前段分成两段。

显然,每个段内部都不会有其他传送门(不然就会再次分段),按照这个来进行建图。

建图:

  • 到达每个段有且仅有一种方式:走这个段左端点处的传送门。

  • 离开这个段也只有一种方式:走右端点处的传送门(因为如果是左端点肯定是进入这段而不是出去,如果是中间那么只能向右走)。

  • 这样,把每个段看成一个点,那么每个点只有唯一的出入和入度,起始点除外。

  • 也就是说,我们一定会从起点入,从终点出。

现在我们有若干个连通块。不难发现,除了起点到终点是一条简单路径,其他点一定在一个环上(这个很显然吧,都是唯一入度/出度,怎么说也不会连没啊)。

来考虑加传送门。

有三种方式:

  • 连接两个不在同一个连通块的点。
    • 那么这两个点分成两部分,所在连通块合并。
  • 连接两个同一连通块的点。
    • 这样会把一个连通块拆成两半,对答案没有任何贡献。直接忽略。
  • 放在某一段内部,成为一个独立的点。
    • 这种情况下,可以先成点再连入答案连通块,但是要花费两个代价。

那么解法就很明显了。

将所有原图中的连通块(都是链/环)按大小降序排序,然后从大到小依次用第一种方法连到“起点-终点”的路径上去。如果连通之后还有剩余,采用第三种方法就能花费两倍代价累计入答案。如果剩余个数是奇数,就将一个连到终点前面的地方,能增加一个贡献。

实现的时候,直接特殊处理起点到终点的路径,然后找环即可。

Code

菜鸡的代码又臭又长……洛谷的机子也不太行,BZOJ 上面直接过去了,洛谷还要开 O2……

//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;
}

BZOJ 上另一份代码 真的短……

fread 也是真的快……

#include<cstdio>
const int N=2000010,BUF=20000000;
int n,m,mx,i,x,y,ans,g[N],f[N];bool v[N],flag;char Buf[BUF],*buf=Buf;
inline void read(int&a){for(a=0;*buf<48;buf++);while(*buf>47)a=a*10+*buf++-48;}
inline void go(int x){
  int t=0;
  while(!v[x]){
    v[x]=1;
    if(g[x])x^=g[x],t++;
    x++;
  }
  if(!flag)ans+=t;else f[t]++;
  flag=1;
}
int main(){
  fread(Buf,1,BUF,stdin);read(n),read(m);
  for(i=1;i<=n;i++){
    read(x),read(y);
    if(y>mx)mx=y;
    g[x]=g[y]=x^y;
  }
  v[mx+=5]=1;
  for(i=0;i<=mx;i++)if(!v[i])go(i);
  for(i=n*2;i;i--)while(m&&f[i])m--,f[i]--,ans+=i+2;
  while(m>=2)m-=2,ans+=4;
  printf("%d",ans+m);
}
posted @ 2020-12-23 20:46  MontesquieuE  阅读(215)  评论(0编辑  收藏  举报