10 收心赛1 T3 逻辑网络 题解

巡逻网络

题面

平面上,一开始有 \(n\) 个固定站点 \(a_1,a_2...a_n\)\(m\) 个可选站点 \(b_1,b_2,...b_m\)

小明要选出一些站点作为巡逻网络的节点。为了巡逻网络的高效,小明希望选出的节点之间两两曼哈顿距离之和尽可能大。

接下来有 \(q\) 次修改操作,每次加入一个点 \(p\) 作为新的可选站点。

另外有查询操作:选出所有的固定站点,再从当前所有的可选站点中选出 \(m\) 个可选站点,使得这 \(m + n\) 个点之间两两曼哈顿距离之和尽可能大,并输出这个最大的曼哈顿距离之和。

小明只在所有修改全部结束后询问你一次。

另外有 \(k\) 表示特殊性质,\(k = 1\) 表示只有一维,\(k = 2\) 表示有两维

\(1 \le k \le 2\)

\(0 \le n \le 20,\ 1 \le q \le 2 \times 10^4\)

\(2 \le m \le 4\)

曼哈顿距离:两个点 \((x_1,y_1)\)\((x_2,y_2)\) 的曼哈顿距离 \(d(x,y)\)\(|x_1 - x_2| + |y_1 - y_2|\)

题解

\(k = 1\)

这道题我们可以将这些点对答案的贡献分成类:固定点之间的距离,固定点和可选点之间的距离,可选点之间的距离

第一类的贡献是定值,可以直接预处理出来

第二类可以看做每个点的额外贡献,可以放在每个点上

所以我们要着重考虑第三类贡献如何计算

先来一个巧妙的转化

考虑两个点之间的距离计算,设第一个点的坐标为 \(a\) ,第二个点的坐标为 \(b\) ,两个点之间的距离为 \(|a - b|\)

实际上这个绝对值可以转化为最大值 \(max\{-a + b, -b + a\}\)

什么意思呢?我们可以假设放在后面的点的坐标一定比前面的点的坐标大,那么我们的答案就是后面点的坐标减去前面点的坐标,那么我们可以枚举两个点之间的位置关系,然后利用刚才的公式算个最大值即为我们想要的两个点间的距离

有什么用?原题中我们不是要选出 \(m\) 个点吗,这 \(m\) 个点之间的距离不好算,我们可以先规定一个位置关系,也就是枚举每个点放在哪个位置

假设我们已经选好了 \(m\) 个点,那么这个方案的贡献为

\[max\{\sum_{i = 1}^m (2i - m - 1)x_i\} = \sum_{i \not= j} |x_i - x_j| \]

那我们可以将贡献分配到每个点上,设 \(f(i,S)\) 表示考虑到第 \(i\) 个点,已经选了一些点放在了位置集合为 \(S\) 的位置的最大贡献

转移:

\[f(i,S) = max \{f(i, S), \ f(i - 1, S \oplus (1 << p_i)) + (2p_i - m - 1)x_i + calc (i)\} \]

时间复杂度为 \(O((m + q)2^mm)\) ,大概是 \(6e6\) ,可以过,\(calc(i)\) 可以预处理出来

\(k = 2\)

\(k = 2\) 的情况和 \(k = 1\) 的情况差不多,不过多了一维的限制,我们同样可以用类似的 \(dp\) 去做

\(f(i,S_1,S_2)\) 表示考虑前 \(i\) 个点,已经选的点的横坐标占据了 \(S_1\) 的位置,纵坐标占据了 \(S_2\)

转移和上面的差不多

\[f(i,S_1, S_2) = max \{f(i,S_1, S_2),\ f(i - 1, S_1 \oplus (1 << p1_i), S_2 \oplus (1 << p2_i)) + (2p1_i - m - 1)x_i + (2p2_i - m - 1)y_i + calc (i) \} \]

时间复杂度 \(O((m + q)4^mm^2)\) 大概 \(8e7\) 有点勉强,因为选的点的数量是一定的,所以 \(S_1\)\(S_2\) 中的 1 的数量也一定是一样的,所以实际的状态量为

\[\sum_{k = 0}^m \binom m k ^2 \]

这是 \(O(\frac {4^m} {\sqrt m})\) 的(不会证明)不过这样大概就是 \(4e7\) ,过掉没什么问题

code

代码不会写,给个标程跑路了

#include <iostream>
#include <algorithm>
#include <cstring>
#include <cstdio>

using namespace std;

typedef long long ll;
#define popc(x) __builtin_popcount (x)

const int N = 2e4 + 50;

int n, m, _, t;
ll X[N], Y[N], X1[N], Y1[N];
ll f[N][1 << 4][1 << 4], c[N], sum;


int main () {

    freopen ("net.in", "r", stdin);
    freopen ("net.out", "w", stdout);

    ios :: sync_with_stdio (0);
    cin.tie (0);
    cout.tie (0);

    cin >> m >> n >> _ >> t;

    n += m;

    //输入固定站点
    for (int i = 1; i <= t; i++) {
        if (_ == 2) cin >> X1[i] >> Y1[i];
        else cin >> X1[i];
    }

    for (int i = 1; i <= n; i++) {
        if (_ == 2) cin >> X[i] >> Y[i];
        else cin >> X[i];
    }

    //计算固定点之间的贡献
    for (int i = 1; i <= t; i++) {
        for (int j = i + 1; j <= t; j++) {
            sum += abs (X1[i] - X1[j]) + abs (Y1[i] - Y1[j]);
        }
    }

    //计算 c 数组,c[i] 表示可选站 i 到所有固定站的距离贡献
    for (int i = 1; i <= n; i++) {
        for (int j = 1; j <= t; j++) {
            c[i] += abs (X[i] - X1[j]) + abs (Y[i] - Y1[j]);
        }
    }

    //dp数组初始化
    memset (f, -0x3f, sizeof f);
    f[0][0][0] = 0;

    int mn = (1 << m);
    for (int i = 1; i <= n; i ++) {
        for (int S1 = 0; S1 < mn; S1 ++) {
            for (int S2 = 0; S2 < mn; S2 ++) {
                if (popc (S1) != popc (S2)) continue;

                //注意不要落情况

                f[i][S1][S2] = f[i - 1][S1][S2];    //不选当前站点

                for (int x = 0; x < m; x ++) {      //选当前点
                    if (!((S1 >> x) & 1)) continue;
                    for (int y = 0; y < m; y ++) {
                        if (!((S2 >> y) & 1)) continue;
                        ll val = X[i] * ((x + 1) * 2 - m - 1) + Y[i] * ((y + 1) * 2 - m - 1) + c[i];
                        f[i][S1][S2] = max (f[i][S1][S2], f[i - 1][S1 ^ (1 << x)][S2 ^ (1 << y)] + val);
                    }
                }
            }
        }
    }

    cout << sum + f[n][mn - 1][mn - 1] << endl;
    return 0;
}
posted @ 2025-08-28 14:17  michaele  阅读(38)  评论(0)    收藏  举报