qoj4238 Zero Sum

题意

给定一个大小为 \(n\times (2k+1)\)的矩阵 \(a\),行编号从 \(1\)\(n\),列编号从 \(-k\)\(k\)。要求选出 \(n\) 个数 \(x_1,x_2,\cdots,x_n\) 使得 \(\sum_{i=1}^n x_i=0\)\(\sum_{i=1}^n a_{i,x_i}\) 最小。

\(n\le 35000,k\le 3,|a_{i,j}|\le 10^9\)

思路

300iq Contest。

考虑朴素 \(dp\)

\(f_{i,j}\) 表示考虑到第 \(i\) 项时 \(\sum_{i=1}^n x_i=j\) 的最小值,显然有 \(f_{i,j}=\min\{f_{i-1,j-p}+a_{i,p}\}\),答案即为 \(f_{n,0}\)

由于 \(|j|\) 最大可达 \(nk\),所以复杂度是 \(O(n^2k^2)\) 的,显然不能通过。

直接优化 \(dp\) 是不太可能了,于是我们去考虑如何缩小 \(|j|\) 的范围。

答:直接随机。

考虑答案中 \(x_i\) 正数和负数的数量差距不会太多,所以将序列随机打乱后可以保证 \(x_i\) 的最大前缀和极大概率不会超过某个值 \(m\),最后的复杂度即为 \(O(nmk)\),于是将 \(m\) 设到一个不会超过时限的值即可。

什么?你问我证明?

实在不会证,然后看了眼官方题解。

Well, you just can run this idea on your computer and look at the values of the largest prefix sum that you are getting.
好吧,你只需在计算机上运行这个想法,看看你得到的前缀和最大值是多少。

读者自证不难。

代码

#include <bits/stdc++.h>
using namespace std;
#define int long long
mt19937 rnd(time(0));
const int mx=800;
int f[2][1605],n,k,a[35005][15],rd[35005];
signed main() {
	ios::sync_with_stdio(false);
	cin.tie(nullptr),cout.tie(nullptr);
	cin>>n>>k;
	for(int i=1;i<=n;i++)
		rd[i]=i;
	shuffle(rd+1,rd+1+n,rnd);
	for(int i=1;i<=n;i++){
		for(int j=-k;j<=k;j++){
			cin>>a[rd[i]][3+j];
		}
	}
	memset(f,0x3f,sizeof(f));
	f[0][mx]=0;
	for(int v,i=1;i<=n;i++){
		v=i&1;
		for(int j=-mx;j<=mx;j++){
			f[v][mx+j]=0x3f3f3f3f3f3f3f3f;
			for(int l=-k;l<=k;l++){
				if(abs(j-l)<=mx)
					f[v][mx+j]=min(f[v][mx+j],f[v^1][mx+j-l]+a[i][3+l]);
			}
		}
	}
	cout<<f[n&1][mx];
	return 0; 
}
posted @ 2025-09-06 10:51  WuMin4  阅读(19)  评论(0)    收藏  举报