[POI2014] TUR-Tourism
題意
給出一張圖,在這張圖中,任意兩點間不存在節點數超過 \(10\) 的簡單路徑。
第 \(i\) 個點被選的代價為 \(C_i\),每個節點要麼選,要麼與它直接相連的點中至少有一個被選。
求最小代價。
思路
圖的生成樹上狀壓動態規劃。
由於給出的是一張圖,無法直接dp,我們可以在這張圖的 dfs 樹上dp。
又因為任意兩點間不存在節點數超過 \(10\) 的簡單路徑,
我們可以把每個點到根的路徑上點的狀態進行壓縮。
\(0\):這個點選了。
\(1\):這個點沒選也沒被覆蓋。
\(2\):這個點沒選但被覆蓋。
每次從子節點回收答案,看子節點選還是不選被覆蓋。
每次還需要從父節點繼承答案,列舉返祖邊。
透過祖先的狀態,統計出當前點選和不選時的狀態,從父親的 dp 值繼承過來。
實現時用 \(dp_{i,j}\) 表示深度為 \(i\)(而不是點 \(i\)),狀態為 \(j\) 的最小值,每次清空節省空間。
程式碼和註釋
#define Get(x, y) ((x) / Pow[y] % 3)
#define Set(x, y, v) (x -= Get(x, y) * Pow[y], x += v * Pow[y])
#define min(x, y) ((x) < (y) ? (x) : (y))
const int N = 1e5 + 5;
/*
0:選了
1:不選且未被覆蓋
2:不選且被覆蓋
*/
int ver[N << 1], nxt[N << 1], head[N];
int a[N], n, m, tot;
int dep[N], ans, cnt, z[N];
int dp[11][N], Pow[11];
bool vis[N];
void add(int x, int y) {
ver[++ tot] = y;
nxt[tot] = head[x];
head[x] = tot;
}
void dfs(int x) {
vis[x] = 1, cnt = 0;
for (int i = head[x], y; i; i = nxt[i]) {
y = ver[i];
if (vis[y]) z[++ cnt] = dep[y]; // 返祖邊連向的祖先
}
if (dep[x] == 0) { // 根節點
dp[0][0] = a[x];
dp[0][1] = 0;
dp[0][2] = 1e9;
} else {
for (int i = 0; i < Pow[dep[x] + 1]; i ++) dp[dep[x]][i] = 1e9; // 賦值
for (int i = 0; i < Pow[dep[x]]; i ++) { // 列舉父親狀態
int yes = i, no = i;
// yes:當前點選後的狀態
// no:當前點不選後的狀態
Set(no, dep[x], 1); // 初始時就是不選未覆蓋
for (int j = 1; j <= cnt; j ++) {
int y = z[j];
if (Get(i, y) == 0) Set(no, dep[x], 2); // 如果有祖先選了,當前點變為未選被覆蓋
if (Get(i, y) == 1) Set(yes, y, 2); // 如果有祖先不選未覆蓋,把它變為未選被覆蓋
}
dp[dep[x]][no] = min(dp[dep[x]][no], dp[dep[x] - 1][i]); // 沒選當前點,直接繼承
dp[dep[x]][yes] = min(dp[dep[x]][yes], dp[dep[x] - 1][i] + a[x]); // 選了當前點,加上權值
}
}
for (int i = head[x], y; i; i = nxt[i]) {
y = ver[i];
if (vis[y]) continue;
dep[y] = dep[x] + 1;
dfs(y);
for (int i = 0; i < Pow[dep[y]]; i ++)
dp[dep[x]][i] = min(/*選*/dp[dep[y]][i], /*不選被覆蓋*/dp[dep[y]][i + 2 * Pow[dep[y]]]); // 從兒子回收答案
}
}
int main() {
memset(dp, 0x3f, sizeof(dp));
Pow[0] = 1;
for (int i = 1; i <= 10; i ++)
Pow[i] = Pow[i - 1] * 3;
read(n), read(m);
for (int i = 1; i <= n; i ++) read(a[i]);
for (int i = 1, u, v; i <= m; i ++) {
read(u), read(v);
add(u, v);
add(v, u);
}
for (int i = 1; i <= n; i ++) {
if (vis[i]) continue;
dfs(i);
ans += min(dp[0][0], dp[0][2]);
}
print(ans);
print_final();
return 0;
}