D.Three Integers
给3个数(a <= b <= c<=10000),每次操作对一个数+1或者-1,可以加(减)任意次。记经过(t)次操作后,使得(A <= B <= C且B\% A = 0, C \% B = 0),求最少次数(t)?
题解
因为才(1e^4),所以暴力枚举(A),和(A)的倍数,记(B = k * A),那么(C = B * (C / B) or C = B * (C / B) + B),复杂度(O(Nlog(N)))。注意(A)的范围可以限定在: (0 < A <= 2*a),因为当(A>2*a)时,(A - a > a - 1),任何数都能除尽1
int cas;
cin >> cas;
while(cas--) {
re[0] = re[1] = re[2] = 0;
cin >> a >> b >> c;
int ans = INF, tp = 0;
for (int ca = 1; ca <= 2 * a; ++ca) {
for (int cb = ca; cb <= 2 * b; cb += ca) {
for (int i = 0; i < 2; ++i) {
tp = abs(a - ca) + abs(b - cb) + abs(cb * (c / cb) + i * cb - c);
if (tp < ans) {
ans = tp;
re[0] = ca, re[1] = cb, re[2] = cb * (c / cb) + i * cb;
}
}
}
}
cout << ans << endl;
cout << re[0] << " " << re[1] << " " << re[2] << endl;
}
return 0;
E.Construct the Binary Tree
构造结点个数为(n)且深度之和为(d)的二叉树
题解
贪心:先构成一条链,然后每次从尾部取一个节点移到合适的位置(先把这个点拿去构造类似完全二叉树这样的东西,如果拿上去之后,深度之和小于(d),那么就让它成为这条链的某个节点的子节点),直到深度之和为(d)。
想不出来怎么实现,于是找了一个实现简单(但是wrong answer)的代码:https://www.cnblogs.com/Dont-Starve/p/12449186.html
原代码没有考虑这个点不能移到顶部构成类似完全二叉树的东西的情况,所以要修改一下
const int MAXN = 5005;
int num[MAXN], tree[MAXN][MAXN], ans[MAXN]; // 记录每层有几个节点,记录这棵树,记录答案
bool check(int n, int d) {
int sum = n * (n - 1) / 2 - d;
if (sum < 0) return false;
int cur = 2, tp; // cur记录当前插入的层数
for (int i = n; i >= cur && sum > 0; i--) {
tp = i - cur;
if (tp > sum) { // 把它作为这条链上某个节点的子节点
ans[i] = i - sum - 1;
sum = 0;
}
else {
sum -= tp;
num[cur]++;
ans[i] = tree[cur - 1][(num[cur] + 1) >> 1];
tree[cur][num[cur]] = i;
if (1 << (cur - 1) == num[cur]) cur++;
}
}
if (sum == 0) return true;
else return false;
}
void Solve() {
int n, d;
cin >> n >> d;
for (int i = 1; i <= n; ++i) {
num[i] = 1;
tree[i][1] = i;
ans[i] = i - 1;
}
if (check(n, d)) {
puts("YES");
for (int i = 2; i <= n; ++i) cout << ans[i] << " ";
cout << endl;
}
else {
puts("NO");
}
}
int main() {
int ca;
cin >> ca;
while(ca--) Solve();
return 0;
}
F. Moving Points
(x)轴上有(n)个点,每个点的初始位置为(x_i),并以速度(v_i)(可以是负数)移动,所以(t)时刻的位置为(y_i = x_i + t * v_i(t = 0,y=x_i))。定义(d(i,j):=abs(min(y_i - y_j))),求$$sum_{1leq i < j leq n} d(i, j)$$
题解
将(n)个点按(x)进行排序后,发现(x_i < x_j)时,如果(v_i leq v_j,d(i, j) = x_j - x_i);(v_i > v_j,d(i,j) = 0)。所以只需要统计每个点的左边有几个点的速度小于或者等于它:
cnt = 0;
for i = 1 to j:
if vi <= vj: cnt++, ans += xj - xi;
整理一下:(ans_j = cnt * x_j - sum_{v_i leq v_j}x_i)
怎么高效的求(cnt)和(sum) ?将(v)离散化,然后用树状数组求区间和即可。因为(x)已排序,所以(x > x_j)的那些点(此时才遍历到(x_j))不会影响(ans_j)。
int n, m;
int maps[MAXN];
long long sum[MAXN][2];
pair<long long, long long> node[MAXN];
inline long long lowbit(long long x) {
return x&(-x);
}
void add(long long id, long long x) {
while(id <= m) {
sum[id][0]++;
sum[id][1] += x;
id += lowbit(id);
}
}
long long query(int id, int op) {
long long ans = 0;
while(id > 0) {
ans += sum[id][op];
id -= lowbit(id);
}
return ans;
}
void Solve() {
sort(node + 1, node + n + 1);
sort(maps + 1, maps + n + 1);
m = unique(maps + 1, maps + n + 1) - (maps + 1); // [ )
long long ans = 0, id = -1;
for (int i = 1; i <= n; ++i) {
id = lower_bound(maps + 1, maps + m + 1, node[i].second) - maps;
ans += node[i].first * query(id, 0) - query(id, 1);
add(id, node[i].first);
}
cout << ans << endl;
}
int main() {
cin >> n;
for (int i = 1; i <= n; ++i) cin >> node[i].first;
for (int i = 1; i <= n; ++i) {
cin >> node[i].second;
maps[i] = node[i].second;
}
Solve();
return 0;
}