题目大意
对于 种物品,每种物品各有一份,第 种物品的价格为 ,价值为 。定义除了第 种物品之外,选择任意个物品,使得他们的价值总和对 取模后价值结果为 的最小价格为 ,当且仅当价值取模无法达到 时, 。
你需要输出 个整数,第 行输出 。
题解
可以发现,题目要求的是每种物品不取的时候所有不同价值的最小价格之和。最优化问题,考虑动态规划,考虑 表示考虑前 个物品,取模后价值为 的最小价格(若状态无法达到则为无穷大),转移方程: 现在是要求第 种物品不能取得情况,因此我们考虑再做一个后缀dp。定义 表示考虑 个物品,取模后价值为 的最小价格(若状态无法达到则为无穷大),转移方程: 这两个转移均为线性的,因而两个动态规划数组可以在 的时间内求出。接下来我们开始考虑如何求出不取第 个物品时的dp数组(即不考虑第 种物品的情况下,取模后价值为 的最小价格 (若状态无法达到则为无穷大)),可以发现,通过 和 可以用 的时间内得到,过程如下:
如此我们得到了一个时间复杂度为 的做法。
接下来考虑进一步优化这个过程。刚刚我们的操作是从前往后算,从后往前算,然后中途将两个dp数组合并,这意味着其实我们并不需要按顺序从前往后将第 个物品加入dp数组。
最开始,我们用前缀和求数组中某一段的和,后来我们学会了线段树。那我们能否沿用这种思想?定义 表示不取 之间的物品,取模后价值为 的最小价格(若状态无法达到则为无穷大),显然边界 为一个为全无穷大的数组。
在计算完 后,我们考虑分治,令 ,尝试计算 ,这个过程可以在原来 的基础上,将 种物品的贡献加进去。计算 时,同理将 种物品的贡献加进去。
注意这个过程中,由于我们每种物品只能有一个,因此要考虑01揹包的降维转移或者用滚动数组的方式保证同一个物品不会被取多次。
考虑这个过程的时间复杂度,由于每次计算的区间长度减半,因而共有 层,换言之,对于 种物品,每种物品最多被加入另一个序列中 次,而每次加入时更新dp数组的转移时间复杂度为 。因而总时间复杂度为 说起来麻烦其实代码很好打……
#include<bits/stdc++.h>
using namespace std;
#define maxn 20010
#define maxm 2010
#define inf 0x3f3f3f3f
int n,m,a[maxn],c[maxn];
void solve(int l,int r,int *t){
if(l==r){
long long ans=0;
for(int i=0;i<m;i++)
if(t[i]!=inf) ans+=t[i];
else ans--;
printf("%lld\n",ans);
return ;
}
int mid=(l+r)>>1;
int f[maxm<<1];
memset(f,63,sizeof f);f[0]=0;
for(int i=0;i<m;i++) f[i]=t[i];
for(int i=mid+1;i<=r;i++){
for(int j=m-1;j>=0;j--)
if(f[j]!=inf) f[j+a[i]]=min(f[j+a[i]],f[j]+c[i]);
for(int j=m;j<=m-1+a[i];j++)
f[j-m]=min(f[j-m],f[j]),f[j]=inf;
} solve(l,mid,f);
memset(f,63,sizeof f);f[0]=0;
for(int i=0;i<m;i++) f[i]=t[i];
for(int i=l;i<=mid;i++){
for(int j=m-1;j>=0;j--)
if(f[j]!=inf) f[j+a[i]]=min(f[j+a[i]],f[j]+c[i]);
for(int j=m;j<=m-1+a[i];j++)
f[j-m]=min(f[j-m],f[j]),f[j]=inf;
} solve(mid+1,r,f);
}
int f[maxm];
int main(){
scanf("%d%d",&n,&m);
for(int i=1;i<=n;i++)
scanf("%d%d",&a[i],&c[i]);
memset(f,63,sizeof f);
f[0]=0;solve(1,n,f);
return 0;
}