zoukankan      html  css  js  c++  java
  • 「NOIP2005」「Codevs1106」篝火晚会

    题目描述 Description

    佳佳刚进高中,在军训的时候,由于佳佳吃苦耐劳,很快得到了教官的赏识,成为了小教官。在军训结束的那天晚上,佳佳被命令组织同学们进行篝火晚会。一共有n个同学,编号从1n。一开始,同学们按照12……n的顺序坐成一圈,而实际上每个人都有两个最希望相邻的同学。如何下命令调整同学的次序,形成新的一个圈,使之符合同学们的意愿,成为摆在佳佳面前的一大难题。
    佳佳可向同学们下达命令,每一个命令的形式如下:
    (b1,b2,...bm-1,bm)
    这里m的值是由佳佳决定的,每次命令m的值都可以不同。这个命令的作用是移动编号是b1b2…… bm1bm的这m个同学的位置。要求b1换到b2的位置上,b2换到b3的位置上,……,要求bm换到b1的位置上。
    执行每个命令都需要一些代价。我们假定如果一个命令要移动m个人的位置,那么这个命令的代价就是m。我们需要佳佳用最少的总代价实现同学们的意愿,你能帮助佳佳吗?

    输入描述 Input Description

    输入第一行是一个整数n3<=n<=50000),表示一共有n个同学。其后n行每行包括两个不同的正整数,以一个空格隔开,分别表示编号是1的同学最希望相邻的两个同学的编号,编号是2的同学最希望相邻的两个同学的编号,……,编号是n的同学最希望相邻的两个同学的编号。

     

    输出描述 Output Description

    这一行只包含一个整数,为最小的总代价。如果无论怎么调整都不能符合每个同学的愿望,则输出-1。

    样例输入 Sample Input

    4

    3 4

    4 3

    1 2

    1 2

     

    样例输出 Sample Output

    2

    数据范围及提示 Data Size & Hint


    【数据规模】



    对于30%的数据,n<=1000

    对于全部的数据,n<=50000

    题解

    大概是我太弱了吧,每篇题解都一头懵哔emmm

    首先这道题题面可以说很难懂了

    有n个同学,初始时从1~n坐成一圈。每个同学有两个最想坐在边上的同学。
    
    问能否通过交换部分人的座位满足所有人的需求。若能满足,求交换的最小代价。
    
    其中交换的定义:对于某一次交换,让同学a坐到上次交换结束后b坐的位置上,b坐到上次交换结束后c的位置上......让被涉及到的最后一位同学坐到上次结束后a坐的位置上。
    
    代价的定义:对于每次交换,这次的代价为被涉及到的同学数,总代价为每次交换的代价之和。

    首先我们拆环成链。

    然后我们可以反过来想:给定一个序列,求交换成1~n的序列的最小代价。

    可以想到,其实只需要一次交换就可以解决问题:

    设同学a的编号为ca,则把a同学放到ca座位;
    
    然后把之前坐在ca号座位上的同学(设为b)赶到cb座位......
    
    这样每个坐错位置的人,都只会挪一次位置。
    
    所以这种拆链方案的代价=坐错位置的同学数


    这样就可以用n方的时间解决问题啦!(撒花

    在n方的解法中,我们枚举了断点。对于每个断点,用O(n)的时间再扫一遍。

    会炸。

    想像两个手链,一个手链从1写到n,另一个手链乱序。
    
    枚举断点,实际上只是把乱序手链的某个珠子对准1号珠拿好,然后把每个珠子看一遍,数不同的珠子数;再把乱序手链的下一个珠子对准1号珠......
    
    如果有一些珠子,它们在第一次数珠子的时候离目标位还有x个珠子,那么下一次离目标就会还有x-1格......
    
    那么它们要么同时对上,要么同时对不上。
    
    于是问题转化成了:
    
    随便拆一下,求此时的 max(dis[]),也就是一次最多对上的珠子数。
    
    答案就是n=max(dis[])的值,也就是最少有多少对不上。

    (撒花~

    #include<cmath>
    #include<cstdio>
    #include<cstring>
    #include<iostream>
    using namespace std;
    int f[50007][2],a[50007],dis[100007],b[50007];
    bool used[50007];
    int maxd=0;
    void make(int pos,int x){
        maxd=max(pos,maxd);//记录一下排座位到哪了
        a[pos]=x;used[x]=1;
        if(!used[f[x][0]]){make(pos+1,f[x][0]);}
        else if(!used[f[x][1]]){make(pos+1,f[x][1]);}
        return;
    }
    int main(){
        int n;
        scanf("%d",&n);
        //造乱序链
        //造链的方法很多啦,随便哪种都可以
        for(int i=1;i<=n;++i){
            int x,y;
            scanf("%d%d",&x,&y);
            if(f[x][0]){//如果x的0号朋友位满了
                if(f[x][1]){cout<<-1;return 0;}//如果x已经有了两个朋友
                f[x][1]=i;
            }
            else f[x][0]=i;
            if(f[y][0]){
                if(f[y][1]){cout<<-1;return 0;}
                f[y][1]=i;
            }
            else f[y][0]=i;
        }
        make(1,1);//在1号位放1号同学,然后往下递归
        if(maxd!=n){cout<<-1;return 0;}//如果造不出长为n的链,那不可能只坐一个圈
        //
        for(int i=1;i<=n;++i)
        dis[(a[i]-i+n)%n]++;
        int ans=0;
        for(int i=0;i<n;++i)
        ans=max(ans,dis[i]);
        //然后还要像硬币翻面一样翻一下乱序手链
        b[1]=a[1];
        for(int i=2;i<=n;++i)
        b[i]=a[n+2-i];
        //翻了之后再跑一次
        memset(dis,0,sizeof(dis));
        for(int i=1;i<=n;++i)
        dis[(b[i]-i+n)%n]++;
        for(int i=0;i<n;++i)
        ans=max(ans,dis[i]);
        //
        cout<<n-ans;
        return 0;
    }


    关于翻面:

    如果乱序链为 1 4 3 2 ,对比 1 2 3 4需要代价2

    但是如果翻一下就不需要代价了,因为读入中只要坐在一起,但我们强行加了他们坐左边还是右边的要求,所以要反过来再跑一遍。

  • 相关阅读:
    变量的解构赋值 (1)对象
    变量的解构赋值 (1)数组
    const 命令
    let 命令
    【BZOJ3295】[Cqoi2011]动态逆序对 cdq分治
    【BZOJ3771】Triple 生成函数+FFT
    【BZOJ4976】宝石镶嵌 DP
    【BZOJ4972】小Q的方格纸 前缀和
    【BZOJ4998】星球联盟 LCT+并查集
    【BZOJ4710】[Jsoi2011]分特产 组合数+容斥
  • 原文地址:https://www.cnblogs.com/qwerta/p/9520016.html
Copyright © 2011-2022 走看看