7 ABC422 D~F 题解
ABC422 D~F 题解
D-AtCoder AAC Contest
题面
设 \(N\) 为正整数。将长度为 \(2^N\) 的非负整数序列 \(A=(A_1, A_2, \dots, A_{2^N})\) 的不平衡值定义为通过以下运算得到的非负整数值:
- 最初,设置 \(X=0\) 。
- 执行以下一系列操作 \(N\) 次:
- 将 \(X\) 更新为 \(\max(X, \max(A) - \min(A))\) ,其中 \(\max(A)\) 和 \(\min(A)\) 分别表示序列 \(A\) 的最大值和最小值。
- 将开头的元素两两配对,并排列它们的和,形成一个长度为开头一半的新序列。即设置 \(A \gets (A_1 + A_2, A_3 + A_4, \dots, A_{\vert A \vert - 1} + A_{\vert A \vert})\) 。
- 最后的值 \(X\) 就是不平衡值。
例如,当 \(N=2, A=(6, 8, 3, 5)\) 时,通过以下步骤,不平衡值为 \(6\) :
- 初始值为 \(X=0\) 。
- 第一轮操作如下
- 将 \(X\) 更新为 \(\max(X, \max(A) - \min(A)) = \max(0, 8 - 3) = 5\) 。
- 将 \(A\) 设为 \((6+8, 3+5) = (14, 8)\) 。
- 第二步操作如下
- 将 \(X\) 更新为 \(\max(X, \max(A) - \min(A)) = \max(5, 14 - 8) = 6\) 。
- 将 \(A\) 设置为 \((14 + 8) = (22)\) 。
- 最后是 \(X=6\) 。
给定一个非负整数 \(K\) 。在所有长度为 \(2^N\) ,总和为 \(K\) 的非负整数序列中,构造一个序列,使不平衡值最小。
题解
这道题就是一个简单的分治
题目的意思是每次合并\(A \gets (A_1 + A_2, A_3 + A_4, \dots, A_{\vert A \vert - 1} + A_{\vert A \vert})\) 。
要求每次合并出来的这些区间中数的和的极差最小
现在直接给定一个这些数的总和,然后让我们构造,使得极差最小
我们可以反过来考虑,将 \(K\) 由 1 个逐渐分成 \(2^n\) 个,这样的话考虑分出来的两个区间中的数最多能差多少,发现好像最多差 1 ,继续分下去的话也不会将这个差值拉大
如果 \(K \mid 2^n\) 那么差值为 0 ,否则差值为 1
时间复杂度为 \(O(n\log n)\)
code
#include <iostream>
#include <algorithm>
#include <cstring>
#include <cstdio>
using namespace std;
const int N = (1 << 20) + 7;
int n, m;
int a[N];
void dfs (int l, int r, int sum) {
if (l == r) {
a[l] = sum;
return;
}
int mid = (l + r) >> 1;
int sum1 = sum / 2;
int sum2 = (sum + 1) / 2;
dfs (l, mid, sum1);
dfs (mid + 1, r, sum2);
}
int main () {
cin >> n >> m;
n = 1 << n;
if (m % n == 0) {
cout << 0 << endl;
} else {
cout << 1 << endl;
}
dfs (1, n, m);
for (int i = 1; i <= n; i ++) {
cout << a[i] << ' ';
}
cout << endl;
return 0;
}
E-Colinear
题面
二维平面上有 \(N\) 个点。 \(N\) 是奇数。第 \(i\) 个点位于 \((x_i, y_i)\) 处。所有点的坐标都是不同的。
判断是否存在一条经过 \(N\) 个点中一半以上的点的直线,如果存在,输出它。
对于任何满足约束条件的输入,如果存在满足条件的直线,则可以用整数 \(a,b,c\) 与 \(-10^{18} \leq a,b,c \leq 10^{18}\) (其中 \((a,b,c) \neq (0,0,0)\) )表示为 \(ax+by+c=0\) 。输出这些 \(a,b,c\) 。
题解
这道题又学到一个trick,这种看起来没法做的题应该想想随机化能否巧妙的解决
首先我们要知道:两个点确定一条直线
假设答案为yes,而我们要找的直线穿过了 \(> \frac N 2\) 个点,如果我们每次随机两个点,然后枚举 \(N\) 个点,看是否有 \(> \frac N 2\) 个点在这条直线上
这样的话,找到这条直线的概率是 \(\frac 1 4\) ,找不到这条直线的概率是 \(\frac 3 4\)
只进行一次这样的操作错误的概率太高,但如果进行 \(T\) 次都没有找到,那么错误的概率就是 \((\frac 3 4)^T\) ,就很小了
然后这题还需要一个两点式(因为题目中的 a,b,c 为正整数)其实和 \(y = ax + b\) 差不多

就是 \(\frac {x - x_1} {x_1 - x_2} = \frac {y - y_1} {y_1 - y_2}\) ,然后将分母移上来即可算出题目中的 \(ax + by + c = 0\) 的 \(a,b,c\)
code
哈哈哈,还有我的卡时大法
#include <iostream>
#include <algorithm>
#include <cstring>
#include <cstdio>
#include <ctime>
#include <random>
using namespace std;
typedef long long ll;
const int N = 5e5 + 10;
int n;
ll x[N], y[N];
mt19937 rng (time (0));
int rnd (int l, int r) {
return rng () % (r - l + 1) + l;
}
int main () {
int st = clock (), ed;
scanf ("%d", &n);
for (int i = 1; i <= n; i ++) {
scanf ("%lld%lld", &x[i], &y[i]);
}
while (true) {
int i, j;
ll a, b, c;
do {
i = rnd (1, n);
j = rnd (1, n);
} while (i == j);
a = y[i] - y[j];
b = x[j] - x[i];
c = x[i] * y[j] - x[j] * y[i];
int cnt = 0;
for (int k = 1; k <= n; k ++) {
if (a * x[k] + b * y[k] + c == 0) cnt ++;
}
if (cnt > (n >> 1)) {
printf ("Yes\n%lld %lld %lld\n", a, b, c);
return 0;
}
ed = clock ();
if ((double)(ed - st) / 1000 > 1800) break;
}
printf ("No\n");
return 0;
}
F-Eat and Ride
题面
有一个连通无向图,其中有 \(N\) 个顶点和 \(M\) 条边。顶点编号为顶点 \(1,\) 顶点 \(2,\ldots,\) 顶点 \(N\) , \(i\) -th 边 \((1\le i\le M)\) 连接顶点 \(u _ i\) 和 \(v _ i\) 。
对于 \(i=1,2,\ldots,N\) ,请解决以下问题:
最初,高桥的权重为 \(0\) 。
他一开始在顶点 \(1\) ,并向顶点 \(i\) 移动。当他到达顶点 \(v\ (1\le v\le N)\) 时,他的体重增加了 \(W _ v\) 。
他乘坐的汽车可以沿边移动。当他通过一条边时,假设 \(X\) 为他当时的重量,则汽车消耗 \(X\) 单位燃料。
求他到达顶点 \(i\) 所消耗的燃料的最小值。
题解
考虑每条边的贡献,可以将原题转化成:
设高桥有 \(x\) 张票,每次经过一个点的时候要消耗 \(xW_u\) 单位燃料,那么我们只需要将原图转化一下
因为两次经过同一个点肯定是不优的,所以我们至多经过 \(n - 1\) 条边
原图的每个点都化为 \(n\) 个点,\((u, x), 0 \le x \le n - 1\)
原图的每条边 \((u,v)\) 转化成 \((u,x) \to (v, x-1),\ 1 \le x \le n - 1\)
然后就是 \(n\) 个起点 \((1, x)\) 跑最短路即可,时间复杂度为 \(O((N^2 + NM) \log N^2)\) ,我试了跑不过去
所以我们要优化一下这个求最短路的过程
我们发现这道题的转移它很有规律,一定是 \(x \to x - 1\) 所以我们可以采用一种递推的方式去转移
先枚举阶段 \(x\) ,然后再枚举 \(m\) 条边,每条边单独转移,时间复杂度为 \(O(NM)\) 可过
其实空间还可以再优化,不过不想写了
code
#include <iostream>
#include <algorithm>
#include <cstring>
#include <cstdio>
#include <vector>
#include <queue>
using namespace std;
int rd () {
int res = 0;
char c = getchar ();
while (c < '0' || c > '9') c = getchar ();
while (c >= '0' && c <= '9') {
res = (res << 1) + (res << 3) + (c ^ 48);
c = getchar ();
}
return res;
}
typedef long long ll;
const int N = 5e3 + 10;
int n, m;
ll f[N][N], w[N];
pair <int, int> e[N];
int main () {
n = rd (), m = rd ();
for (int i = 1; i <= n; i ++)
w[i] = rd ();
for (int i = 1; i <= m; i ++) {
int x = rd (), y = rd ();
e[i] = {x, y};
}
memset (f, 0x3f, sizeof f);
for (int i = 0; i < n; i ++) {
f[1][i] = i * w[1];
}
for (int i = n - 2; i >= 0; i --) {
for (int j = 1; j <= m; j ++) {
int x = e[j].first, y = e[j].second;
f[x][i] = min (f[x][i], f[y][i + 1] + i * w[x]);
f[y][i] = min (f[y][i], f[x][i + 1] + i * w[y]);
}
}
for (int i = 1; i <= n; i ++) {
printf ("%lld\n", f[i][0]);
}
return 0;
}

浙公网安备 33010602011771号