uva 11525(单点修改)

来源:互联网 发布:linux 进入sql 编辑:程序博客网 时间:2024/04/27 21:15

题意:有一个由1到k组成的序列,最小是1 2 … k,最大是 k k-1 … 1,给出n的计算方式,n = s0 * (k - 1)! + s1 * (k - 2)! +… + sk-1 * 0!,给出s1…sk,输出序列里第n大的序列。
题解:通过找规律发现结果是可以递推的,比如第三组样例:
4
2 1 1 0
那么n = 2 * 3! + 1 * 2! + 1 * 1! + 0 * 0! = 15,通过把1~4的排列全写下来,发现3!是更换第一个数字的间隔,2!是更换第二个数字的间隔,以此类推,那么2*3!明显就是以数字3也就是s0 + 1开头的一个序列,因为已经经过了以1开头和以2开头的两个序列,那么之后的就是以数字2也就是s1 + 1作为第2个数字的序列,照着这个规律可以发现:
1 2 3 4 找到了第s0 + 1个数字,3被使用
1 2 4 找到第s1 + 1个数字,2被使用
1 4 找到第s2 + 1个数字,4被使用
1 找到第s3 + 1个数字,1被使用
所以最终序列就是 3 2 4 1。
然后就是想到如果第s[i] + 1个数字如果未被使用就标记已使用,如果已经被标记就要往后找第一个未被使用的数字标记,可是n有50000,所以想到要用线段树来减少时间(被想到),线段树每个结点存的是所有子节点是否被标记的情况。

#include <cstdio>#include <cstring>#include <algorithm>#include <vector>using namespace std;const int N = 50005;int n, s[N], sum[N << 2];void pushup(int k) {    sum[k] = sum[k * 2] + sum[k * 2 + 1];}void build(int k, int left, int right) {    if (left == right) {        sum[k] = 1;        return;    }    int mid = (left + right) / 2;    build(k * 2, left, mid);    build(k * 2 + 1, mid + 1, right);    pushup(k);}int query(int k, int left, int right, int x) {    if (left == right) {        sum[k] = 0;        return left;    }    int mid = (left + right) / 2, res;    if (sum[k * 2] >= x)        res = query(k * 2, left, mid, x);    else        res = query(k * 2 + 1, mid + 1, right, x - sum[k * 2]);    pushup(k);    return res;}int main() {    int t;    scanf("%d", &t);    while (t--) {        scanf("%d", &n);        build(1, 1, n);        for (int i = 0; i < n; i++)            scanf("%d", &s[i]);        printf("%d", query(1, 1, n, s[0] + 1));        for (int i = 1; i < n; i++)            printf(" %d", query(1, 1, n, s[i] + 1));        printf("\n");    }    return 0;}
0 0