zoukankan      html  css  js  c++  java
  • SDOI2015 排序【搜索-思考-细节】

    题目链接

    题意简述

    给定一个长度为(2^n)的排列,有(n)种操作,第(i)种操作为:将序列分成(2^{n-i+1})段,每段恰好包含(2^{i-1})个数,然后任选其中两段进行交换。每个操作最多用一次,求有多少操作序列能把序列按照从小到大排序。

    题目解析

    数据范围这么小,可以先考虑考虑爆搜啊。

    但是我没有想出来,看了题解很疑惑为什么都在说操作顺序不影响,结果是我自己把题意理解错了,它所谓的“操作位置不同”是指一个操作在操作序列中的位置不同,即操作顺序不一样,而不是操作时选择的那两段位置不一样。

    好的,操作序列的顺序对答案最后长什么样没有任何影响,因为你可以选任意两段进行交换,无论顺序怎样,你都只需要在操作的时候怼着你最后的那个结果操作就可以了,而这(n)个操作又没有交集,相互独立,没有影响。

    那么只需要确定选哪些操作就可以了,答案要再乘上一个(len!)(len)是操作序列长度

    接下来用搜索确定选哪些操作。从小到大枚举每种操作,对于第(i)种操作,我们把序列分成(2^{n-i})段,每段(2^{i})个数,找到序列中不是连续递增的段。如果这样的段超过两个,那显然救不回来了,因为一种操作只能操作一次,而下一次操作的最小单位就是现在分好的段的大小。

    如果这样的段有(1)个,就交换这个段的前后两半,判断是否合法,如果合法可以继续搜下去,不合法也救不回来了,原因同上。

    如果这样的段有(2)个,就有四种情况可以交换(这里不会再出现自己的前半段和自己的后半段交换的情况了,因为如果自己内部解决的话,别人也无法解决,而且由于是个排列,所以实际上这样的话自己也是无法解决的呢),分别讨论一下。

    当然如果这样的段没有的话,就不用交换,直接往下搜。

    复杂度分析的话,枚举每个操作要不要用似乎是(2^{12})的,但是复杂度大头显然在判断上面,每次判断还要跑一次(2^{12}),姑且认为它是(2^{12} imes 2^{12}=2^{24})吧,但反正是(O(跑得过))


    ►Code View

    #include<cstdio>
    #include<algorithm>
    #include<cstring>
    #include<vector>
    #include<queue>
    using namespace std;
    #define LL long long
    #define N 5000
    #define INF 0x3f3f3f3f
    int rd()
    {
    	int x=0,f=1;char c=getchar();
    	while(c<'0'||c>'9'){if(c=='-')f=-1; c=getchar();}
    	while(c>='0'&&c<='9'){x=(x<<3)+(x<<1)+(c^48); c=getchar();}
    	return f*x;
    }
    int n;
    int pw[20],fac[20],a[N];
    LL ans;
    void Init()
    {
    	pw[0]=1,fac[0]=1;
    	for(int i=1;i<=12;i++)
    		pw[i]=pw[i-1]<<1,fac[i]=fac[i-1]*i;
    }
    bool check(int x,int k)//起点 长度 [x,x+pw[k]-1] 
    {
    	for(int i=1;i<pw[k];i++)
    		if(a[x+i]!=a[x+i-1]+1) return 0;
    	return 1;
    }
    void Swap(int x,int y,int k)
    {
    	int tmp;
    	for(int i=0;i<pw[k];i++)
    		tmp=a[x+i],a[x+i]=a[y+i],a[y+i]=tmp;
    }
    void dfs(int k,int len)//段的长度 2^k 已有的操作序列长度:len
    {
    	if(k==n+1)
    	{
    		ans+=fac[len];
    		return ;
    	}
    	int p1=0,p2=0;//两个不连续递增段的位置
    	for(int i=1;i<=pw[n];i+=pw[k])//段的起点
    		if(!check(i,k))
    		{
    			if(!p1) p1=i;
    			else if(!p2) p2=i;
    			else return ;//超过2个这样的段 无解了 
    		}
    	if(!p1&&!p2) dfs(k+1,len);//没有这样的段
    	else if(p1&&!p2)
    	{//只有一个这样的段 
    		Swap(p1,p1+pw[k-1],k-1);
    		if(check(p1,k)) dfs(k+1,len+1);
    		Swap(p1,p1+pw[k-1],k-1);
    	}
    	else
    	{
    		for(int i=0;i<=1;i++)
    			for(int j=0;j<=1;j++)
    			{
    				Swap(p1+i*pw[k-1],p2+j*pw[k-1],k-1);
    				if(check(p1,k)&&check(p2,k))
    					dfs(k+1,len+1);
    				Swap(p1+i*pw[k-1],p2+j*pw[k-1],k-1);
    			}
    	}
    } 
    int main()
    {
    	Init();
    	n=rd();
    	for(int i=1;i<=pw[n];i++)
    		a[i]=rd();
    	dfs(1,0);
    	printf("%lld
    ",ans); 
    	return 0;
    }
    
  • 相关阅读:
    安卓学习第25课——imageswitcher
    安卓学习第24课——viewSwitcher
    安卓学习第23课——ratingBar
    安卓学习第22课——seekBar
    减治求有重复元素的全排列
    【POJ 1182 食物链】并查集
    【POJ 2823 Sliding Window】 单调队列
    ID3算法 决策树 C++实现
    用BFS和DFS解决圆盘状态搜索问题
    用哈希表实现图书管理系统
  • 原文地址:https://www.cnblogs.com/lyttt/p/14008356.html
Copyright © 2011-2022 走看看