zoukankan      html  css  js  c++  java
  • ural 1568 Train Car Sorting 题解

    题目链接

    题目大意:给 (n) 个数的排列(互不相同),一次操作可以选择一个子序列,将子序列按照原来顺序移动至最前,其余元素按照原来顺序放在最后。问最小操作次数和一种可行的操作方案

    input:
    6
    6 5 2 4 1 3
    
    output:
    3
    6 5 2 4 1 3
    6 4 1 5 2 3
    6 1 2 3 4 5
    1 2 3 4 5 6
    

    一开始看的是 CTSC 2008 曹钦翔《数据结构的提炼与压缩》这篇论文。结果按照他的算法写,一直wa,花了不少时间,太气人了(也可能是我的问题因为我没看论文里的证明)。最后在discuss里才看到正解

    网上似乎没什么题解,那就我来写一个吧

    算法如下:

    1. 若序列已经递增,停止算法
    2. 把序列分割成尽可能少的递增子序列,满足子序列内相邻两个数都相差 1。比如 ([6,5,2,4,1,3]) 的分割为 ([6][5][2,3][4][1])
    3. 将子序列排序(以第一个元素为关键字)。我们把它称为原序列的有序分割。比如 ([6][5][2,3][4][1]) 排序后为 ([1][2,3][4][5][6])
    4. 将位置在奇数的子序列合并在一起,记为集合 (A),位置在偶数的子序列合并在一起,记为集合 (B)。比如 ([1][2,3][4][5][6]) 中奇数位的 ([1][4][6]) 合并为 (A={1,4,6}),偶数位是 ([2,3][5]) 合并为 (B={2,3,5})
    5. 回到原序列,把在集合 (A) 中的元素看作操作里的子序列,进行一次操作,回到第 1 步。比如 ([6,5,2,4,1,3]) 中操作子序列 ([6,4,1]) 变为 ([6,4,1,5,2,3]),接下来操作 ([6,1,2,3]) 变为 ([6,1,2,3,4,5]),接下来操作 ([1,2,3,4,5]) 变为 ([1,2,3,4,5,6]),停止算法

    上述算法每一步操作是 (O(nlog n)) 的,足以过这题。当然事实上,先记录一下 (pos[a[i]]=i),即 (i)(a[1..n]) 中的位置。如果 (pos[i]>pos[i+1]) 表示数字 (i) 在数字 (i+1) 的后面,我们就在 (i)(i+1) 中间断开。这样就直接得到第 3 步得到的有序分割,可以 (O(n)) 处理每一步操作

    又由于操作数为 (O(log n)),总复杂度 (O(nlog n))

    那么为什么这么做是正确的呢?或者说怎么证明操作数 (O(log n))?感性地来说,就是第 3 步得到的有序分割(比如 ([1][2,3][4][5][6])),因为所有奇数位将会排到偶数位之前,所以可以看作奇数位和它后面相邻的子序列进行了合并。比如 ([1]) 一定排到 ([2,3]) 之前,那么在下次操作后 ([1,2,3]) 一定是作为一个子序列出现的,([4])([5]) 同理。这样,有序分割的大小就会小一半。这个操作看似是归并排序反过来的操作,但是本质和归并排序是一样的思想,真是神奇

    #include <bits/stdc++.h>
    using namespace std;
    #define repeat(i,a,b) for(int i=(a),_=(b);i<_;i++)
    #define repeat_back(i,a,b) for(int i=(b)-1,_=(a);i>=_;i--)
    const int N=200010; typedef long long ll; ll read(){ll x; if(scanf("%lld",&x)!=1)exit(0); return x;}
    int n,a[N],b[N],f[N],A[N],pos[N];
    // f[i]标记了a[i]是有序分割中第几个子序列的元素,pos含义见题解
    void print(){
    	repeat(i,1,n+1)
    		printf("%d%c",a[i]," 
    "[i==n]);
    }
    int getcnt(){ // 得到有序分割的大小
    	repeat(i,1,n+1)pos[a[i]]=i;
    	int cnt=1;
    	repeat(i,1,n+1){
    		if(i>1 && pos[i-1]>pos[i])cnt++;
    		f[pos[i]]=cnt;
    	}
    	return cnt;
    }
    void work(){ // 进行一次操作
    	getcnt();
    	int t=0;
    	repeat(i,1,n+1)if(f[i]%2==1)b[++t]=a[i];
    	repeat(i,1,n+1)if(f[i]%2==0)b[++t]=a[i];
    	copy(b+1,b+n+1,a+1);
    }
    void Solve(){
    	n=read();
    	repeat(i,1,n+1){
    		A[i]=a[i]=read();
    	}
    	int ans=0;
    	while(getcnt()!=1){
    		work();
    		ans++;
    	}
    	copy(A+1,A+n+1,a+1);
    	printf("%d
    ",ans);
    	print();
    	repeat(i,0,ans){
    		work();
    		print();
    	}
    }
    signed main(){
    	//freopen("data.txt","r",stdin);
    	int T=1; //T=read();
    	repeat(ca,1,T+1){
    		Solve();
    	}
    	return 0;
    }
    
  • 相关阅读:
    Vue里用moment.js
    回到顶部|回到底部功能的实现(Vue)
    Vue绑定下拉框型的树
    Excel导入数据库前后端代码
    Windows应用程序开发笔记-控制和获取其他程序窗口控件内容
    SQL Server查询、添加、修改表和字段的备注描述
    windows 共享文件夹,和共享打印机
    Visual Studio 2015安装过程卡住,解决办法
    python绘图 初识Python绘图
    GCC、LLVM、Clang
  • 原文地址:https://www.cnblogs.com/axiomofchoice/p/14263859.html
Copyright © 2011-2022 走看看