您的位置:首页 > 其它

uva 11525(单点修改)

2015-08-09 20:38 375 查看
题意:有一个由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
, 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;
}
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: