我们知道1——k有K!种排列,现在给定k和n,要你按字典序输出 第n种排列的数列
而且题目给的 n是 n=S1(k-1)!+S2(k-2)!+...+Sk-1*1!+Sk*0!(0=<Si<=k-i),
k最大为50000,直接算出来不可能,我发现这个n的表达式很有意思,明显(k-1)!就是表示第一次除第一个以外,剩下k-1个数的排列个数,乘一个S1就说明之前用了多少次这种排列了,根据这个 再手算了一下,发现就是,每次找第Sk个数,输出,当然已经访问过的数就要T出去
这很明显就是树状数组 (或者线段树的单点修改和查询)功能,先初始化每个都是大小为1,然后每次二分找第Sk个,找到之后,把该位置置为-1,消除该位的影响,继续找,继续输出即可。你看题目里给定的S的范围也就是顺应这个,故意把剩下几个数,S就恰好限定在这个里面
发现自己二分写的还不是很熟,一开始还搞了一下子,是用mid还是L做结果呢 最后在我的写法里被证明是用L做结果
#include <iostream> #include <cstdio> #include <cstring> #include <algorithm> #define N 50000+10 using namespace std; int A[N]; int n; int lowbit(int x) { return x&(-x); } void add(int x,int v) { while (x<=n){ A[x]+=v; x+=lowbit(x); } } void init() { for (int i=1;i<=n;i++){ add(i,1); } } int query(int x) { int ret=0; while (x>0){ ret+=A[x]; x-=lowbit(x); } return ret; } int main() { int t; scanf("%d",&t); while (t--) { scanf("%d",&n); init(); for (int i=1;i<=n;i++){ int a; scanf("%d",&a); int L=1,R=n,mid; while (L<R) { mid=(L+R)>>1; int tmp=query(mid); //if (tmp==a+1) break; if (tmp<a+1) L=mid+1; else R=mid; } //cout<<"@@"<<mid<<endl; add(L,-1); printf("%d",L); if (i<n) putchar(‘ ‘); } puts(""); } return 0; }
UVA_11525 树状数组的活用 二分
时间: 2024-11-08 08:59:31