正题
题目链接:http://codeforces.com/contest/1603/problem/C
题目大意
定义一个序列aaa的f(a)f(a)f(a)为你每次可以将序列中的一个数zzz分裂成x+y=zx+y=zx+y=z,然后再把x,yx,yx,y放回原来的位置,然后f(a)f(a)f(a)表示把aaa变成不降序列的最少操作次数
给出一个长度为nnn的序列aaa,求它所有子区间的fff值的和。
1≤n,ai≤1051\leq n,a_i\leq 10^51≤n,ai≤105
解题思路
显然的每个数字aia_iai最终肯定是分解为k−1k-1k−1个⌊aik⌋\lfloor\frac{a_i}{k}\rfloor⌊kai⌋和一个⌈aik⌉\lceil\frac{a_i}{k}\rceil⌈kai⌉。
然后我们对于一个序列可以从右往左每个选择能分解的最小的次数来分肯定是最优的。
那么考虑暴力的dpdpdp,设fi,jf_{i,j}fi,j表示现在第iii个,所有左端点为iii的区间中aia_iai分解的最前面那个为kkk的方案,然后倒着转移就好了。
考虑到jjj肯定是某个⌊aik⌋\lfloor\frac{a_i}{k}\rfloor⌊kai⌋,而⌊aik⌋\lfloor\frac{a_i}{k}\rfloor⌊kai⌋最多只有2ai2\sqrt a_i2ai种取值,所以我们可以只枚举这些取值就好了。
时间复杂度:O(nai)O(n\sqrt a_i)O(nai),略微卡常
code
#include<cstdio>
#include<cstring>
#include<algorithm>
#define ll long long
using namespace std;
const ll N=1e5+10,P=998244353;
ll T,n,ans,a[N],f[2][N],g[2][N];
signed main()
{scanf("%lld",&T);while(T--){scanf("%lld",&n);for(ll i=1;i<=n;i++)scanf("%lld",&a[i]);ll o=0;g[o][a[n]]=1;ans=0;for(ll i=n-1;i>=1;i--){o^=1;ll m=a[i+1];for(ll l=1,r;l<=m;l=r+1){r=m/(m/l);ll x=m/l,w=(a[i]+x-1)/x,k=a[i]/w;f[o][k]+=f[!o][x]+(w-1)*g[!o][x];g[o][k]+=g[!o][x];ans+=f[!o][x];ans%=P;f[!o][x]=g[!o][x]=0;}g[o][a[i]]++;}for(ll l=1,r;l<=a[1];l=r+1)r=a[1]/(a[1]/l),(ans+=f[o][a[1]/l])%=P,f[o][a[1]/l]=g[o][a[1]/l]=0;printf("%lld\n",ans);}return 0;
}