Atcoder Regular Contest 125 E - Snack(最小割轉化+貪心)
Preface:
這是生平第一道現場 AC 的 arc E,也生平第一次經歷了 performance \(\ge 2800\),甚至還生平第一次被 hb 拉到會議裡講題,講的就是這個題,然鵝比較尬的一點是我不知道騰訊會議開了白板之後不能看到電腦,導致我的 typora 沒人能看到……所以就暫且把我 typora 的內容整了整改成了一篇題解(
題意:
- 有 \(n\) 種零食,第 \(i\) 種零食有 \(a_i\) 個。
- 有 \(m\) 個人領這些零食,第 \(i\) 個人最多領 \(b_i\) 個同種零食
- 第 \(i\) 個人領零食的總數不能超過 \(c_i\)
- 求最多分出去多少零食
- \(n,m\le 2\times 10^5\)
首先考慮網路流建圖,我們新建源 \(S\) 匯 \(T\),將零食看作左部點 \(X_i\),人看作右部點 \(Y_i\),那麼我們可以建出以下幾類邊:
- \(S\to X_i\),容量 \(a_i\)
- \(X_i\to Y_j\),容量 \(b_j\)
- \(Y_i\to T\),容量 \(c_i\)
然後跑最大流就是答案。直接建圖複雜度爆炸,考慮最大流&最小割定理轉化為最小割問題。也就是割掉權值最小的邊集使得 \(S,T\) 不連通。下記 \(S\to X_i\) 為第一類邊,\(X_i\to Y_j,Y_i\to T\) 分別記作第二、三類邊。
考慮列舉保留多少個一類邊,記作 \(x\),那我們割掉的一類邊肯定是最小的 \(n-x\) 條,排序+字首和求解即可。對於一個 \(Y_j\),目前有用的邊肯定只剩下 \(x\) 條與 \(Y_j\) 相連的二類邊和 \(Y_j\) 連向匯點的三類邊。刪掉與 \(j\) 有關的邊的代價是 \(\min(c_j,b_j·x)\)。將所有右部點按照 \(\dfrac{c_j}{b_j}\) 排序後 \(\min\) 取到 \(c_j\) 的部分一定是一段字首,\(\min\) 取到 \(b_j·x\) 的部分肯定是對應部分的一個補集,也就是一段字尾。我們在列舉 \(x\) 的過程中 two pointers 找出這段字首,然後預處理出 \(b,c\)
時間複雜度 \(n\log n\)(如果 \(n,m\) 視作同階),注意精度,直接比較 double 可能會獲得 AC \(\times\) 26,WA \(\times\) 1 的好成績。
const int MAXN=2e5;
int n,m,ord[MAXN+5];
ll a[MAXN+5],b[MAXN+5],c[MAXN+5];
ll suma[MAXN+5],sumb[MAXN+5],sumc[MAXN+5];
bool cmp(int x,int y){return 1ull*c[x]*b[y]<1ull*b[x]*c[y];}
int main(){
scanf("%d%d",&n,&m);
for(int i=1;i<=n;i++) scanf("%lld",&a[i]);
for(int i=1;i<=m;i++) scanf("%lld",&b[i]);
for(int i=1;i<=m;i++) scanf("%lld",&c[i]);
for(int i=1;i<=m;i++) ord[i]=i;
sort(a+1,a+n+1);sort(ord+1,ord+m+1,cmp);
for(int i=1;i<=n;i++) suma[i]=suma[i-1]+a[i];
for(int i=1;i<=m;i++) sumb[i]=sumb[i-1]+b[ord[i]];
for(int i=1;i<=m;i++) sumc[i]=sumc[i-1]+c[ord[i]];
ll res=1e18;
for(int i=0;i<=n;i++){
int l=1,r=m,p=0;
while(l<=r){
int mid=l+r>>1;
if(1ll*b[ord[mid]]*i<=c[ord[mid]]) r=mid-1;
else p=mid,l=mid+1;
} //printf("%d %d\n",i,p);
chkmin(res,suma[n-i]+sumc[p]+1ll*(sumb[m]-sumb[p])*i);
} printf("%lld\n",res);
return 0;
}