POJ 1037 Adecorative fence 动态规划
题意,给一个数字J,要求1-J中的数字进行交错排列,即每个数的左右两边的数应比它大或比它小,并且将所有排列进行字典序排序后,查找第C个排列。
步骤一:枚举预处理
首先,交错排列无非就是两种,一种是,第一数比第二个数大,即W型,一种是第一个数比第二个数小,即M型。我们可以定义两个数组,分别记录以i开头,总数为j根的排列的个数。
up[i][j]表示以j开头总数为i根的排列个数,同时第一根比第二根矮,即M型。
down[i][j]表示以j开头总数为i根的排列个数,同事第一根比第二根高,即W型。
很容易的想到对于W型排列,如果想要以j开头并且总根数为i根,那么我们可以把以比j小的数字开头的并且总根数为i-1的M型排列接在j后面如下图。而对于M型略有不同,详情看注释。。
这样就可以得到down[i][j]= ∑(up[i-1][k]) ,k∈[1,j-1];
up[i][j]= ∑(down[i-1][k]) ,k∈[j,i-1];
注:在写题解的过程中,又出现了两个比较纠结的地方,就是当为什么k必须小于i-1,(废话,因为k是属于i-1根,最大只能到i-1啊),另外有一个就是为什么k可以等于j,这个写程序的时候没有发现,但是写题解才注意到,仔细想发现对于一个这样的过程,是把这个j放到了后面,或者中间,例如,对于up[2][1]+=down[1][1] 则是1,2排列把2放在了后面,但是这样还是不对,后来又发现,对于任意一个j<=k的情况都不是单纯地把一个排列接在另一个排列后面,因为j在i-1的情况里已经出现过,所以仔细一想觉得是把i放在了i-1的情况的第一位后也就是第二位,因为在i-1中并没有出现过i,而因为是正向枚举,所以i肯定比i-1中任意一个数都要大因此如果要在原本的W型排列中出现升序,一定是将i放在了第二位这样就能保证它一定比第一位和第三位要大,这样才能成立。
步骤二:查找第c个排列
按开头顺序枚举下来,肯定是按字典序排好的,既然我们用up,和down数组已经求出了各种开头排列方式的数量,那么就可以逐个减去这些开头以求得第c各排列在哪个区间内。
具体我们可以先对于第一个位置进行确定,方法嘛就是对于第一个位置进行枚举j表示当前木板,则对于第一个位置,j∈[1,N],因为是第一个位置所以我们只需要逐个求和Temp+=up[N][j]+down[N][j]并判断Temp是否大于C,如果大于,则当位的j就是我们要找的第一根木板,并且用一个vis数组来记录这个木板是否访问过以避免后面出现重复计算,同时C-=temp。
接着再对第二个位置进行确定,这里于第一根的确定方法不太相同,因为是fence是交错的所以我们需要先确定第二个位置是出于up序列还是down序列,判断后同样用j往下枚举并且每一步都判断处于up还是down序列并且减去相应的排列数以判断是否超过C,判断方法与第一根木板相同。(注:这些都是对于同一位置的枚举,不要误解为一位一位找下去)
接下来就是每一个木板的确认啦,方法嘛与第二步相同。
最坏的时间就是((n+1)*n/2)啦。。一个等差数列。
下面是代码:
#include <iostream>
#include <cstdio>
#include <queue>
#include <cstring>
#include <cstdlib>
#include <queue>
#include <stack>
#include <cmath>
#include <map>
#include <set>
#include <algorithm>
#include <cctype>
#include <vector>
#include <ctime>
#include <list>
#include <deque>
#include <bitset>
#define MEM(a,x) memset(a,x,sizeof(a));
#define MEMINF(a) memset(a,0x3f,sizeof(a));
#define MAX(a,b) (a)>(b)?(a):(b)
#define MIN(a,b) (a)<(b)?(a):(b)
#define FL(s,n,a) fill(s,s+n,a);
#define MAPIT(a,b) map<a,b>::iterator it;
#define VECIT(type) vector<type>::iterator it;
#define SETIT(type) set<type>::iterator it;
using namespace std;
typedef long long ll;
const int MAXN=22;
ll up[MAXN][MAXN],down[MAXN][MAXN],Find[MAXN],C;
int vis[MAXN],T,N;
void init()
{
MEM(up,0);
MEM(down,0);
up[1][1]=down[1][1]=1;
for(int i=2;i<=5;i++)
for(int j=1;j<=i;j++)
{
for(int k=1;k<=j-1;k++)down[i][j]+=up[i-1][k];
for(int k=j;k<=i-1;k++)up[i][j]+=down[i-1][k];
}
}
void findc()
{
int cnt;
MEM(vis,false);
MEM(Find,0);
ll l=0,temp;
for(int i=1;i<=N;i++)
{
int j;
cnt=0;
for(j=1;j<=N;j++)
{
temp=C;
/**记录下当前C的大小,每次循环减去排列数后都不一样,当找
到当前需要的木板后,C是多减了一组排列数的,所以下面需要一个回到
上一个状态的过程,**/
if(!vis[j])
{
cnt++;//把j当做剩下木棒里的第cnt短。
if(i==1)
C-=(up[N][cnt]+down[N][cnt]);
else if(j>=Find[i-1]&&(i==2||Find[i-2]>Find[i-1]))
C-=down[N-i+1][cnt];
else if(j<Find[i-1]&&(i==2||Find[i-2]<Find[i-1]))
C-=up[N-i+1][cnt];
if(C<=0) break;
}
}
vis[j]=true;//记录j已经访问,减少重复计算。
Find[i]=j;//确定当前位置是j木板
C=temp;
}
printf("%d",Find[1]);
for(int i=2;i<=N;i++) printf(" %d",Find[i]);
puts("");
}
int main()
{
init();
cin>>T;
while(T--){
scanf("%d %I64d",&N,&C);
findc();
}
}