[状压dp] P6622 [省选联考 2020 A_B 卷] 信号传递

posted on 2024-04-28 13:30:19 | under | source

显然不关注给出的序列具体是啥,只关心两两相邻的元素组成的 \(n-1\) 个二元组。

然后对于有序二元组 \((a,b)\),记 \(d_a,d_b\) 为最终 \(a,b\) 号基站的坐标,贡献就是:

  • \(d_a\le d_b:d_b-d_a\)
  • \(d_a>d_b:kd_a+kd_b\)

然后因为 \(m\) 的范围不知道比 \(n\) 小多少去了,所以直接记录 \(cnt_{a,b}\) 表示 \((a,b)\) 数量。显然它们完全一样。

直接全排列枚举 \(d\) 会超时,所以用状压 \(\rm dp\) 优化。

状压肯定不能记录每个 \(d\) 具体取值了,所以这里拆下贡献,即新增 \(i\) 就只考虑 \(d_i\) 这项的贡献。

再仔细观察上面的贡献式子,发现只关心 \(d_i\) 取值和 \(d_i\) 与其它 \(d_j\) 的大小关系。

所以希望状态定义能体现出基站标号,并有序枚举 \(d\) 来去掉判断偏序关系这一步。

所以定义 \(f_{S}\) 表示已确定 \(S\) 表示的标号的基站的 \(d\),并且这些 \(d\) 的值域落在 \([1,|S|]\) 中时,最小总贡献。

有转移:\(f_S+g_{S,i}*|S|\to f_{S\cup i}\)

\(g_{S,i}\) 是转移代价中 \(d_i\) 的系数:\(g_{S,i}=(\sum\limits_{j\in S} cnt_{i,j}*k+cnt_{j,i})+(\sum\limits_{j\notin S\cup i} -cnt_{i,j}+cnt_{j,i}*k)\)

直接枚举 \(i,j\),复杂度 \(O(m^22^m)\)

考虑优化。显然 \(g\) 可以递推,时间优化为 \(O(m2^m)\)

然后空间爆了。注意到 \(g_{S,i}\)\(S\) 肯定不包含 \(i\),所以舍弃 \(i\) 这位,优化了一半空间,刚刚好够。

启示:考虑贡献法减少考虑因素;状压处理全排列有两种写法。

代码

好丑啊(

#include<bits/stdc++.h>
using namespace std;

const int N = 23;
int n, m, k, cnt[N][N], s, _s, f[1 << N], g[1 << (N - 1)][N];

inline void init(){
	memset(f, 0x3f, sizeof f); f[0] = 0;
	for(int i = 0; i < m; ++i)
		for(int j = 0; j < m; ++j)
			if(i ^ j) g[0][i] += -cnt[i][j] + cnt[j][i] * k;
}
inline int get(int a, int k) {return a & ((1 << k) - 1);}
int main(){
	cin >> n >> m >> k;
	for(int i = 1; i <= n; ++i)	scanf("%d", &s), (_s ? ++cnt[_s - 1][s - 1] : 1) , _s = s;
	init();
	for(int S = 0; S + 1 < 1 << m; ++S){
		int ppc = __builtin_popcount(S), lb = S & -S, _S = S ^ lb, lst = log2(lb);
		for(int i = 0; i < m; ++i){
			if((S >> i) & 1) continue;
			int S2 = S | (1 << i), ct = 0, qs = S & ((1 << i) - 1), gs = qs | (S - qs >> 1);
			if(S) g[gs][i] = g[gs ^ (1 << (lst > i ? lst - 1 : lst))][i] + (cnt[i][lst] * k + cnt[lst][i]) - (-cnt[i][lst] + cnt[lst][i] * k);
			f[S2] = min(f[S2], f[S] + (ppc + 1) * g[gs][i]);
		}
	}
	cout << f[(1 << m) - 1];
	return 0;
}
posted @ 2026-01-13 11:25  Zwi  阅读(0)  评论(0)    收藏  举报