[状压DP]luogu P6622 [省选联考 2020 A/B 卷] 信号传递
题面
https://www.luogu.com.cn/problem/P6622
分析
枚举每个信号塔的位置显然不行,考虑设置 DP 状态
f[S] 表示选择了集合为 S 的塔,排在前 |S| 个位置
方程则为 $f[S|i]=f[S]+h[S,i]$ $h[S,i]$ 表示 S 中与 i 有连边的贡献
$h[S,i]$ 怎么求呢?据题意有
$w_{i,j}=j-i (i<j)$
$w_{i,j}=ki+kj (i>j)$
发现可以对于每个位置来计算贡献,有一条向后的出边则 -1 ,向前的出边则 +k ,从后来的入边则 +k ,从前来的入边则 +1
设 $c_{i,j}$ 为 i 到 j 的边数,则有 $h[S,i]=(|S|+1)\times(\sum_{j\in S}(kc_{i,j}+c_{j,i}) + \sum_{j\notin S|i}(kc_{j,i}+c_{i,j}))$
这样时间复杂度是 $O(m^22^m)$ 的,如果从低位往高位枚举 S 并同时转移 h[S,i] ,则可以压到 $O(m2^m)$
但是空间复杂度仍然不足以AC,瓶颈在于 h[S,i]
发现如果从小往大枚举,那么最近枚举的一个位数为 i 的状态 S 枚举到下一个位数为 i 的状态 S' 之前一定会枚举到一个位数为 i+1 的状态 S'' ,而 S'' 显然为 S+lowbit(S)
所以可以直接改为 h[|S|,i] ,每次转移的时候从 h[|S|-1,i] 加上对应贡献即可
代码
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
#include <iostream> #include <cstdio> #define lowbit(x) (x&-x) using namespace std; const int Inf=2147483647; const int N=1e5+1; const int M=23; int n,m,k; int g[M][M],f[1<<M],h[M][M],cnt[1<<M],lg[1<<M]; int main() { scanf("%d%d%d",&n,&m,&k); int u;scanf("%d",&u); for (int i=1,v;i<n;i++) scanf("%d",&v),g[u-1][v-1]++,u=v; for (int i=0;i<m;i++) for (int j=0;j<m;j++) if (i!=j) h[0][i]+=k*g[j][i]-g[i][j]; n=1<<m;lg[0]=-1;for (int i=1;i<n;i++) lg[i]=lg[i>>1]+1,cnt[i]=cnt[i-lowbit(i)]+1,f[i]=Inf; for (int i=0;i<n-1;i++) { if (i) for (int j=0;j<m;j++) h[cnt[i]][j]=h[cnt[i]-1][j]-k*g[lg[lowbit(i)]][j]+k*g[j][lg[lowbit(i)]]+g[j][lg[lowbit(i)]]+g[lg[lowbit(i)]][j]; for (int j=(n-1)^i;lowbit(j);j-=lowbit(j)) f[i|lowbit(j)]=min(f[i|lowbit(j)],f[i]+h[cnt[i]][lg[lowbit(j)]]*(cnt[i]+1)); } printf("%d\n",f[n-1]); }
在日渐沉没的世界里,我发现了你。