1. 程式人生 > 其它 >21.7.2 t3

21.7.2 t3

tag:二分,容斥,組合計數


仔細觀察可以發現,對於一個方程來說,將 \(t\) 作為橫座標,解數看成縱座標,那麼會是一個上凸函式(而且是對稱的,但不重要)。眾所周知幾個上凸函式的和也是上凸函式,而上凸函式的頂點可以二分,所以可以分段然後每一段二分頂點。

問題變為如何求一個點的值。

簡單轉化一下,有 \(n\) 個球,放到 \(3\) 個盒子裡,第 \(i\) 個盒子只能裝 \(a_i\) 個球,求有多少種裝法。

可以容斥,列舉強制讓哪些盒子的球超過容量,容斥係數為 \((-1)^{|S|}\)

所以複雜度 \(O(n^2logm)\)

所以為什麼n只有50啊喂


#include<bits/stdc++.h>
using namespace std;

template<typename T>
inline void Read(T &n){
	char ch; bool flag=false;
	while(!isdigit(ch=getchar()))if(ch=='-')flag=true;
	for(n=ch^48;isdigit(ch=getchar());n=(n<<1)+(n<<3)+(ch^48));
	if(flag)n=-n;
}

typedef long long ll;

int n, a[5][5][55], mn[55], mx[55];

inline ll calc(int k){return 1ll*(k+2)*(k+1)/2;}

inline ll calc(int t, int pos){
    ll ans=0;
    for(int s=0; s<8; s++){
        int cnt=0, sum=0;
        for(int i=0; i<3; i++) if(s>>i&1) cnt++, sum += a[i+1][2][pos]-a[i+1][1][pos]+1;
        if(sum>t) continue;
        if(cnt&1) ans -= calc(t-sum);
        else ans += calc(t-sum);
    }
    return ans;
}

inline ll check(int t){
    ll res=0;
    for(int i=1; i<=n; i++) if(mn[i]<=t and t<=mx[i]) res += calc(t-mn[i],i);
    return res;
}

int bp[105];

inline ll solve(int l, int r){
    int L=l, R=r;
    while(l<r){
        int mid = l+r>>1;
        if(check(mid)<check(mid+1)) l = mid+1;
        else r = mid;
    }
    ll res=0;
    for(int i=l-2; i<=l+2; i++) if(L<=i and i<=R) res = max(res,check(i));
    return res;
}

int main(){
    // freopen("3.in","r",stdin);
    Read(n); int cnt=0;
    bp[++cnt] = -1;
    for(int T=1; T<=n; T++){
        for(int i=1; i<=2; i++) for(int j=1; j<=3; j++) Read(a[j][i][T]);
        for(int i=1; i<=3; i++) mn[T] += a[i][1][T], mx[T] += a[i][2][T];
        bp[++cnt] = mn[T]-1; bp[++cnt] = mx[T];
    }
    sort(bp+1,bp+cnt+1); cnt = unique(bp+1,bp+cnt+1)-bp-1;
    ll res=0;
    for(int i=1; i<cnt; i++) res = max(res,solve(bp[i]+1,bp[i+1]));
    cout<<res<<'\n';
    return 0;
}