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\) 差不多

image-20250907192508856

就是 \(\frac {x - x_1} {x_1 - x_2} = \frac {y - y_1} {y_1 - y_2}\) ,然后将分母移上来即可算出题目中的 \(ax + by + c = 0\)\(a,b,c\)

\[\begin {align} &a = y_1 - y_2 \\ &b = x_2 - x_1 \\ &c = x_1 y_2 - x_2y_1 \end {align} \]

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;
}
posted @ 2025-09-07 20:54  michaele  阅读(55)  评论(0)    收藏  举报