题目链接
题目大意
要你构造一个长为n(n<=1e5)的全排列,使得其最长上升子序列和最长下降子序列的长度分别为x和y,而且要使得这个构造的全排列,字典序最小,如果不能构造则输出NO,否则输出YES,并且输出其全排列
题目思路
这个构造比较神奇吧,以前都没有见到过,现在学习一下,其实他的构造方法简单来说就是分段和翻转
如本来是1 2 3 4 5 6 7 8 9 10的全排列 而你如果要构造一个长度为5的单调递减序列那么你就把最后5个元素翻转变成
1 2 3 4 5 10 9 8 7 6 然后假如你要构造4个最长的单调上升子序列,显然是前面5个找三个出来,因为最后这5个数肯定可以有一个元素在最长上升子序列的最后。你就可以把前面5个数分为三段。然后再将这三段分别翻转,而且你的每一段的长度不能超过5,如果超过5了,那么最长下降子序列就会变长,而且你要使得这些分配中,前面段的长度尽可能小(为了保证字典序最小),那么这个排列就会分为
1…2…3 4 5…6 7 8 9 10这四段,然后分别翻转变成1 2 5 4 3 10 9 8 7 6
我不太会证明这个字典序为何是最小的,但是感觉很像(ac就是证明了
然后如何判断是否成立呢,显然最后y个元素翻转后还剩下n-y个元素,要分为x-1段,而且每一段都必须是大于1而且小于等于y即:
1
<
=
n
−
y
x
−
1
<
=
y
1<=\frac{n-y}{x-1}<=y
1<=x−1n−y<=y
x
+
y
−
1
<
=
n
<
=
x
∗
y
x+y-1<=n<=x*y
x+y−1<=n<=x∗y
然后自己贪心使得前面的段尽可能短,具体看代码实现
代码
#include<set>
#include<map>
#include<queue>
#include<stack>
#include<cmath>
#include<cstdio>
#include<vector>
#include<string>
#include<cstring>
#include<iostream>
#include<algorithm>
#include<unordered_map>
#define fi first
#define se second
#define debug printf(" I am here\n");
using namespace std;
typedef long long ll;
typedef unsigned long long ull;
typedef pair<int,int> pii;
const ll INF=0x3f3f3f3f3f3f3f3f;
const int maxn=2e5+5,inf=0x3f3f3f3f,mod=998244353;
const double eps=1e-10;
int n,x,y,ans[maxn];
ll pre[maxn],len[maxn];
signed main(){
int _;scanf("%d",&_);
while(_--){
scanf("%d%d%d",&n,&x,&y);
if(1ll*x*y>=n&&x+y<=n+1){
printf("YES\n");
ll res=n-y;
for(int i=1;i<=x-1;i++){
len[i]=res-1ll*(x-1-i)*y;
if(len[i]<=0){
len[i]=1;
}
res=res-len[i];
pre[i]=pre[i-1]+len[i];
}
for(int i=1;i<=x-1;i++){
for(int j=pre[i-1]+1;j<=pre[i];j++){
ans[i]=pre[i]-j+pre[i-1]+1;
printf("%d ",ans[i]);
}
}
for(int i=n;i>=n-y+1;i--){
printf("%d%c",i,i==n-y+1?'\n':' ');
}
}else{
printf("NO\n");
}
}
return 0;
}