[BZOJ3990] [SDOI2015] 排序
A. 排序
题目描述
小A有一个$1-2^N$的排列$A[1..2^N]$,他希望将A数组从小到大排序,小A可以执行的操作有N种,每种操作最多可以执行一次,对于所有的$i(1<=i<=N)$,第$i$种操作为将序列从左到右划分为$2^{N-i+1}$段,每段恰好包括$2^{i-1}$个数,然后整体交换其中两段.
小A想知道可以将数组$A$从小到大排序的不同的操作序列有多少个,小$A$认为两个操作序列不同,当且仅当操作个数不同,或者至少一个操作不同(种类不同或者操作位置不同).
下面是一个操作事例: $N=3,A[1..8]=[3,6,1,2,7,8,5,4]$.
第一次操作,执行第$3$种操作,交换$A[1..4]$和$A[5..8]$,交换后的$A[1..8]$为$[7,8,5,4,3,6,1,2]$.
第二次操作,执行第$1$种操作,交换$A[3]$和$A[5]$,交换后的$A[1..8]$为$[7,8,3,4,5,6,1,2]$.
第三次操作,执行第$2$中操作,交换$A[1..2]$和$A[7..8]$,交换后的$A[1..8]$为$[1,2,3,4,5,6,7,8]$.
输入格式
第一行,一个整数$N$
第二行,$2^N$个整数,$A[1..2^N]$
输出格式
一行,一个整数,表示可以将数组$A$从小到大排序的不同的操作序列的个数。
样例
样例输入
3
7 8 5 6 1 2 4 3
样例输出
6
数据范围与提示
对于$30\%$的数据,$1<=N<=4$; 对于全部的数据,$1<=N<=12$。
题解
这个题考试的时候一开始想到了要用一颗树来做,然而并没有考虑全。
打了一个$dp$,结果最后发现树上的$dp$是错的,因为每层交换使之成立的情况有好几种。
考试错解($WA 15$)
#include<iostream>
#include<cstring>
#include<cstdio>
#include<cmath>
#define Reg register
using namespace std;
int n,jpp,jaa,jjj,anp[10050],pos[1<<13];
struct Tree {int fat,judge,num,ans;} tree[1000050];
void build(int k,int l,int r,int fat)
{
tree[k].fat=fat;
if(l==r)
{
scanf("%d",&tree[k].num);
pos[tree[k].num]=k;
return;
}
int mid=(l+r)/2;
if(l<=mid) build(k*2,l,mid,k);
if(mid+1<=r) build(k*2+1,mid+1,r,k);
tree[k].num=max(tree[k*2].num,tree[k*2+1].num);
return;
}
void dp(int k,int l,int r)
{
if(l==r) return;
int mid=(l+r)/2;
if(l<=mid) dp(k*2,l,mid);
if(mid+1<=r) dp(k*2+1,mid+1,r);
if(r-l+1==2)
{
if(abs(tree[k*2].num-tree[k*2+1].num)==1)
{
if(tree[k*2].num>tree[k*2+1].num)
{
if(jaa) { jpp=1; return; }
tree[k].ans=1;
}
return;
}
else
{
if(jaa==1) { jpp=1; return; }
int ok=0;
if(tree[k*2+1].num>tree[k*2].num)
{
ok=1;
swap(tree[k*2+1].num,tree[k*2].num);
}
int p=pos[tree[k*2].num-1],judgg=0;
if(tree[tree[p].fat*2].num==tree[k*2].num-1) //右儿子
{
if(abs(tree[tree[p].fat*2+1].num-tree[k*2+1].num)==1)
{
judgg=1;
swap(tree[tree[p].fat*2].num,tree[k*2+1].num);
tree[tree[p].fat].num=max(tree[tree[p].fat*2].num,tree[tree[p].fat*2+1].num);
tree[k].num=max(tree[k*2].num,tree[k*2+1].num);
}
}
else
{
if(abs(tree[tree[p].fat*2].num-tree[k*2+1].num)==1)
{
judgg=1;
swap(tree[tree[p].fat*2+1].num,tree[k*2+1].num);
tree[tree[p].fat].num=max(tree[tree[p].fat*2].num,tree[tree[p].fat*2+1].num);
tree[k].num=max(tree[k*2].num,tree[k*2+1].num);
}
}
if(!judgg)
{
p=pos[tree[k*2+1].num+1];
if(tree[tree[p].fat*2].num==tree[k*2+1].num+1) //左儿子
{
if(abs(tree[tree[p].fat*2+1].num-tree[k*2].num)==1)
{
judgg=1;
swap(tree[tree[p].fat*2].num,tree[k*2].num);
tree[tree[p].fat].num=max(tree[tree[p].fat*2].num,tree[tree[p].fat*2+1].num);
tree[k].num=max(tree[k*2].num,tree[k*2+1].num);
}
}
else
{
if(abs(tree[tree[p].fat*2].num-tree[k*2].num)==1)
{
judgg=1;
swap(tree[tree[p].fat*2+1].num,tree[k*2].num);
tree[tree[p].fat].num=max(tree[tree[p].fat*2].num,tree[tree[p].fat*2+1].num);
tree[k].num=max(tree[k*2].num,tree[k*2+1].num);
}
}
}
if(!judgg) jpp=1;
if(ok) swap(tree[k*2+1].num,tree[k*2].num);
if(judgg)
{
jaa=1;
if(tree[k*2].num>tree[k*2+1].num)
{
if(jaa) { jpp=1; return; }
tree[k].ans=1;
}
return;
}
}
}
else
{
if(tree[k*2].num<tree[k*2+1].num)
{
if(tree[k*2].judge&&tree[k*2+1].judge) jpp=1;
else tree[k].ans=tree[k*2].ans+tree[k*2+1].ans;
}
else
{
if(tree[k*2].judge&&tree[k*2+1].judge) jpp=1;
else
{
tree[k].ans=tree[k*2].ans+tree[k*2+1].ans+1;
tree[k].judge=1;
}
}
}
return;
}
void cheng(int x)
{
int y=0;
for(Reg int i=1;i<=anp[0];++i)
{
anp[i]*=x;
anp[i]+=y;
y=anp[i]/10;
anp[i]%=10;
}
while(y)
{
anp[++anp[0]]=y%10;
y/=10;
}
return;
}
void dfs(int k,int l,int r)
{
if(l==r) cout<<tree[k].num<<' ';
else
{
int mid=(l+r)/2;
if(l<=mid) dfs(k*2,l,mid);
if(mid+1<=r) dfs(k*2+1,mid+1,r);
}
return;
}
int main()
{
scanf("%d",&n);
build(1,1,1<<n,0);
dp(1,1,1<<n);
if(jpp) printf("0");
else
{
anp[0]=1,anp[1]=1;
for(Reg int i=2;i<=tree[1].ans+jaa;++i) cheng(i);
for(Reg int i=anp[0];i>=1;--i) printf("%d",anp[i]);
}
return 0;
}
考试暴力($TLE 30$)
#include<iostream>
#include<cstring>
#include<cstdio>
#define Reg register
using namespace std;
int ans,n,num[(1<<12)+1];
void dfs(int state)
{
bool k=1;
for(Reg int i=1;i<=(1<<n);++i)
if(num[i]!=i) { k=0; break; }
if(k==1) ++ans;
else
{
for(Reg int i=1;i<=n;++i)
{
if((state&(1<<(i-1)))) continue;
for(Reg int j=1;j<=(1<<i);++j)
{
for(Reg int k=j+1;k<=(1<<i);++k)
{
for(Reg int p=1;p<=(1<<(n-i));++p)
{
swap(num[(1<<(n-i))*(k-1)+p],num[(1<<(n-i))*(j-1)+p]);
}
dfs((state|(1<<(i-1))));
for(Reg int p=1;p<=(1<<(n-i));++p)
{
swap(num[(1<<(n-i))*(k-1)+p],num[(1<<(n-i))*(j-1)+p]);
}
}
}
}
}
return;
}
int main()
{
scanf("%d",&n);
for(Reg int i=1;i<=(1<<n);++i) scanf("%d",&num[i]);
dfs(0);
printf("%d",ans);
return 0;
}
/*
11 2 3 7 4 10 1 13 5 9 14 16 6 15 8 12
5 6 7 8 1 3 2 4
*/
正解($AC$)
#include<iostream>
#include<cstring>
#include<cstdio>
#include<cmath>
#include<vector>
#define Reg register
using namespace std;
int vis[100050],vpp[(1<<12)+10];
int n,tot,num[(1<<12)+10],st[150],ed[150];
struct Tree {int lch,rch,fat,num,dep;} tree[1000050];
long long app;
void build(int k,int l,int r,int deep,int fat)
{
tree[k].fat=fat;
tree[k].dep=deep;
st[deep]=min(st[deep],k);
ed[deep]=max(ed[deep],k);
if(l==r)
{
scanf("%d",&tree[k].num);
return;
}
int mid=(l+r)/2;
if(l<=mid)
{
tree[k].lch=k*2;
build(k*2,l,mid,deep+1,k);
}
if(mid+1<=r)
{
tree[k].rch=k*2+1;
build(k*2+1,mid+1,r,deep+1,k);
}
return;
}
bool judge(int x,int deep)
{
if(abs(tree[tree[x].lch].num-tree[tree[x].rch].num)>(1<<(n-deep-1))) return 0;
if(tree[tree[x].lch].num>tree[tree[x].rch].num) return 0;
return 1;
}
void update(int deep)
{
for(Reg int i=st[deep];i<=ed[deep];++i)
tree[i].num=max(tree[tree[i].lch].num,tree[tree[i].rch].num);
return;
}
void swap(int x,int y)
{
int m=tree[y].num,n=tree[x].num;
tree[x].num=m,tree[y].num=n;
return;
}
void dfs(int ans,int deep,int state)
{
if(deep==-1)
{
if(vpp[state]) return;
vpp[state]=1;
long long lss=1;
for(Reg int i=2;i<=ans;++i) lss*=i;
app+=lss;
return;
}
vis[0]=0;
for(Reg int i=st[deep];i<=ed[deep];++i)
{
if(abs(tree[tree[i].lch].num-tree[tree[i].rch].num)>(1<<(n-deep-1))) vis[++vis[0]]=i;
else if(tree[tree[i].lch].num>tree[tree[i].rch].num) vis[++vis[0]]=i;
}
if(vis[0]>=3) return;
else if(vis[0]==2)
{
int p1=vis[1],p2=vis[2];
swap(tree[p1].lch,tree[p2].lch);
if(judge(p1,deep)&&judge(p2,deep))
{
update(deep);
dfs(ans+1,deep-1,state|(1<<deep));
}
swap(tree[p1].lch,tree[p2].lch); //左儿子换左儿子
update(deep);
swap(tree[p1].rch,tree[p2].lch);
if(judge(p1,deep)&&judge(p2,deep))
{
update(deep);
dfs(ans+1,deep-1,state|(1<<deep));
}
swap(tree[p1].rch,tree[p2].lch); //右儿子换左儿子
update(deep);
swap(tree[p1].lch,tree[p2].rch);
if(judge(p1,deep)&&judge(p2,deep))
{
update(deep);
dfs(ans+1,deep-1,state|(1<<deep));
}
swap(tree[p1].lch,tree[p2].rch); //左儿子换右儿子
update(deep);
swap(tree[p1].rch,tree[p2].rch);
if(judge(p1,deep)&&judge(p2,deep))
{
update(deep);
dfs(ans+1,deep-1,state|(1<<deep));
}
swap(tree[p1].rch,tree[p2].rch); //右儿子换右儿子
update(deep);
}
else if(vis[0]==1)
{
int p=vis[1];
if(abs(tree[tree[p].lch].num-tree[tree[p].rch].num)>(1<<(n-deep))) return;
swap(tree[p].lch,tree[p].rch);
update(deep);
dfs(ans+1,deep-1,state|(1<<deep));
swap(tree[p].lch,tree[p].rch);
update(deep);
}
else if(vis[0]==0)
{
update(deep);
dfs(ans,deep-1,state);
}
return;
}
int main()
{
scanf("%d",&n);
memset(st,0x7f,sizeof(st));
memset(ed,0,sizeof(ed));
build(1,1,1<<n,0,0);
dfs(0,n-1,0);
printf("%lld",app);
return 0;
}
其实这个题思路很简单,因为总共有2的整数次幂倍,所以很容易想到完全二叉树,
如果要使最后的序列合法,那么肯定每一层都要合法。
看样例: $7 8 5 6 1 2 4 3$
我们发现假如两个数一组,那么会分成${7 8}$,${5 6}$,${1 2}$,${4 3}$。
那么肯定要让$4$、$3$互换,${7 8}$,${5 6}$,${1 2}$,${3 4}$。
再让4个数一组,${{7 8} {5 6}},{{1 2} {3 4}}$,肯定要让${7 8}$、${5 6}$互换。
变成$ {{5 6} {7 8}}$,${{1 2} {3 4}}$。
最后肯定在让最后两个区间互换。
如果我们找到一种合法的方案,那么换的顺序可以随意,也就是方案数为它的阶乘。
然后考虑每一层,
可以分4种情况:
1、不合法的数的组数有$>2$个,那么不可能通过交换$1$次使其合法。
2、不合法的数的组数有$2$个,那么我们让他们两两交换,也就是 左儿子交换右儿子、左儿子交换左儿子......看它合不合法。
这可能会有好几种合法的情况,比如:$1 4 3 2$
既可以变成$ 1 2 3 4$,也可以变成$3 4 1 2$。
3、不合法的数的组数有$1$个,那么考虑,
一个例子:$ 1 4 2 3$,那么这种情况不可能一次交换合法。
所以要判断一下这两个数的差。
4、没有不合法的数,那么就不需交换。
这里的不合法有两种情况,一种是左儿子大于右儿子,另一种是两儿子的差大于$1<<(n-deep-1)$。
我们要上传区间最大值。
具体实现看代码。