NOI.AC #713. 魔术 题解

题目传送门

题目大意: n n n 个物品,背包大小为 m m m,每个物品有体积 v [ i ] v[i] v[i] 和代价 c [ i ] c[i] c[i] 两个属性,设 f [ i ] [ j ] f[i][j] f[i][j] 表示不选物品 i i i,用剩下的物品凑出体积 j + k m   ( k ≥ 0 ) j+km~(k\geq 0) j+km (k0) 的最小代价,对于每个 i ∈ [ 1 , n ] i\in [1,n] i[1,n],求出 ∑ j = 0 m − 1 f [ i ] [ j ] \sum_{j=0}^{m-1} f[i][j] j=0m1f[i][j]

题解

先考虑普通的dp,设 g [ i ] [ j ] g[i][j] g[i][j] 为前 i i i 个物品凑出体积 j + k m j+km j+km 的最小代价,那么有:
g [ i ] [ j ] = min ⁡ ( g [ i ] [ j ] , g [ i − 1 ] [ j − v [ i ] ] + c [ i ] ) g[i][j]=\min(g[i][j],g[i-1][j-v[i]]+c[i]) g[i][j]=min(g[i][j],g[i1][jv[i]]+c[i])

其中, j − v [ i ] j-v[i] jv[i] 在模 m m m 意义下计算。

像这样从前往后dp一次得到 g g g,再从后往前dp一次得到 h h h,那么每次将 g [ i − 1 ] g[i-1] g[i1] h [ i + 1 ] h[i+1] h[i+1] 合并一下就能得到 f [ i ] f[i] f[i],这样做时间复杂度为 O ( m n 2 ) O(mn^2) O(mn2)

发现我们并不关心每个物品的加入顺序,于是考虑线段树分治,设 f ( l , r ) [ j ] f(l,r)[j] f(l,r)[j] 表示不选区间 [ l , r ] [l,r] [l,r] 内的物品,凑出体积 j + k m j+km j+km 的最小代价,那么物品 i i i 会对每个管理区间不包含 i i i 的节点造成贡献,而这样的节点数是 O ( log ⁡ n ) O(\log n) O(logn) 的,也就是说,每个 i i i 只会被加入 log ⁡ n \log n logn 次,而加入一次的复杂度为 O ( m ) O(m) O(m),一共有 n n n 个物品,那么总时间复杂度为 O ( n m log ⁡ n ) O(nm\log n) O(nmlogn)

代码如下:

#include <cstdio>
#include <cstring>
#include <algorithm>
using namespace std;
#define maxn 20010
#define maxm 2010
#define inf 999999999

int n,m,v[maxn],c[maxn];
int f[maxn<<1][maxm],t=0,g[maxm];
int add(int x){return x>=m?x-m:x;}
void trans(int from,int to,int l,int r)
{
	for(int i=0;i<m;i++)g[i]=f[to][i]=f[from][i];
	for(int i=l;i<=r;i++){
		for(int j=0;j<m;j++)f[to][add(j+v[i])]=min(f[to][add(j+v[i])],g[j]+c[i]);
		memcpy(g,f[to],m<<2);
	}
}
void solve(int l,int r,int id)
{
	if(l==r){
		long long tot=0;
		for(int i=0;i<m;i++)if(f[id][i]>400000000)tot--;
		else tot+=f[id][i]; printf("%lld\n",tot);
		return;
	}
	int mid=l+r>>1;
	t++;trans(id,t,mid+1,r);solve(l,mid,t);
	t++;trans(id,t,l,mid);solve(mid+1,r,t);
}

int main()
{
	scanf("%d %d",&n,&m);
	for(int i=1;i<=n;i++)scanf("%d %d",&v[i],&c[i]);
	for(int i=1;i<m;i++)f[0][i]=inf;
	solve(1,n,0);
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值