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;
}