Loj#6039-「雅禮集訓 2017 Day5」珠寶【四邊形不等式,dp】
阿新 • • 發佈:2021-12-22
正題
題目大意
有\(n\)個物品,第\(i\)個費用為\(w_i\),價值為\(v_i\),對於\(k\in[1,m]\)求費用為\(m\)時能獲得的最大價值。
\(1\leq n\leq 10^6,1\leq m\leq 5\times 10^4,1\leq w_i\leq 300,1\leq v_i\leq 10^9\)
解題思路
好早以前寫的不過不知道為啥錯了,現在來補個新的。
\(w_i\)很小,考慮以其為突破口,顯然地我們可以把\(w_i\)相同的按照\(v_i\)從大到小排序,那麼對於每個\(w_i\),我們就可以選擇若干個。
設\(f_{i,j}\)
(\(s_{i,z}\)表示\(w=i\)的物品中前\(z\)大的價值和)
這個式子很難用常規的優化,但是可以用四邊形不等式。至於證明,我們有\(w_{i,j}=s_{j-i}\)
要證明
然後因為\(s_{i+1}-s_{i}\)是遞減的,所以成立。
那麼我們現在對於每個列舉的\(w=i\)
然後對於每一組我們都用四邊形不等式優化,不過我忘了優化的方法了,還是記一下吧:
對於所有的可能的決策我們用一個單調佇列記錄,順帶記錄\(z_i\)表示佇列裡第\(i\)個決策和第\(i+1\)個決策的交叉點(在\(z_i\)之前\(q_{i}\)更優,\(z_i\)以之後\(q_{i+1}\)更優)。
然後每次彈出佇列前面的來找答案,加入的時候我們就二分出隊尾和新加入的決策交換點,然後一直彈尾部直到不交叉。
時間複雜度:\(O(mw\log m)\)
code
#include<cstdio> #include<cstring> #include<algorithm> #include<vector> #define ll long long using namespace std; const ll N=5e4+10; ll n,m,g,f[2][N],q[N],z[N]; vector<ll> w[310]; bool cmp(ll x,ll y) {return x>y;} ll calc(ll i,ll j,ll p,ll k) {return f[!g][i*p+k]+w[p][j-i-1];} ll bound(ll i,ll j,ll p,ll k){ ll l=i+1,r=(m-k)/p; while(l<=r){ ll mid=(l+r)>>1; if(calc(i,mid,p,k)<calc(j,mid,p,k)) l=mid+1; else r=mid-1; } return l; } signed main() { freopen("jewelry.in","r",stdin); freopen("jewelry.out","w",stdout); scanf("%lld%lld",&n,&m); for(ll i=1,c,v;i<=n;i++){ scanf("%lld%lld",&c,&v); w[c].push_back(v); } g=0; for(ll p=1;p<=300;p++){ if(w[p].empty())continue;g^=1; memcpy(f[g],f[!g],sizeof(f[g])); // memset(f[g],0,sizeof(f[g])); sort(w[p].begin(),w[p].end(),cmp); while(w[p].size()<=m/p)w[p].push_back(0); for(ll i=1;i<w[p].size();i++)w[p][i]+=w[p][i-1]; for(ll k=0;k<p;k++){ ll head=1,tail=0; for(ll i=0;i*p+k<=m;i++){ while(head<tail&&z[head]<=i)head++; if(head<=tail)f[g][i*p+k]=max(f[g][i*p+k],calc(q[head],i,p,k)); while(head<tail&&z[tail-1]>=bound(i,q[tail],p,k))tail--; z[tail]=bound(i,q[tail],p,k);q[++tail]=i; } } } for(ll i=1;i<=m;i++) printf("%lld ",f[g][i]); return 0; }