Loading

11.22 CW 模拟赛 T2.通信

算法

显然的, 我们可以先转化问题
对于无向图上的 \(n\) 个点, 点之间的边权就是 \(\min(\text{图上的欧氏距离的平方和}, v)\) , 求走完所有点时经过的最小边权和

手玩样例看下有没有思路?

显然的, 对于 \(50 \rm{pts}\) , 状压可以解决

考虑剩下的 \(50 \rm{pts}\) , 注意到我们的目标是在完全图中掏一个长为 \(n\) 的环出来使得边权和最小, 显然可以状态压缩 , 由于是完全图, 状态膨胀非常快, 大概是 \(n^n\) 这一级的, 这一点也不好处理

顺着状压 \(\rm{dp}\) 往下走
注意到每一条边 \((u, v)\) 都可以更新 \(State_{without \ v \ and \ with \ u} \to State_{with \ v \ and \ with \ u}\)
考虑逆向处理
我们可以知道, 对于每一个点 \(v\) , 我们可以枚举其出边权值最小, 加在一起即可
但是这里有约束条件, 注意到如果 \(u\) 已经不在集合中, 我们不能使用 \((u, v)\)

所以我们要找出 \(n - 1\) 条边, 其中每个点作为终点只出现一次, 也就是说入度为 \(1\) , 并且要求边权和最小, 这是最小生成树的典型运用

然后就可以用最小生成树处理了, 注意起始点的边权应当设为 \(V\) , 因为第一个点必须空降而来

代码

#include <bits/stdc++.h>
using namespace std;

template <typename T>
inline void read(T &x)
{
    x = 0;
    char ch = getchar();
    while (ch < '0' or ch > '9')
        ch = getchar();
    while (ch >= '0' and ch <= '9')
        x = (x << 1) + (x << 3) + (ch ^ 48), ch = getchar();
    return;
}

const int N = 5e3 + 10;

int n, V;
int x[N], y[N];

struct Edge
{
    int v, w;
};
vector<Edge> e[N];

inline int dis(int i, int j)
{
    return (x[i] - x[j]) * (x[i] - x[j]) + (y[i] - y[j]) * (y[i] - y[j]);
}

inline void init()
{
    read(n), read(V);
    for (int i = 1; i <= n; ++i)
        read(x[i]), read(y[i]);
    for (int i = 1; i <= n; ++i)
        for (int j = 1; j < i; ++j)
        {
            if (i == j) continue;
            int d = dis(i, j);
            if (d < V)
                e[i].push_back({j, d}), e[j].push_back({i, d});
            else
                e[i].push_back({j, V}), e[j].push_back({i, V});
        }
    return;
}

long long ans = 0;
int tot = 0;


/*
inline void Kruskal()
{
    while (!e.empty())
    {
        auto x = e.top();
        e.pop();
        int f1 = find(x.u), f2 = find(x.v);
        if (f1 ^ f2)
        {
            ++tot, ans += x.w;
            fa[f1] = f2;
        }
    }
    return;
}
*/

bool down[N];
struct node
{
    int Num;
    int dis;

    friend bool operator < (const node &a, const node &b)
    {
        return a.dis > b.dis;
    }
};
priority_queue<node> Q;
void Prim()
{
    Q.push({1, V});
    while (!Q.empty())
    {
        node Now = Q.top();
        Q.pop();
        if (down[Now.Num]) continue;

        down[Now.Num] = true;
        ans += Now.dis;
        for (int i = 0; i < int(e[Now.Num].size()); i++) {
            int NowTo = e[Now.Num][i].v, NowW = e[Now.Num][i].w;
            if (down[NowTo]) continue;
            Q.push({NowTo, NowW});
        }
    }

    printf("%lld", ans);
}

inline void calculate()
{
    Prim();
    return;
}

inline void solve()
{
    init();
    calculate();
    return;
}

signed main()
{
    solve();
    return 0;
}

/*
6 1000
0 0
0 10
20 20
30 30
80 100
100 100

3200
*/

总结

通过部分分推正解方法是好的

善于转化问题

优化后的 \(\rm{Prim}\) 算法在稠密图中比未优化的慢

posted @ 2024-11-22 18:31  Yorg  阅读(14)  评论(0)    收藏  举报