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\),则可以得到转移

\[f_{p,S}=f_{p,S'}+g_{p,S'}+(cnt_{p,j}\times k_1+cnt_{j,p}\times k_2)\times |S|-(cnt_{p,j}\times k_2-cnt_{j,p}\times k_1)\times |S|\\ g_{p,S}=g_{p,S'}+(cnt_{p,j}\times k_1+cnt_{j,p}\times k_2)-(cnt_{p,j}\times k_2-cnt_{q,j}\times k_1) \]

具体实现见代码吗,现在的时空复杂度均为 \(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 网络赛了,如果有问题欢迎评论,我看到后会回复的。

posted @ 2026-09-19 11:45  BigSmall_En  阅读(10)  评论(0)    收藏  举报