zoukankan      html  css  js  c++  java
  • NOIP 2005 篝火晚会

    题目描述

    佳佳刚进高中,在军训的时候,由于佳佳吃苦耐劳,很快得到了教官的赏识,成为了“小教官”。在军训结束的那天晚上,佳佳被命令组织同学们进行篝火晚会。一共有n个同学,编号从1到n。一开始,同学们按照1,2,……,n的顺序坐成一圈,而实际上每个人都有两个最希望相邻的同学。如何下命令调整同学的次序,形成新的一个圈,使之符合同学们的意愿,成为摆在佳佳面前的一大难题。

    佳佳可向同学们下达命令,每一个命令的形式如下:

    (b1, b2,... bm -1, bm)

    这里m的值是由佳佳决定的,每次命令m的值都可以不同。这个命令的作用是移动编号是b1,b2,…… bm –1,bm的这m个同学的位置。要求b1换到b2的位置上,b2换到b3的位置上,……,要求bm换到b1的位置上。

    执行每个命令都需要一些代价。我们假定如果一个命令要移动m个人的位置,那么这个命令的代价就是m。我们需要佳佳用最少的总代价实现同学们的意愿,你能帮助佳佳吗?

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

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

    题解:

    这个题又很可惜地一个细节没有考虑清楚。大致思路完全正确。

    结果还是只拿了60分 WA了4个点

    一看这个题就是一个类似置换的问题。

    肯定要和图论连边一样考虑的。

    我们首先要有两个环,一个是最终状态的环,一个是初始状态的环(即1~n

    首先判断不合法。

    如果存在一个人喜欢另外一个人,但是TA喜欢的人不喜欢TA,就凉凉了。

    然后就是合法情况了。

    发现,这个命令就是一个置换环,

    最终的所有代价就是所有环的长度。

    但是,每次两个环的对正方式不同,有n种,(对正方式即:一个环不动,另一个环绕着转,每一个位置对应的数对(映射),就是一种对应方式,显然有n种)

    每次连边 dfs还要n,所以就是n^2了。TLE

    考虑优化。

    发现这个连环跑dfs没办法优化 (lll¬ω¬)

    所以要发现更深处的东西。

    发现,一种对正方式下,代价,即总环长,就是位置要动的人的个数。

    证明:每一个位置不同的都要动。

    由于合法,所以,这些位置一定可以连成若干个环。环长就是点数。证毕

    所以,要考虑每种正对方式下的不同位置个数。

    但是直接做还是n^2的。

    考虑优化。

    发现可以优化!( •̀ ω •́ )~

    当我们从一种对正方式到下一种的时候,就是把初始状态的环转一下。

    不妨钦定每次都顺时针转好了。

    我们只需要知道每次正对方式下,有几个点位置不用动,剩下的就是要动的了。

    可以这样做:

    随便找一种对正方式,假设1和1对正,算出初始状态所有的点距离目标位置需要旋转的次数(可能是负数)。

    对于次数,加入一个num[i]表示次数为i的点有多少个。

    统计num[i]最多的是几就好啦!~

    这就表示,我要在这种随便开始的正对方式,旋转i次,就有num[i]个点不用动,n-num[i]就是代价。

    但是次数是负数怎么办??即逆时针转这么多次。

    因为我们钦定每次顺时针转,所以,就(+n)%n好了。

    (这里我写挂了,把负数和正数加了一个fix处理,但是对于一种正对方式,可能一个位置跨过了1到达了目标点,而这个次数我算的是负数,没有加进去。

    即逆时针转a次,即顺时针转n-a次。

    导致答案就偏大了)

    教训:还是要注意顺序,如果开始就明白,钦定顺时针转,就不会算漏了

    代码:

    #include<bits/stdc++.h>
    using namespace std;
    const int N=50000+5;
    const int inf=0x3f3f3f3f;
    int n;
    int le[N],ri[N];
    int with[N][2];
    int d[N];
    int num[2*N];
    bool fl=true;
    int dis[N];
    bool vis[N];
    
    int p[N];
    int ans=inf;
    
    int main()
    {
        scanf("%d",&n);
        int x,y;
        for(int i=1;i<=n;i++){
            scanf("%d%d",&with[i][0],&with[i][1]);
        }
        int st=1;
        while(fl){
            vis[st]=1;
            if(with[with[st][1]][0]!=st&&with[with[st][1]][1]!=st) fl=false;
            if(with[with[st][0]][0]!=st&&with[with[st][0]][1]!=st) fl=false;
            
            if(st==1){
                le[st]=with[st][1];
                ri[with[st][1]]=st;
                
                ri[st]=with[st][0];
                le[with[st][0]]=st;
                
                st=with[st][1];
            }
            else if(st!=1){
                if(!vis[with[st][1]]){
                    le[st]=with[st][1];
                    ri[with[st][1]]=st;
                    st=with[st][1];
                }
                else if(!vis[with[st][0]]){
                    le[st]=with[st][0];
                    ri[with[st][0]]=st;
                    st=with[st][0];
                }
                else break;
            }
        }
        
        if(!fl){
            printf("-1");return 0;
        }
    
        st=1;
        int cnt=0;
        do{
            p[st]=cnt;
            cnt++;
            st=le[st];
        }while(st!=1);
    
        for(int i=1;i<=n;i++){
            dis[i]=p[i]-(i-1);
            num[(dis[i]+n)%n]++;
        }
        
        for(int i=0;i<=2*n;i++){
            ans=min(ans,n-num[i]);
        }
        
        memset(num,0,sizeof num);
        memset(dis,0,sizeof dis); 
        p[1]=n;
        for(int i=1;i<=n;i++){
            dis[i]=p[i]-(n-i+1);
            num[(dis[i]+n)%n]++;
        }
    
        for(int i=0;i<=2*n;i++){
            ans=min(ans,n-num[i]);
        }
        
        printf("%d",ans);
        return 0;
    } 
  • 相关阅读:
    抽奖系统 random()
    JavaScript 稀奇的js语法
    Node初识笔记 1第一周
    vue 自定义指令
    vue 组件 单选切换控制模板 v-bind-is
    学习网址
    vue 组件 子向父亲通信用自定义方法用事件监听
    vue 组件 模板中根数据绑定需要指明路径并通信父
    vue 组件 模板input双向数据数据
    测试样式
  • 原文地址:https://www.cnblogs.com/Miracevin/p/9571720.html
Copyright © 2011-2022 走看看