CF571A题解

· · 题解

前置知识

\Delta f(x)=f(x+1)-f(x) \sum_{x=a}^{b}\Delta f(x)=f(b+1)-f(a) x^{\underline{n}}=x(x-1)(x-2)\dots(x-n+1) x^{\underline{n}}=\Delta (\frac{x^{\underline{n+1}}}{n+1})

基础思路

容斥原理,先计算出所有情况,再减去不合法的情况。(也就是不符合两边之和大于第三边这个三角形规律)。

三边分别当作最长边,枚举分配给最长边的长度,可以轻松算出不合法方案数,时间复杂度 O(L)

代码其他题解写的非常清楚。

优化

可以发现这个算法瓶颈就在于有一个循环枚举了一遍长度。可以发现循环内的运算是很具有规律的,所以尝试一下简化。

A=a+b-c

for(int x=max(0,A);x<=L;x++){
    int y=min(L-x,x-A);
    ans-=(y+1)*(y+2)/2;
}

这段代码中把 x 看成自变量,-x+L,x-A 看成一次函数,y 取值的变化过程画在一个坐标系内,可以有所发现。

红色是 x-A,蓝色是 -x+L

- $L<x\Rightarrow A>L$,循环一次也不会执行,所以无事发生,答案不会有任何变化。 - $\frac{L}{2}\le x<L\Rightarrow 0\le A<L$,图象是这样的。 ![](https://cdn.luogu.com.cn/upload/image_hosting/f2v2ekve.png) 根据 $x=\frac{L+A}{2}$ 记 $lim=\frac{L-A}{2}$ 为转折点的纵坐标。可以分两段求和,先看上升段。 $$\sum_{y=0}^{lim}\frac{(y+1)(y+2)}{2}$$ $$=\sum_{y=2}^{lim+2}\frac{y(y-1)}{2}$$ $$=\sum_{y=2}^{lim+2}\frac{y^{\underline{2}}}{2}$$ $$=\sum_{y=2}^{lim+2}\Delta(\frac{x^{\underline{3}}}{6})$$ $$=\frac{(lim+3)^{\underline{3}}}{6}-\frac{2^{\underline{3}}}{6}$$ $$=\frac{(lim+3)^{\underline{3}}}{6}$$ 下降段反着看其实就是上升段,所以最终不合法方案数翻倍,再减重复计算的点 $lim$,不合法数量是 $$\frac{(lim+3)^{\underline{3}}}{3}-\frac{(lim+1)(lim+2)}{2}$$ - $0\le x<\frac{L}{2}\Rightarrow -L\le A<0$,图象是这样的。 ![](https://cdn.luogu.com.cn/upload/image_hosting/y0gfn8tq.png) 所有的减去黄色点就是绿色点。所有点刚才已经求出了,黄色点的求法类似。 $$\sum_{y=0}^{-1-A}\frac{(y+1)(y+2)}{2}$$ $$\cdots$$ $$=\frac{(2-A)^{\underline{3}}}{6}$$ 那么不合法方案数只需在上一条基础上减去它即可。 - $x<0\Rightarrow -A<L$ 最长边与其余两边的和之差大于 $L$,无论怎么分配也不可能构成一个三角形。所以直接 $ans\gets0$。 --- 接下来就是 $x$ 不是整数的情况了。显然只对 $0\le x<L$ 的两种情况有影响。 ![](https://cdn.luogu.com.cn/upload/image_hosting/rzt3zmwd.png) 可以发现其他不变,只是 $lim$ 变成了它的上一个点。并且,也不用担心 $lim$ 被重复计算了。所以 $x$ 不是整数的情况下,只需要对刚才第二、三种情况的式子稍加修改即可。 去掉 $$\frac{(lim+1)(lim+2)}{2}$$ 再改 $lim$。 $$\frac{(lim+3)^{\underline{3}}}{3}\to \frac{(lim+2)^{\underline{3}}}{3}$$ 理论时间复杂度 $O(1)$,可以通过大约 $L\le 10^{12}$ 的数据。因为太大会爆 `__int128`。 # 代码 ```cpp #include<bits/stdc++.h> using namespace std; typedef long long ll; ll _3(ll x){return x*(x-1)*(x-2);}//3次下降幂 ll a,b,c,L,ans; void figure(ll A,ll L){ ll lim=(L-A+1)/2;//上取整 double x=1.0*(L+A)/2; if(x<0){ans=0;return;} if(x>L)return; if((L+A)%2)ans-=_3(lim+2)/3;//判断x是否是整数 else{ ans-=_3(lim+3)/3; ans+=(lim+1)*(lim+2)/2; } if(x<1.0*L/2)ans+=_3(2-A)/6; } int main(){ scanf("%lld%lld%lld%lld",&a,&b,&c,&L); ans=_3(L+3)/6; figure(a+b-c,L); figure(b+c-a,L); figure(c+a-b,L); printf("%lld\n",ans); return 0; } ```