題目大意
對於 種物品,每種物品各有一份,第 種物品的價格爲 ,價值爲 。定義除了第 種物品之外,選擇任意個物品,使得他們的價值總和對 取模後價值結果爲 的最小价格爲 ,當且僅當價值取模無法達到 時, 。
你需要輸出 個整數,第 行輸出 。
題解
可以發現,題目要求的是每種物品不取的時候所有不同價值的最小价格之和。最優化問題,考慮動態規劃,考慮 表示考慮前 個物品,取模後價值爲 的最小价格(若狀態無法達到則爲無窮大),轉移方程: 現在是要求第 種物品不能取得情況,因此我們考慮再做一個後綴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;
}