题解:【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);
}
posted @ 2022-10-24 21:32  LgxTpre  阅读(81)  评论(0编辑  收藏  举报