CF79D Password (差分+狀壓 dp+最短路/bfs)

Fire_Raku發表於2024-06-29

CF79D Password

差分 + 狀壓 dp + 最短路/bfs

好題。區間取反不好做?考慮差分一下,那麼操作就轉化為 \(i\)\(i+k\) 兩個位置的單點取反。因為差分陣列上 \(1\) 的數量 \(\le 20\),滿足題目狀態等價於滿足差分陣列的狀態,考慮狀壓 \(1\) 的位置。設 \(f_s\) 表示達到狀態 \(s\) 的最少運算元。

考慮轉移。實際上可以把所有操作拆分成若干條從其中一個 \(1\) 到另一個 \(1\) 的路徑(這裡的 \(1\) 指目標狀態中的 \(1\))。考慮建圖,對於長度 \(v\) 在每個位置連邊 \((i,i+v)\)。那麼顯然每條路徑要最短,跑大約 \(20\) 次單源最短路/bfs 就可以了。那麼列舉兩個位置 \(i\)\(j\),有\(f_{s|2^i|2^j}=\min(f_s+mp_{i,j})\)

複雜度 \(O(2^{2k}k)\)

#include <bits/stdc++.h>
#define pii std::pair<int, int>
#define fi first
#define se second
#define pb push_back

using i64 = long long;
using ull = unsigned long long;
const i64 iinf = 0x3f3f3f3f, linf = 0x3f3f3f3f3f3f3f3f;
const int N = 1e4 + 10, S = 21;
int n, k, l, num;
int a[N], dis[25][N], vis[N], id[N], f[1 << S];
std::vector<int> e[N];
struct com {
	int v, w;
	friend bool operator < (com a, com b) {
		return a.w > b.w;
	}
} tmp;
void dij(int s) {
	id[++num] = s;
	std::priority_queue<com> q;
	for(int i = 1; i <= n + 1; i++) dis[num][i] = iinf, vis[i] = 0;
	dis[num][s] = 0;
	tmp = {s, 0};
	q.push(tmp);
	while(!q.empty()) {
		int u = q.top().v;
		q.pop();
		if(vis[u]) continue;
		vis[u] = 1;
		for(auto v : e[u]) {
			if(dis[num][v] > dis[num][u] + 1) {
				dis[num][v] = dis[num][u] + 1;
				tmp = {v, dis[num][v]};
				q.push(tmp);
			}
		}
	}
	return;
}
int main() {
    std::ios::sync_with_stdio(false);
    std::cin.tie(nullptr);
    
	std::cin >> n >> k >> l;
	for(int i = 1; i <= k; i++) {
		int x;
		std::cin >> x;
		a[x] = 1;
	}
	
	for(int i = 1; i <= l; i++) {
		int v;
		std::cin >> v;
		for(int j = 1; j <= n + 1; j++) {
			if(j + v > n + 1) continue;
			e[j].pb(j + v);
			e[j + v].pb(j);
		}
	}

	int cnt = 0;
	for(int i = n + 1; i >= 1; i--) {
		a[i] = abs(a[i] - a[i - 1]);
		cnt += a[i];
	}
	for(int i = 1; i <= n + 1; i++) if(a[i]) dij(i);

	memset(f, 0x3f, sizeof(f));
	f[0] = 0;
	int lim = (1 << cnt) - 1;
	for(int s = 0; s <= lim; s++) {
		for(int i = 0; i < cnt; i++) {
			if((s >> i) & 1) continue;
			for(int j = i + 1; j < cnt; j++) {
				if((s >> j) & 1) continue;

				f[s | (1 << i) | (1 << j)] = std::min(f[s | (1 << i) | (1 << j)], f[s] + dis[i + 1][id[j + 1]]);

			}
		}
	}
	std::cout << (f[lim] == iinf ? -1 : f[lim]) << "\n";

	return 0;
}

相關文章