UOJ422 小Z的礼物
小Z的礼物
小Z有一个神奇的自动售货机,里面有 \(n \times m\) 种物品,分别放在 \(n\) 行 \(m\) 列个格子中。每当小Z向自动售货机中投入一枚硬币,他就能获得一对相邻格子中的物品(已经获得的物品可能再次获得),获得每一对相邻格子中的物品的概率是相等的。在这 \(n \times m\) 种物品中,有一些物品是小Z喜欢的(小Z喜欢的用 * [星号] 表示,其他的用 .[英文句号] 表示),他想把这些物品包装成一份礼物。小Z想知道,期望投入多少枚硬币后,就可以获得这些他喜欢的物品。
对于所有数据,保证 \(1 \leq n \leq 6, 2 \leq m \leq 100\)。
题解
一眼就能看出是min-max容斥。
其中 \(all=n(m-1)+m(n-1)\) 表示多米诺骨牌总数。\(F(T)\) 表示能覆盖 \(T\) 中至少一个点的多米诺骨牌数量。
考虑设计DP求出所有 \(F(T)=x\) 的 \(T\) 的容斥系数 \((-1)^{|T|+1}\) 和。向 \(T\) 中加入一个星号时可以向四周摆放多米诺骨牌,两个相邻的星号会算重,所以我们需要用状压记录选择情况。
设 \(dp(i,j,S,T,x)\) 表示做到格子 \((i,j)\),前一列的星号选择情况为 \(S\),这一列 \(1\sim i-1\) 行的星号选择情况为 \(T\),多米诺骨牌总数为 \(x\) 的容斥系数和。显然 \(O(2^{2n}n^2m^2)\) 会TLE。
注意到我们只需要知道前一列 \(i\sim n\) 行的星号选择情况,所以可以使用轮廓线优化。时间复杂度 \(O(2^nn^2m^2)\)。
min-max容斥的时候不需要管 \(T\) 集合以外的元素的情况。这点很显然,但是我之前没理解透,看了好久那个
dx
的转移百思不得其解……
CO int N=6,M=101;
char a[N][M];
int dp[2][1<<N][2*N*M];
int main(){
int n=read<int>(),m=read<int>();
for(int i=0;i<n;++i) scanf("%s",a[i]);
int sum=n*(m-1)+m*(n-1);
int now=0,pre=1;
dp[now][0][0]=mod-1;
for(int j=0;j<m;++j)for(int i=0;i<n;++i){
swap(now,pre),memset(dp[now],0,sizeof dp[now]);
for(int s=0;s<1<<n;++s)
for(int x=0;x<=sum;++x)if(dp[pre][s][x]){
int t=s>>i&1?s^1<<i:s;
cadd(dp[now][t][x],dp[pre][s][x]);
if(a[i][j]=='.') continue;
t|=1<<i;
int dx=(i>0 and ~s>>(i-1)&1)+(j>0 and ~s>>i&1)+(i<n-1)+(j<m-1);
cadd(dp[now][t][x+dx],mod-dp[pre][s][x]);
}
}
int ans=0;
for(int i=1;i<=sum;++i){
int inv=fpow(i,mod-2);
for(int s=0;s<1<<n;++s) cadd(ans,mul(dp[now][s][i],inv));
}
ans=mul(ans,sum);
printf("%d\n",ans);
return 0;
}
轮廓线DP常用优化办法:滚动数组。它的意义不仅在于卡空间,而且能方便转移。比如列数变化的时候。