zoukankan      html  css  js  c++  java
  • 【题解】[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
    ",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);
    }
    
  • 相关阅读:
    C# comboBox实现省市两级联动(winform)
    Alter用法
    封装SQLHelper
    杨中科版C#射击游戏
    C# TXT文件导入至数据库
    C# 手机号码归属地查询
    C#中从数据库导出至txt
    解决C#中txt文档导入数据库时,中文显示乱码的问题
    第一篇博文与技术无关 纯瞎扯
    全国省市数据库
  • 原文地址:https://www.cnblogs.com/UntitledCpp/p/14180961.html
Copyright © 2011-2022 走看看