正题
题目链接:https://jzoj.net/senior/#contest/show/3017/0
题目大意
长度为2n2^n2n的序列,nnn个操作,第iii个可以将序列划分为2i2^i2i段后交换其中两段,每个操作只能用一次,求有多少种操作可以使得序列有小到大。
解题思路
每个操作只能一次操作顺序不会影响结果,所以可以假设从小到大操作,做到第iii个操作时,每个可以被交换的段都必须是已经交换好的。
那么假设现在交换的段长度xxx,那么每段2x2x2x的最多只有两个不按顺序,否则无解,所以我们搜索一下就好了
时间复杂度O(2nn)O(2^nn)O(2nn)
codecodecode
#include<cstdio>
#include<cstring>
#include<algorithm>
#define ll long long
using namespace std;
const ll N=14;
ll n,l,ans,a[1<<N],fac[N];
void dfs(ll dep,ll f){if(dep>n){ans+=fac[f];return;}ll len=1<<dep,h=len/2,z=0,w[4];for(ll i=1;i<=l;i+=len){if(a[i+h]!=a[i]+h) w[++z]=i;if(z>2){return;}}if(!z)dfs(dep+1,f);if(z==1){if(a[w[1]]==a[w[1]+h]+h){for(ll i=w[1];i<w[1]+h;i++)swap(a[i],a[i+h]);dfs(dep+1,f+1);for(ll i=w[1];i<w[1]+h;i++)swap(a[i],a[i+h]);}}if(z==2){if(a[w[1]]+h==a[w[2]]&&a[w[1]+h]+h==a[w[2]+h]){for(ll i=0;i<h;i++)swap(a[w[1]+h+i],a[w[2]+i]);dfs(dep+1,f+1);for(ll i=0;i<h;i++)swap(a[w[1]+h+i],a[w[2]+i]);}if(a[w[2]]+h==a[w[1]+h]&&a[w[1]]+h==a[w[2]+h]){for(ll i=0;i<h;i++)swap(a[w[1]+i],a[w[2]+i]);dfs(dep+1,f+1);for(ll i=0;i<h;i++)swap(a[w[1]+i],a[w[2]+i]);}if(a[w[1]]+h==a[w[2]+h]&&a[w[2]]+h==a[w[1]+h]){for(ll i=0;i<h;i++)swap(a[w[1]+h+i],a[w[2]+h+i]);dfs(dep+1,f+1);for(ll i=0;i<h;i++)swap(a[w[1]+h+i],a[w[2]+h+i]);}if(a[w[2]+h]+h==a[w[1]+h]&&a[w[2]]+h==a[w[1]]){for(ll i=0;i<h;i++)swap(a[w[1]+i],a[w[2]+h+i]);dfs(dep+1,f+1);for(ll i=0;i<h;i++)swap(a[w[1]+i],a[w[2]+h+i]);}}
}
int main()
{scanf("%lld",&n);l=1<<n;fac[0]=1;for(ll i=1;i<=n;i++)fac[i]=fac[i-1]*i;for(ll i=1;i<=l;i++)scanf("%lld",&a[i]);dfs(1,0);printf("%lld",ans);
}