http://codeforces.com/contest/1208
C. Magic Grid
一开始想着直接排,结果20的时候纵向就翻车了。但4个连续自然数一组的思路是没问题的。一个偶然的机会,发现其实相差是纵向4的几个异或起来也是0。所以就是16个连续自然数一组自由构造。
来一个迷惑性极强的构造法:
#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
int tg[5][5];
int g[1005][1005];
void Init() {
int cnt = 0;
for(int i = 1; i <= 4; ++i) {
for(int j = 1; j <= 4; ++j) {
tg[i][j] = cnt++;
}
}
}
void Random() {
int r = rand() % 4 + 1;
for(int i = 1; i <= 4; ++i) {
swap(tg[i][1], tg[i][r]);
}
r = rand() % 4 + 1;
for(int i = 1; i <= 4; ++i) {
swap(tg[i][2], tg[i][r]);
}
r = rand() % 4 + 1;
for(int j = 1; j <= 4; ++j) {
swap(tg[1][j], tg[r][j]);
}
r = rand() % 4 + 1;
for(int j = 1; j <= 4; ++j) {
swap(tg[2][j], tg[r][j]);
}
}
int Draw(int x, int y, int b) {
Random();
for(int i = 1; i <= 4; ++i) {
for(int j = 1; j <= 4; ++j) {
g[x + i - 1][y + j - 1] = tg[i][j] + 16 * b;
}
}
}
int bs[1000 * 1000 / 16 + 5];
int main() {
#ifdef Yinku
freopen("Yinku.in", "r", stdin);
#endif // Yinku
int n;
Init();
while(~scanf("%d", &n)) {
int c = n * n / 16;
for(int i = 1; i <= c; ++i) {
bs[i] = i - 1;
}
random_shuffle(bs + 1, bs + 1 + c);
int cnt = 1;
for(int i = 1; i <= n / 4; ++i) {
for(int j = 1; j <= n / 4; ++j) {
Draw(4 * (i - 1) + 1, 4 * (j - 1) + 1, bs[cnt++]);
}
}
for(int i = 1; i <= n; ++i) {
for(int j = 1; j <= n; ++j) {
printf("%d%c", g[i][j], "
"[j == n]);
}
}
}
}
效果如下图,太有趣liao。
把两个random去掉之后就蛮正常的。
D. Restore Permutation
昨天晚上读了假题,题目不是那个意思。原本以为i是前i个数的顺序对的个数和。那么作差就得到第i个数贡献的顺序对的个数。从右往左通过平衡树一顿操作(顺序对即排名)就可以完成构造。但是这个题并不是这个意思,他的s[i]是i之前的比i小的数字的和。qnickx太强了。搞了个线段树二分。首先确定1的位置,1比任何数都要小,放在哪里都是0,假如有多个0,那一定是最右边的0,因为假如放了左边就一定会破坏掉构造。把1放进去之后考虑把2放进去,这个时候2也是最小的,去除1的影响之后2的值也是0。所以就是需要一个带区间修改的线段树消除某个数造成的影响。
#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
#define lt ls, l, m
#define rt rs, m + 1, r
#define ls (o<<1)
#define rs (o<<1|1)
const ll INF = 1e18;
const int MAXM = 200000 + 5;
ll a[MAXM];
ll st[MAXM * 4], lazy[MAXM * 4];
inline void PushUp(int o) {
st[o] = min(st[ls], st[rs]);
}
inline void PushDown(int o, int l, int r) {
if(lazy[o]) {
lazy[ls] += lazy[o];
lazy[rs] += lazy[o];
st[ls] += lazy[o] ;
st[rs] += lazy[o];
lazy[o] = 0;
}
}
void Build(int o, int l, int r) {
if(l == r) {
st[o] = a[l];
} else {
int m = l + r >> 1;
Build(lt);
Build(rt);
PushUp(o);
}
lazy[o] = 0;
}
void Update(int o, int l, int r, int ql, int qr, ll v) {
if(ql <= l && r <= qr) {
lazy[o] += v;
st[o] += v ;
return;
} else {
PushDown(o, l, r);
int m = l + r >> 1;
if(ql <= m)
Update(lt, ql, qr, v);
if(qr >= m + 1)
Update(rt, ql, qr, v);
PushUp(o);
}
}
int Query(int o, int l, int r, int ql, int qr) {
if(l == r)
return l;
PushDown(o, l, r);
int m = l + r >> 1;
if(st[rs] == 0)
return Query(rt, ql, qr);
else
return Query(lt, ql, qr);
}
int Query2(int o, int l, int r, int x) {
if(l == r)
return st[o];
PushDown(o, l, r);
int m = l + r >> 1;
if(x >= m + 1)
return Query2(rt, x);
else
return Query2(lt, x);
}
int ans[MAXM];
int main() {
#ifdef Yinku
freopen("Yinku.in", "r", stdin);
#endif // Yinku
int n;
while(~scanf("%d", &n)) {
for(int i = 1; i <= n; ++i)
scanf("%lld", &a[i]);
Build(1, 1, n);
for(int i = 1; i <= n; ++i) {
int p = Query(1, 1, n, 1, n);
//消除这个元素对其他元素的影响
Update(1, 1, n, p, n, -i);
//防止下次再找到这个元素
Update(1, 1, n, p, p, INF);
ans[p] = i;
}
for(int i = 1; i <= n; ++i)
printf("%d%c", ans[i], "
"[i == n]);
}
}
从这道题里面突然明白在线段树上二分的原理,假如可以通过区间的信息确定要找的位置/元素在左子区间/右子区间,就可以在线段树上面二分。和主席树里面找第k大的类似。