算法
显然的, 我们可以先转化问题
对于无向图上的 \(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}\) 算法在稠密图中比未优化的慢
标签:ch,int,11.22,void,T2,Num,Now,CW,边权 From: https://www.cnblogs.com/YzaCsp/p/18562871