uva 11525(单点修改)
2015-08-09 20:38
387 查看
题意:有一个由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,所以想到要用线段树来减少时间(被想到),线段树每个结点存的是所有子节点是否被标记的情况。
题解:通过找规律发现结果是可以递推的,比如第三组样例:
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 , 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; }