[Tkey] A decorative fence

还是看看简单而富有美感的爆搜吧
#include<bits/stdc++.h>
using namespace std;
#define int long long
#define tests int cases;cin>>cases;while(cases--)
int n,l;
vector<int> e;bool vis[21];int cnt=0;
void dfs(int p){
	if(cnt==l) return;
	if(p>n){
		cnt++;
		if(cnt==l){
			for(int i:e) cout<<i<<" ";
			cout<<endl;
		}
		return;
	}
	for(int i=1;i<=n;++i){
		if(!vis[i]){
			if(e.size()<=1){
				e.push_back(i);vis[i]=1;
				dfs(p+1);
				e.pop_back();vis[i]=0;
			}
			else if((*(e.end()-1)>*(e.end()-2)&&*(e.end()-1)>i)||
			(*(e.end()-1)<*(e.end()-2)&&*(e.end()-1)<i)){
				e.push_back(i);vis[i]=1;
				dfs(p+1);
				e.pop_back();vis[i]=0;
			}
		}
	}
}
signed main(){
	tests{
		e.clear();cnt=0;
		cin>>n>>l;
		dfs(1);
	}
}

思路

这题爆搜肯定是不行,考虑一个事实:

假设 1xxxx\(Rank=a\) 的一种方案,1yyyy\(Rank=b\) 的另一种方案,而目标 \(Rank\ k\) 满足 \(a\le k\le b\),则有 \(Rank=k\) 的方案的首位一定是 \(1\).

跟我们猜数是一样的. 假设有个数给你猜,\(114\) 小了,\(191\) 大了,那你肯定知道这个数最高位是什么了.

所以我们就开个数组来转移并维护这个 \(Rank\) 值.

注意到 \(Rank\) 并不是非常好维护,我们可以考虑维护每种情况的方案数,然后按字典序从小到大依次加起来,这样就是 \(Rank\) 值了.

\(f[i][j][k]\) 为放入前 \(i\) 块木板构成的栅栏,当第 \(i\) 块木板的 \(Rank=j\) 时的方案数. 注意到这样还是不好维护,因为要考虑是高低高还是低高低,那么再开一维 \(k\) 来表示这个. \(k=1\)\(1\) 为高,反之亦然.

那么这个转移非常好写,也不是本题的难点.

\[\begin{cases}f[i][j][1]=\sum_{1\le k\le j-1} f[i-1][k][0]\\f[i][j][0]=\sum_{j\le k\le i-1} f[i-1][k][1]\end{cases} \]

这里唯一需要注意的是求和的范围. 因为我们这个 \(k\) 指代的是 \(Rank=k\),而且会涉及到选高的还是选低的的问题,也就有了 \(k\) 的范围的差异.

那么还很容易注意到,这个转移和 \(n,m\) 完全没有关系,所以从多测里提出来作为初始化.

然后就是按上面的思想来逼近我们要求的答案.

先来确定第一位吧,我们需要做的就是遍历每个 \(1\le i\le n\),只要有 \(\sum^{i}_{j=1}(f[n][j][0]+f[n][j][1])> m\),就能判定 \(j-1\) 是我们要求的那个第一位.

很显然,当我们之前几位选过某个数字,那我们就不能再选了,因此在之后的几次逼近中,我们还需要判断当前 \(Rank\) 的板子是不是已经被使用过了,然后进行类似的判断即可.

#include<bits/stdc++.h>
using namespace std;
#define int long long
#define speed ios::sync_with_stdio(false);
#define tests int cases;cin>>cases;while(cases--)
#define clear(i) memset((i),0,sizeof (i))
int f[21][21][2];   //now fences have n planks, and the leftest planks ranking j
			        //k=0 means leftest is shorter,else taller
int n,m;
bool vis[21];
/*
f[i][j][1]=sum k{from 1 to j-1} f[i-1][k][0]
f[i][j][0]=sum k{from j to i-1} f[i-1][k][1]
*/
void prework(){
	f[1][1][1]=1;f[1][1][0]=1;
	for(int i=2;i<=20;++i){
		for(int j=1;j<=i;++j){
			for(int k=1;k<=j-1;++k){
				f[i][j][1]+=f[i-1][k][0];
			}
			for(int k=j;k<=i-1;++k){
				f[i][j][0]+=f[i-1][k][1];
			}
		}
	}
}
signed main(){
	prework();
	speed tests{
		cin>>n>>m;
		clear(vis);
		int now,last;
		for(int i=1;i<=n;++i){
			if(f[n][i][1]>=m){
				last=i;now=1;break;
			}
			else{
				m-=f[n][i][1];
			}
			if(f[n][i][0]>=m){
				last=i;now=0;break;
			}
			else{
				m-=f[n][i][0];
			}
		}
		cout<<last<<" ";
		vis[last]=true;
		for(int i=2;i<=n;++i){
			now=1-now;int rank=0;
			for(int len=1;len<=n;++len){
				if(vis[len]) continue;
				rank++;
				if((now==0 and len<last)or(now==1 and len>last)){
					if(f[n-i+1][rank][now]>=m){
						last=len;break;
					}
					else{
						m-=f[n-i+1][rank][now];
					}
				}
			}
			vis[last]=true;
			cout<<last<<" ";
		}
		cout<<endl;
	}
}
posted @ 2024-06-17 15:39  HaneDaniko  阅读(17)  评论(0编辑  收藏  举报