Codeforces Round #624 (Div. 3)
A - Add Odd or Subtract Even
诚心诚意的签到题。不过做的时候有点启发,也就是(a-b)%2的结果,可以是-1,0,+1。
B - WeirdSort
题意:给一个数组,以及指定其中一些位置是可以进行邻位交换的,可以交换任意多次。求是否能把数组变回有序。
题解:数据量太小,甚至可以检查每个可交换的位置是否需要交换。但是更明显的是一段连续的可邻位交换的位置可以直接sort。注意sort传入的下标的格式。
int a[1005];
int p[1005];
void test_case() {
int n, m;
scanf("%d%d", &n, &m);
for(int i = 1; i <= n; ++i)
scanf("%d", &a[i]);
for(int i = 1; i <= m; ++i)
scanf("%d", &p[i]);
sort(p + 1, p + 1 + m);
for(int l = 1, r = 1; l <= m; l = r + 1, r = l) {
while(r + 1 <= m && p[r + 1] == p[r] + 1)
++r;
sort(a + p[l], a + p[r] + 1 + 1);
}
for(int i = 2; i <= n; ++i) {
if(a[i] < a[i - 1]) {
puts("NO");
return;
}
}
puts("YES");
return;
}
C - Perform the Combo
诚心诚意的签到题。比B还水。
*D - Three Integers
题意:给三个数a,b,c(1<=a,b,c<=1e4),用最少的操作使得a<=b<=c且a是b的因数且b是c的因数,每次操作可以对一个数进行+1或者-1(需要保证结果仍然是正数)。
题解:发现b是对两边进行制约的,假如枚举b的取值,可以立刻算出c的取值,但是a的取值比较难算,要分解因数。但是假如反过来dp,枚举a的取值,然后把a的倍数全部更新,复杂度会显著降低。易知枚举的上限是2e4,因为把三个数变成完全相等,需要的代价最大也是1e4(max减去min的值就是1e4,min和max都往中间的数靠,总共至多移动1e4次,所以b最大变到2e4)。
int dis[20005];
int from[20005];
void test_case() {
int a, b, c;
scanf("%d%d%d", &a, &b, &c);
memset(dis, INF, sizeof(dis));
for(int i = 1; i <= 20000; ++i) {
int d = abs(a - i);
for(int j = i; j <= 20000; j += i) {
if(d < dis[j]) {
dis[j] = d;
from[j] = i;
}
}
}
int ans = INF, ansa, ansb, ansc;
for(int i = 1; i <= 20000; ++i) {
int tmpans = dis[i];
tmpans += abs(b - i);
tmpans += (c <= i) ? (i - c) : min(c % i, i - c % i);
if(ans > tmpans) {
ans = tmpans;
ansa = from[i];
ansb = i;
if(c <= i)
ansc = i;
else if(c % i <= i - c % i)
ansc = c / i * i;
else
ansc = (c / i + 1) * i;
}
}
printf("%d\n", ans);
printf("%d %d %d\n", ansa, ansb, ansc);
}
*E - Construct the Binary Tree
又一条2400的题,是不是准备要起飞了?
题意:构造一棵以1为根的n个节点的二叉树,使得其所有节点的深度和恰好为d。
题解:易知最小深度和为完全二叉树,最大深度和为一条链,判断d不超过最大深度和之后,可以逐步调整。每次调整贪心把深度最大的节点往上调整,若有某个位置刚刚好,就调整到那个位置,否则调整到当前最小深度的空位上。易知这样会产生两个新的比当前最小深度+1的空位。假如已经形成完全二叉树(当前最小深度的空位的深度已经不小于(不是等于)最大深度的节点的深度了)。每个节点至多被调整1次,所以复杂度与n同阶(比官方题解更好)。
stack<int> dep[5005];
int pa[5005];
void test_case() {
int n, d;
scanf("%d%d", &n, &d);
if(d > n * (n - 1) / 2) {
puts("NO");
return;
}
for(int i = 1; i <= n; ++i) {
while(!dep[i].empty())
dep[i].pop();
}
int curd = n * (n - 1) / 2;
for(int i = 2; i <= n; ++i) {
pa[i] = i - 1;
dep[i - 1].push(i - 1);
}
int mind = 1, maxd = n - 1;
while(1) {
if(curd == d) {
puts("YES");
for(int i = 2; i <= n; ++i)
printf("%d%c", pa[i], " \n"[i == n]);
return;
}
if(mind >= maxd) {
puts("NO");
return;
}
if(curd - d <= maxd - mind) {
pa[maxd + 1] = dep[maxd - (curd - d)].top();
puts("YES");
for(int i = 2; i <= n; ++i)
printf("%d%c", pa[i], " \n"[i == n]);
return;
}
pa[maxd + 1] = dep[mind].top();
dep[mind].pop();
dep[mind + 1].push(maxd + 1);
dep[mind + 1].push(maxd + 1);
curd -= maxd - mind;
if(dep[mind].empty())
++mind;
--maxd;
}
}
F - Moving Points
看通过率觉得可能很水,结果还真的很水。
题意:有n个点,第i个点初始在xi,并且有恒定的速度vi,定义两个点之间的最小距离为从0时刻开始的两点的距离的最小值。求所有点对之间的最小距离的和。
题解:把所有点按xi排序,维护两棵平衡树,把xj小于当前的xi的全部放在左树,把xj大于当前的xi的全部放在右树,在左树中找速度<=vi的点的距离和,在右树中找速度>=vi的点的距离和,按xi的顺序扫描,逐个把右树的点搬到左树,即可。上面这个算法恰好把同一个距离算了两倍,其实只需要保留一棵右树,就可以算出来。
struct Treap {
#define ls ch[id][0]
#define rs ch[id][1]
static const int MAXN = 200000;
int ch[MAXN + 5][2], dat[MAXN + 5];
int val[MAXN + 5];
ll val2[MAXN + 5];
ll sumval2[MAXN + 5];
int cnt[MAXN + 5];
int sumcnt[MAXN + 5];
int tot, root;
inline void Init() {
tot = 0;
root = 0;
}
inline int NewNode(int v, ll x) {
int id = ++tot;
ls = rs = 0;
dat[id] = rand();
val[id] = v;
val2[id] = x;
sumval2[id] = x;
cnt[id] = 1;
sumcnt[id] = 1;
return id;
}
inline void PushUp(int id) {
sumval2[id] = sumval2[ls] + sumval2[rs] + val2[id];
sumcnt[id] = sumcnt[ls] + sumcnt[rs] + cnt[id];
}
inline void Rotate(int &id, int d) {
int temp = ch[id][d ^ 1];
ch[id][d ^ 1] = ch[temp][d];
ch[temp][d] = id;
id = temp;
PushUp(ch[id][d]);
PushUp(id);
}
inline void Insert(int &id, int v, ll x) {
if(!id)
id = NewNode(v, x);
else {
if(v == val[id]) {
val2[id] += x;
cnt[id] += 1;
} else {
int d = val[id] > v ? 0 : 1;
Insert(ch[id][d], v, x);
if(dat[id] < dat[ch[id][d]])
Rotate(id, d ^ 1);
}
PushUp(id);
}
}
void Remove(int &id, int v, ll x) {
if(!id)
return;
else {
if(v == val[id]) {
if(val2[id] > x) {
val2[id] -= x;
cnt[id] -= 1;
PushUp(id);
} else if(ls || rs) {
if(!rs || dat[ls] > dat[rs])
Rotate(id, 1), Remove(rs, v, x);
else
Rotate(id, 0), Remove(ls, v, x);
PushUp(id);
} else
id = 0;
} else {
val[id] > v ? Remove(ls, v, x) : Remove(rs, v, x);
PushUp(id);
}
}
}
//查询小于等于v的数的和
ll GetSumValue2LEQ(int id, int v) {
ll res = 0;
while(id) {
if(val[id] > v)
id = ls;
else if(val[id] == v) {
res += sumval2[ls] + val2[id];
break;
} else {
res += sumval2[ls] + val2[id];
id = rs;
}
}
return res;
}
//查询小于等于v的数的数量
int GetSumCntLEQ(int id, int v) {
int res = 0;
while(id) {
if(val[id] > v)
id = ls;
else if(val[id] == v) {
res += sumcnt[ls] + cnt[id];
break;
} else {
res += sumcnt[ls] + cnt[id];
id = rs;
}
}
return res;
}
//查询大于等于v的数的和
ll GetSumValue2GEQ(int id, int v) {
ll res = 0;
while(id) {
if(val[id] > v) {
res += sumval2[rs] + val2[id];
id = ls;
} else if(val[id] == v) {
res += sumval2[rs] + val2[id];
break;
} else
id = rs;
}
return res;
}
//查询大于等于v的数的数量
int GetSumCntGEQ(int id, int v) {
int res = 0;
while(id) {
if(val[id] > v) {
res += sumcnt[rs] + cnt[id];
id = ls;
} else if(val[id] == v) {
res += sumcnt[rs] + cnt[id];
break;
} else
id = rs;
}
return res;
}
#undef ls
#undef rs
}Right;
pii p[200005];
void test_case() {
int n;
scanf("%d", &n);
for(int i = 1; i <= n; ++i)
scanf("%d", &p[i].first);
for(int i = 1; i <= n; ++i)
scanf("%d", &p[i].second);
sort(p + 1, p + 1 + n);
ll ans = 0;
Right.Init();
for(int i = 1; i <= n; ++i)
Right.Insert(Right.root, p[i].second, p[i].first);
for(int i = 1; i <= n; ++i) {
ans += Right.GetSumValue2GEQ(Right.root, p[i].second)
- 1ll * p[i].first * Right.GetSumCntGEQ(Right.root, p[i].second);
Right.Remove(Right.root, p[i].second, p[i].first);
}
printf("%lld\n", ans);
}
看了别人的代码,发现其实可以用树状数组来做,因为只关心大于等于某个v值的所有还在树中的点的x值的和,可以反过来变成一个前缀和。
启示:可以离线排序关键字的算法,不需要使用平衡树,使用树状数组或者线段树即可,当询问的区间为>=x或者<=x这样的形式时,尤其适合树状数组。
struct SegmentTree {
#define ls (o<<1)
#define rs (o<<1|1)
static const int MAXN = 200000;
ll sumx[(MAXN << 2) + 5];
int cnt[(MAXN << 2) + 5];
void PushUp(int o) {
sumx[o] = sumx[ls] + sumx[rs];
cnt[o] = cnt[ls] + cnt[rs];
}
void Build(int o, int l, int r) {
if(l == r) {
sumx[o] = 0;
cnt[o] = 0;
} else {
int m = l + r >> 1;
Build(ls, l, m);
Build(rs, m + 1, r);
PushUp(o);
}
}
void Update1(int o, int l, int r, int p, ll v) {
if(l == r) {
sumx[o] += v;
cnt[o] += 1;
return;
} else {
int m = l + r >> 1;
if(p <= m)
Update1(ls, l, m, p, v);
if(p >= m + 1)
Update1(rs, m + 1, r, p, v);
PushUp(o);
}
}
void Update2(int o, int l, int r, int p, ll v) {
if(l == r) {
sumx[o] -= v;
cnt[o] -= 1;
return;
} else {
int m = l + r >> 1;
if(p <= m)
Update2(ls, l, m, p, v);
if(p >= m + 1)
Update2(rs, m + 1, r, p, v);
PushUp(o);
}
}
ll QuerySumx(int o, int l, int r, int ql, int qr) {
if(ql <= l && r <= qr) {
return sumx[o];
} else {
int m = l + r >> 1;
ll res = 0;
if(ql <= m)
res = res + QuerySumx(ls, l, m, ql, qr);
if(qr >= m + 1)
res = res + QuerySumx(rs, m + 1, r, ql, qr);
return res;
}
}
ll QueryCnt(int o, int l, int r, int ql, int qr) {
if(ql <= l && r <= qr) {
return cnt[o];
} else {
int m = l + r >> 1;
ll res = 0;
if(ql <= m)
res = res + QueryCnt(ls, l, m, ql, qr);
if(qr >= m + 1)
res = res + QueryCnt(rs, m + 1, r, ql, qr);
return res;
}
}
#undef ls
#undef rs
} Right;
pii p[200005];
int v[200005], vtop;
void test_case() {
int n;
scanf("%d", &n);
for(int i = 1; i <= n; ++i)
scanf("%d", &p[i].first);
for(int i = 1; i <= n; ++i) {
scanf("%d", &p[i].second);
v[i] = p[i].second;
}
sort(p + 1, p + 1 + n);
sort(v + 1, v + 1 + n);
vtop = unique(v + 1, v + 1 + n) - (v + 1);
ll ans = 0;
Right.Build(1, 1, vtop);
for(int i = 1; i <= n; ++i) {
int pv = lower_bound(v + 1, v + 1 + vtop, p[i].second) - v;
Right.Update1(1, 1, vtop, pv, p[i].first);
}
for(int i = 1; i <= n; ++i) {
int pv = lower_bound(v + 1, v + 1 + vtop, p[i].second) - v;
ans += Right.QuerySumx(1, 1, vtop, pv, vtop)
- 1ll * p[i].first * Right.QueryCnt(1, 1, vtop, pv, vtop);
Right.Update2(1, 1, vtop, pv, p[i].first);
}
printf("%lld\n", ans);
}
struct BinaryIndexTree {
static const int MAXN = 200000;
int n;
ll bit1[MAXN + 5];
int bit2[MAXN + 5];
void Init(int _n) {
n = _n;
memset(bit1, 0, sizeof(bit1[0]) * (n + 1));
memset(bit2, 0, sizeof(bit2[0]) * (n + 1));
}
void Add(int x, ll v1, int v2) {
for(; x <= n; x += x & -x) {
bit1[x] += v1;
bit2[x] += v2;
}
}
pair<ll, int> Sum(int x) {
ll res1 = 0;
int res2 = 0;
for(; x; x -= x & -x) {
res1 += bit1[x];
res2 += bit2[x];
}
return {res1, res2};
}
} bit;
pii p[200005];
int v[200005], vtop;
void test_case() {
int n;
scanf("%d", &n);
for(int i = 1; i <= n; ++i)
scanf("%d", &p[i].first);
for(int i = 1; i <= n; ++i) {
scanf("%d", &p[i].second);
v[i] = p[i].second;
}
sort(p + 1, p + 1 + n);
sort(v + 1, v + 1 + n);
vtop = unique(v + 1, v + 1 + n) - (v + 1);
ll ans = 0;
bit.Init(vtop);
for(int i = 1; i <= n; ++i) {
int pv = lower_bound(v + 1, v + 1 + vtop, p[i].second) - v;
bit.Add(vtop - pv + 1, p[i].first, 1);
p[i].second = pv;
}
for(int i = 1; i <= n; ++i) {
int pv = p[i].second;
pair<ll, int> res = bit.Sum(vtop - pv + 1);
ans += res.first
- 1ll * p[i].first * res.second;
bit.Add(vtop - pv + 1, -p[i].first, -1);
}
printf("%lld\n", ans);
}
启发:只要排序关键字可以离线,就立刻离散化然后换成树状数组来做。