J - 炫耀快乐 简解——2024年“核桃杯”武汉地区ACM萌新赛
J - 炫耀快乐 简解
给定 \(n,m,k_1,k_2\) 和长度为 \(n\) 的数组 \(S\),请生成一个长度为 \(m\) 的排列 \(T\),记 \(i\) 在排列中排在第 \(T_i\) 位,使得以下值最小:
\[\sum_{i=2}^{n} \begin{cases} k_1(T_{S_i} - T_{S_{i-1}}) & T_{S_i} \ge T_{S_{i-1}}, \\ k_2(T_{S_i} + T_{S_{i-1}}) & T_{S_i} < T_{S_{i-1}}. \end{cases} \]请输出这个最小值。\(n\leq 10^5,m\leq 20\)
虽然确实好像有点歪榜这个题做的人不是很多,但是这个题对我来说确实理解起来有点费尽。所以这里就只是给出 \(O(m2^m)\) 的一种实现方式,其余部分基本照抄官方题解。
首先这个 \(S\) 序列就是一个陷阱,先统计数对 \(x,y\) 在 \(S\) 中以 \(S_{i}=x,S_{i-1}=y\) 形式出现的次数,记为 \(cnt_{x,y}\)。
然后考虑排列 \(T\) 位置 \(p\) 上的值 \(T_p\) 对答案的贡献, 对于一个位置 \(q\)
- 假如 \(T_p\geq T_q\),则它可以产生 \(cnt_{p,q}\times k_1\times T_p+cnt_{q,p}\times k_2\times T_p\) 的贡献
- 加入 \(T_p<T_q\),则它可以产生 \(cnt_{p,q}\times k_2\times T_p-cnt_{q,p}\times k_1\times T_p\) 的贡献。
因此我们一旦知道一个 \(T_p\) 和其他位置上值大小的相对关系,就能得到它对答案的贡献。有了这个关系我们就可以进行状压DP。
我们考虑从小到大给排列填值,并定义 \(dp_S\) 为排列已填的位置集合为 \(S\) 时的最小总贡献,即 \(1\sim |S|\) 这些数对答案的总贡献,其中 \(|S|\) 表示集合 \(S\) 的大小。要求得 \(dp_S\) 只需要枚举 \(|S|\) 这个数填到 \(S\) 中哪个位置 \(p\),则其他已经出现在 \(S\) 中的位置上的数一定比 \(|S|\) 小,未出现在 \(S\) 中的位置上的数一定比 \(|S|\) 大,于是根据上面我们所说可以求出 \(T_p=|S|\) 时 \(T_p\) 对答案的贡献。这样可以得到下面的 \(O(m^22^m)\) 算法。
dp[0]=0;
for(int S=1;S<(1<<m);++S){
dp[S]=INF;
int cnt=__builtin_popcount(S);
for(int i=0;i<m;++i){
if(!(S>>i&1))continue;
ll val=0;
for(int j=0;j<m;++j){
if(i==j)continue;
if(S>>j&1){
val+=bot[i][j]*k1*cnt;
val+=bot[j][i]*k2*cnt;
}else{
val-=bot[j][i]*k1*cnt;
val+=bot[i][j]*k2*cnt;
}
}
dp[S]=min(dp[S],dp[S^(1<<i)]+val);
}
}
printf("%lld\n",dp[(1<<m)-1]);
如何优化到 \(O(m2^m)\)
可以发现这个算法的瓶颈在于每次都需要额外枚举 \(q\) 来计算枚举的位置 \(p\) 对答案的贡献。
如果我们能预处理这一部分(即上面代码中的 val),便能实现 \(O(m2^m)\)。
我们定义 \(f_{p,S}\) 为选取 \(T_p=|S|\) 时,\(T_p\) 对答案的贡献,即属于或不属于 \(S\) 的数对 \(T_p\) 产生的贡献总和。
可以如果发现 \(f_{p,S}\) 和 \(f_{p,S'}\) 中,如果 \(q\in S\and q\in S'\),即 \(q\) 在两个集合中都存在,则它产生的贡献分别为 $(cnt_{p,q}\times k_1+cnt_{q,p}\times k_2)\times |S| $ 和 $(cnt_{p,q}\times k_1+cnt_{q,p}\times k_2)\times |S'| $ 前面的系数是一样的,只有集合大小 \(|S|\) 会发生改变影响答案,对于 \(q\notin S\and q\notin S\) 的 \(q\) 同理,只不过系数变为 \(cnt_{p,q}\times k_2-cnt_{q,p}\times k_1\)。
我们把系数之和记为 \(g_{p,S}\),如果 \(S'\cup \{j\}=S\)(\(j\neq p\)),即 \(S\) 集合为 \(S'\) 集合增加一个位置 \(j\),则可以得到转移
具体实现见代码吗,现在的时空复杂度均为 \(O(m2^m)\)。
#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
typedef pair<int,int>ttfa;
const int N=500005,M=20;
const ll INF=0x3f3f3f3f3f3f3f3f;
int n,m,a[N],idx[1<<M];
ll k1,k2,bot[M][M],dp[1<<M],f[M][1<<M],g[M][1<<M];
inline int lowbit(int x){return x&-x;}
int main(){
memset(dp,-1,sizeof(dp));
scanf("%d%d%lld%lld",&n,&m,&k1,&k2);
for(int i=1;i<=n;++i){
scanf("%d",&a[i]);
if(i>1)++bot[a[i]-1][a[i-1]-1];
}
//dfsp((1<<m)-1);
for(int i=0;i<m;++i)idx[1<<i]=i;
for(int i=0;i<m;++i){
for(int j=0;j<m;++j){
if(i==j)continue;
f[i][(1<<i)]+=-bot[j][i]*k1+bot[i][j]*k2;
g[i][(1<<i)]+=-bot[j][i]*k1+bot[i][j]*k2;
}
for(int S=(1<<i)+1;S<(1<<m);++S){
if(!(S>>i)&1)continue;
int j=(lowbit(S)==(1<<i)?idx[lowbit(S-(1<<i))]:idx[lowbit(S)]);
int T=S-(1<<j),cnt=__builtin_popcount(S);
f[i][S]=f[i][T]+g[i][T];
g[i][S]=g[i][T];
f[i][S]+=bot[j][i]*k1*cnt-bot[i][j]*k2*cnt;
f[i][S]+=bot[i][j]*k1*cnt+bot[j][i]*k2*cnt;
g[i][S]+=bot[j][i]*k1-bot[i][j]*k2;
g[i][S]+=bot[i][j]*k1+bot[j][i]*k2;
}
}
dp[0]=0;
for(int S=1;S<(1<<m);++S){
dp[S]=INF;
int cnt=__builtin_popcount(S);
for(int i=0;i<m;++i){
if(!(S>>i&1))continue;
// ll val=0;
// for(int j=0;j<m;++j){
// if(i==j)continue;
// if(S>>j&1){
// val+=bot[i][j]*k1*cnt;
// val+=bot[j][i]*k2*cnt;
// }else{
// val-=bot[j][i]*k1*cnt;
// val+=bot[i][j]*k2*cnt;
// }
// }
//printf("%d %d %lld %lld\n",S,i,val,f[i][S]);
//dp[S]=min(dp[S],dp[S^(1<<i)]+val);
dp[S]=min(dp[S],dp[S^(1<<i)]+f[i][S]);
}
}
printf("%lld\n",dp[(1<<m)-1]);
return 0;
}
写得比较敷衍,马上要去打 ccpc 网络赛了,如果有问题欢迎评论,我看到后会回复的。

浙公网安备 33010602011771号