loj3302.「联合省选 2020 A | B」信号传递
非常显然,对于每条边把贡献拆开计算:如果边正向就是 \(y-x\),否则是 \(k(x+y)\),然后把贡献分摊到每个点上计算。
看到 \(m\leq23\),考虑状压。显然一个点放在某个位置产生的贡献只与他前面有哪些点有关,于是枚举这个点集,得到 \(dp_s=\min(dp_{s-\{i\}}+g_{s-\{i\},i})\)。其中 \(g_{s,i}\) 表示前方点集为 \(s\) 当前位置放 \(i\) 的贡献,这个可以直接 \(O(m2^m)\) 递推算,即 \(g_{s+\{i\},j}=g_{s,j}+(1-k)e_{i,j}+(1+k)e_{j,i}\)。\(e_{x,y}\) 表示 \(x\) 到 \(y\) 的边数。
这个做法时空复杂度均为 \(O(m2^m)\),时间没问题但空间太大,只有 \(80\) 分,考虑优化。
优化空间常见套路就是滚动数组。不难发现这个 dp 只会从 \(\mathrm{popcnt(s)}=i\) 的位置转移到 \(\mathrm{popcnt(s)}=i+1\) 的位置,于是可以枚举 \(s\) 的二进制下 \(1\) 的个数,然后每次向 \(1\) 个数多一的点转移。这样空间复杂度降为 \(O(2m\dbinom{23}{11})\),可以通过。
#include<iostream>
#include<cstdio>
#include<vector>
using namespace std;
int n,m,k,a[100001],mp[31][31],dp[1<<23],g[1700001][24][2],cnt,now;
vector<int> v[2];
inline int read()
{
register int x=0;
char c=getchar();
while(c<'0'||c>'9')
c=getchar();
while(c>='0'&&c<='9')
{
x=(x<<1)+(x<<3)+(c^48);
c=getchar();
}
return x;
}
void print(register int x)
{
if(x>=10)
print(x/10);
putchar(x%10+'0');
}
inline int getmin(register int x,register int y)
{
return (x-y)>>31? x:y;
}
int main()
{
//freopen("transfer15.in","r",stdin);
n=read(),m=read(),k=read();
for(register int i=1;i<=n;++i)
{
a[i]=read();
if((i^1)&&(a[i-1]^a[i]))
++mp[a[i-1]][a[i]];
}
for(register int s=1;s<1<<m;++s)
dp[s]=1<<30;
for(register int i=1;i<=m;++i)
for(register int j=1;j<=m;++j)
if(i!=j)
g[1][j][0]+=k*mp[i][j]-mp[j][i];
v[0].push_back(0);
for(register int i=1;i<=m;++i)
{
now^=1;
v[now].clear();
cnt=0;
for(register int t=0;t<(int)v[now^1].size();++t)
{
register int s=v[now^1][t];
for(register int j=1;j<=m;++j)
{
if(s>>(j-1))
continue;
v[now].push_back(s|(1<<(j-1)));
++cnt;
for(register int p=1;p<=m;++p)
g[cnt][p][now]=g[t+1][p][now^1]+(1-k)*mp[j][p]+(1+k)*mp[p][j];
}
}
for(register int t=0;t<(int)v[now^1].size();++t)
{
register int s=v[now^1][t];
for(register int j=1;j<=m;++j)
{
if(s&(1<<(j-1)))
continue;
dp[s|(1<<(j-1))]=getmin(dp[s|(1<<(j-1))],dp[s]+i*g[t+1][j][now^1]);
}
}
}
print(dp[(1<<m)-1]);
putchar('\n');
return 0;
}

浙公网安备 33010602011771号