T1
暴力50分:
跑k次没有写错的dij
看起来似乎是个树
也有可能是森林
也许我们可以把它当树做
据Yousiki说是个入门的树形dp,but我不会
正解:
我们枚举两个距离最近的点的编号
既然两个的int表示不同,就说明它们的二进制表示上至少有一位不同。
假设枚举到第i位,就把这一位为0的点设置为源点,这一位为1的点设置成汇点
然后跑多源多汇最短路
就是设置一个超级源点s,一个超级汇点t
s向每个源点连边权为0的边,所有汇点向t连边权为0的边,然后跑s到t的最短路
stdのcode
#include <queue> #include <cstdio> #include <cstring> template <class cls> inline cls min(const cls & a, const cls & b) { return a < b ? a : b; } const int mxn = 100005; const int mxm = 500005; const int inf = 0x3f3f3f3f; int n, m, k; int points[mxn]; int tot; int hd[mxn]; int nt[mxm]; int to[mxm]; int vl[mxm]; inline void add_edge(int u, int v, int w) { nt[++tot] = hd[u]; to[tot] = v; vl[tot] = w; hd[u] = tot; } int dis[mxn]; struct data { int u, d; data(int _u, int _d) : u(_u), d(_d) {} bool operator < (const data & that) const { return d > that.d; } }; std::priority_queue<data> heap; int main() { int cas; scanf("%d", &cas); for (int c = 0; c < cas; ++c) { scanf("%d%d%d", &n, &m, &k); memset(hd, 0, sizeof(int) * (n + 5)); tot = 0; for (int i = 0, u, v, w; i < m; ++i) { scanf("%d%d%d", &u, &v, &w); add_edge(u, v, w); add_edge(v, u, w); } for (int i = 0; i < k; ++i) scanf("%d", points + i);//神仙指针 int ans = inf; for (int i = 1; i < k; i <<= 1) { memset(dis, inf, sizeof(int) * (n + 5));//只memset dis数组的前n+5位 for (int j = 0, p; j < k; ++j)//这里对每个地下党按照输入顺序进行编号,然后枚举编号 if (p = points[j], (j & i) == 0)//看j的第i位是否是0 heap.push(data(p, dis[p] = 0));//把第i位为0的设置成了源点 while (!heap.empty()) { int u = heap.top().u; int d = heap.top().d; heap.pop(); if (dis[u] != d) continue; for (int e = hd[u], v, w; e; e = nt[e]) if (v = to[e], w = vl[e], dis[v] > d + w) heap.push(data(v, dis[v] = d + w)); } for (int j = 0, p; j < k; ++j) if (p = points[j], (j & i) != 0) ans = min(ans, dis[p]); } printf("%d\n", ans == inf ? -1 : ans); } return 0; }