【BZOJ3990】排序(SDOI2015)-DFS+贪心
测试地址:排序
做法:首先我们知道,如果存在一个长度为k的可行操作序列(就是用了k个操作使原数列排好序),将它操作的顺序随意变换都是一个可行的序列,那么其对答案的贡献为k!。于是我们从小到大搜索操作,当搜索到第i种操作时,表示我们已经处理完前i-1种操作了。将原数列分成2^i个元素一块,判断每个块是否合法,一个块合法当且仅当块中的元素连续且递增。可以证明,操作做在合法的块中是没有意义的,所以我们分析,如果只有一个块不合法,那么则将这一块的左半部分和右半部分互换,再检查这个块是否变得合法,如果合法则继续搜索。如果有两个块不合法,那么尝试调换第一块的左右部分的其中一个和第二块的左右部分的其中一个,分为4种情况,对于每种情况再检查两个块是否变得合法,如果合法则继续搜索。如果有三个以上的块不合法则无解。最后在细节上再多注意即可。
以下是本人代码:
#include <cstdio>
#include <cstdlib>
#include <iostream>
#include <algorithm>
using namespace std;
int num[5010],fac[20],ans=0;
int n;
bool check(int s,int pos)
{
s=((s-1)<<pos)+1;
int last=num[s];
for(int i=2;i<=(1<<pos);i++)
{
if (num[s+i-1]!=last+1) return 0;
else last=num[s+i-1];
}
return 1;
}
void Swap(int s1,int s2,int pos)
{
for(int i=1;i<=(1<<pos);i++)
swap(num[s1+i-1],num[s2+i-1]);
}
void dfs(int pos,int k)
{
if (pos==n+1) {ans+=fac[k];return;}
int p1=0,p2=0;
for(int i=1;i<=(1<<(n-pos));i++)
{
if (!check(i,pos))
{
if (!p1) p1=i;
else if (!p2) p2=i;
else return;
}
}
if (!p1&&!p2) dfs(pos+1,k);
else if (p1&&!p2)
{
Swap(((p1-1)<<pos)+1,((p1-1)<<pos)+(1<<(pos-1))+1,pos-1);
if (check(p1,pos)) dfs(pos+1,k+1);
Swap(((p1-1)<<pos)+1,((p1-1)<<pos)+(1<<(pos-1))+1,pos-1);
}
else
{
for(int a=0;a<=1;a++)
for(int b=0;b<=1;b++)
{
int s1,s2;
if (!a) s1=((p1-1)<<pos)+1;
else s1=((p1-1)<<pos)+(1<<(pos-1))+1;
if (!b) s2=((p2-1)<<pos)+1;
else s2=((p2-1)<<pos)+(1<<(pos-1))+1;
Swap(s1,s2,pos-1);
if (check(p1,pos)&&check(p2,pos))
dfs(pos+1,k+1);
Swap(s1,s2,pos-1);
}
}
}
int main()
{
scanf("%d",&n);
fac[0]=1;
for(int i=1;i<=n;i++) fac[i]=fac[i-1]*i;
for(int i=1;i<=(1<<n);i++)
scanf("%d",&num[i]);
dfs(1,0);
printf("%d",ans);
return 0;
}