题解:【JROI-8】这是新历的朝阳,也是旧历的残阳
属实是太菜了,最开始还想歪了,这篇题解将以我的思考历程为起点来讲述这道题。
首先我们对于分段,因为中间可以放空段,序列又保持不下降,所以我最开始简单的认为对于负数我们让它加的尽可能少,对于正数我们让他加的尽可能多,把负数都放在第一段,把整数都放在最后一段,记 \(k\) 为最后一个负数(显然 \(0\) 放在正数更优),然后就可以得到一个错误的式子:
\[\begin{align*}
Ans
&= m \times \sum_{i=1}^{k} (a_i + 1)^2 + \sum_{i=k+1}^{n} \sum_{j=1}^{m} (a_i + j)^2 \\
&= m \times \sum_{i=1}^{k} (a_i + 1)^2 + (n - k) \times \sum_{i=k+1}^{n} a_i^2 + (m^2 + m) \times \sum_{i=k+1}^{n} a_i + (n - k) \times \sum_{i=1}^{m} i^2
\end{align*}
\]
读入时候预处理 \(\Sigma_{i=k+1}^n a^2\),\(\Sigma_{i=k+1}^n a\),\(\Sigma_{i=1}^m i^2\),然后计算是 \(O(1)\) 的。然而这样是错的。
在给负数加上一个数的时候,可能会出现把它加成了正数,而且绝对值要比原来还大,因此对于一个负数就要分成两类讨论,一类是段数低于 \(2 \times |a_i|\) 的我们还是放在第一段更优,多于这个值的我们则把它放在最后一段。
考虑枚举分段数量,更改指针 \(k\) 的定义为最后一个二倍绝对值大于分段数量的 \(a\),依旧预处理好我们所需要的正数平方和和负数平方和,以及完全平方公式打开后中间的一次项的和,然后转移贡献就好了。
需要注意的小点有可能有多个相同的数,所以转移要写成 while 而不是 if;从负数平方和中去掉贡献时因为取模可能会减成负数,所以要先加上模数;开 ll 就行,不需要开 ull。时间复杂度 \(O(N + M)\)。
\(Code\)
#include<bits/stdc++.h>
#define int long long
#define MAX 1000010
using namespace std;
inline int read()
{
int s=0,w=1;
char c=getchar();
while(!isdigit(c)) {if(c=='-') w=-1; c=getchar();}
while(isdigit(c)) s=(s<<1)+(s<<3)+(c^48),c=getchar();
return s*w;
}
namespace LgxTpre
{
const int mod=998244353;
int n,k,a[MAX];
int zbowsum,last,zsum2k;
int ans,ansf,ansz;
inline void solve()
{
n=read(),k=read();
for(int i=1;i<=n;++i)
{
a[i]=read();
if(a[i]<0) ansf=(ansf+(a[i]+1)*(a[i]+1)%mod)%mod,last=i;
else zbowsum=(zbowsum+(a[i]*a[i])%mod)%mod,zsum2k=(zsum2k+a[i]*2%mod)%mod;
}
for(int i=1;i<=k;++i)
{
while(abs(a[last])*2==i&&last>0)
ansf=(ansf+mod-((a[last]+1)*(a[last]+1)))%mod,
zbowsum=(zbowsum+(a[last]*a[last]%mod))%mod,
zsum2k=(zsum2k+a[last]*2%mod+mod)%mod,--last;
ans=(ans%mod+ansf%mod+zbowsum+zsum2k*i%mod+((n-last)*(i*i%mod)%mod))%mod;
}
cout<<ans;
}
}
signed main()
{
LgxTpre::solve();
return (0-0);
}