【luogu P7417】Minimizing Edges P(貪心)(思維)
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)\) 的時候就把轉兩邊的個數加到 \((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", 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", 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\n", ans);
}
}
KK = 0;
for (int i = 1; i <= n; i++) {
le[i] = le[i + n] = 0;
}
}
return 0;
}