Minimizing Edges P
题目链接:luogu P7417
题目大意
给你一个图,然后 fi,j 表示是否存在一个从 1 到 i 的路径经过的边数是 j。
然后要你构造出来一个边数最少的图使得它的 f 函数跟给出的图的一样。
输出最小边数。
会有自环,没有重边。
思路
首先不难想到它可以在一条边上反复横跳。
所以会影响这个函数的就只有到每个点的最短奇数长度路径和最短偶数长度路径。
这个可以 bfs 求出,不过我用的是 dij,而且我是把点拆成奇数长度到的点和偶数长度到的点来跑的。
然后接着你考虑怎么构新图。
首先如果 (1) 有自环,就是直接 (n) 条边。(一个自环,然后剩下的搞树)
还有一种比较特殊的就是二分图。
那其实就是直接 (n-1) 搞成树就可以了。
那接着就是剩下的,那我们想一个点要从那些点满足,然后连边过来。
不难想到这时候奇偶就没有太大所谓了,我们就直接 ((a,b)) 表示两个距离是 ((a,b)) 的状态。
而且为了方便我们让 (a<b)
(至于这个状态我们可以用 map)
不难想到,两种可能:
((a,b)) 从 ((a-1,b-1)) 转。
((a,b)) 从 ((a-1,b+1)) 和 ((a+1,b-1)) 两个一起转。
那你可以 (a+b) 看做一维,(a) 看做一维,那第二种就相当于内部消化,第一种就相当于往上转移。
那如果只有一种,那肯定就是从那个转。
第一个的转很好想,就是直接加,但是第二个呢?
我们考虑搞一个 (cnt_{x,y}) 表示 ((x,y)) 这个状态从左边转了多少个过来。(这个也是用 map 存)
然后那你转 ((x,y)) 的时候就把转两边的个数加到 ((x+1,y-1)) 里面,然后你转 ((x+1,y-1)) 的时候要转的个数就少了 (cnt_{x+1,y-1})。
然后不难想到会出现一个状态就是 (x+1=y),那就是可以两个碰在一起消掉,那相当于你两个如果有两个的话就可以只用一条边,那费用就变成了 ((nm_{x,y}+1)/2)。
然后如果两个都有就是转两边的就继续转两边,剩下的如果还要转的就一定往上转。
(因为往上转是更优的,显然)
然后你跑的时候记录一下答案,最后输出就可以了。
(这么一看这题除了贪心好像没有什么算法但是就是很阴间)
代码
#include<map>
#include<queue>
#include<cstdio>
#include<iostream>
#include<algorithm>
#define INF 0x3f3f3f3f3f3f3f3f
using namespace std;
const int N = 200001;
struct node {
int to, nxt;
}e[N << 2];
int T, n, m, x, y, le[N << 1], KK;
int ans, ax[N << 1], ay[N << 1];
struct st {
int x, y;
}tp[N];
map <pair<int, int>, int> nm, hv;
int tpn;
struct ztzt {
int dis, now;
};
bool operator <(ztzt x, ztzt y) {
return x.dis > y.dis;
}
priority_queue <ztzt> q;
int dis[N << 1];
bool in[N << 1];
void add(int x, int y) {
e[++KK] = (node){y, le[x]}; le[x] = KK;
e[++KK] = (node){x, le[y]}; le[y] = KK;
}
void dij() {//dij 跑出奇路径和偶路径的最短路(其实 bfs 都可以)
while (!q.empty()) q.pop();
dis[0] = INF;
for (int i = 1; i <= n; i++) {
dis[i] = dis[i + n] = INF;
in[i] = in[i + n] = 0;
}
dis[1] = 0; q.push((ztzt){0, 1});
while (!q.empty()) {
int now = q.top().now;
q.pop();
if (in[now]) continue;
in[now] = 1;
for (int i = le[now]; i; i = e[i].nxt)
if (!in[e[i].to] && dis[e[i].to] > dis[now] + 1) {
dis[e[i].to] = dis[now] + 1;
q.push((ztzt){dis[e[i].to], e[i].to});
}
}
}
bool cmp1(st x, st y) {
if (x.x + x.y != y.x + y.y) return x.x + x.y < y.x + y.y;
return x.x < y.x;
}
int main() {
scanf("%d", &T);
while (T--) {
scanf("%d %d", &n, &m);
for (int i = 1; i <= m; i++) {
scanf("%d %d", &x, &y);
ax[i] = x; ay[i] = y;
add(x, y + n); add(x + n, y);
}
dij();
if (dis[1 + n] == 1) {//起点有自环
printf("%d
", n);
}
else {
bool gogo = 0;
for (int i = 1; i <= n; i++) {//特判二分图
if (dis[i] == dis[0] || dis[i + n] == dis[0]) {
gogo = 1;
break;
}
}
if (gogo) {
printf("%d
", n - 1);
}
else {
ans = 0; tpn = 0;
nm.clear(); hv.clear();
for (int i = 1; i <= n; i++) {
pair <int, int> t = make_pair(dis[i], dis[i + n]);
if (t.first > t.second) swap(t.first, t.second);
nm[t]++; tp[++tpn] = (st){t.first, t.second};
}
sort(tp + 1, tp + tpn + 1, cmp1);
for (int i = 1; i <= tpn; i++) {
if (tp[i].x == tp[i - 1].x && tp[i].y == tp[i - 1].y) continue;
x = tp[i].x; y = tp[i].y;
pair <int, int> t = make_pair(x, y), t1 = make_pair(x - 1, y - 1);
pair <int, int> t2 = make_pair(x - 1, y + 1), t3 = make_pair(x + 1, y - 1);
if (nm[t2] == 0 && nm[t1] != 0) {//直接连 (x-1,y-1)
ans += nm[t];
}
else if (nm[t2] != 0 && nm[t1] == 0) {//直接连 (x-1,y+1)
ans += max(0, nm[t] - hv[t]);//只用连剩下的就可以了
if (x + 1 == y) ans += (nm[t] + 1) / 2;//在中间消化了
else {//继续往右移
ans += nm[t];
hv[t3] += nm[t];
}
}
else if (nm[t2] != 0 && nm[t1] != 0) {//两个都要
ans += max(0, nm[t] - hv[t]);
hv[t] = min(hv[t], nm[t]);//记得这个要变
if (x + 1 == y) ans += (hv[t] + 1) / 2;//跟上面同理
else {
ans += hv[t];
hv[t3] += hv[t];
}
}
}
printf("%d
", ans);
}
}
KK = 0;
for (int i = 1; i <= n; i++) {
le[i] = le[i + n] = 0;
}
}
return 0;
}