1 条题解

  • 0
    @ 2025-8-24 22:24:37

    自动搬运

    查看原文

    来自洛谷,原作者为

    avatar 君のNOIP。
    清新出题人

    搬运于2025-08-24 22:24:37,当前版本为作者最后更新于2020-09-19 15:19:35,作者可能在搬运后再次修改,您可在原文处查看最新版

    自动搬运只会搬运当前题目点赞数最高的题解,您可前往洛谷题解查看更多

    以下是正文


    p0:p_0: 讲个笑话,正解分块。


    数据点 141\sim 4O(2nn)O(2^nn) 暴力枚举子序列即可。


    数据点 1212k=0k=0

    合法的子序列均只有一个元素,答案显然为 i=1nai2\sum \limits _{i=1} ^n a_i^2


    数据点 5115\sim 11O(n2)O(n^2) 暴力递推。

    先不考虑不合法的子序列,即 k=n1k=n-1 的情况,将 i=1xsi\sum \limits _{i=1} ^ x s_ii=1xsi\prod \limits _{i=1} ^ x s_i 分开看。

    fnf_n 为考虑前 nn 个元素的答案。

    显然 fnf_n 表示的答案中由 2n12^n -1 个非空子集组成,设第 ii 个子集的值为 wi×biw_i \times b_i

    显然 fnf_n 的形式为 i=12n1wi×bi\sum \limits _{i=1} ^ {2^n-1} w_i \times b_i

    再设 gn=i=12n1big_n =\sum \limits _{i=1} ^ {2^n-1} b_i

    那么 fn+1f_{n+1} 表示的答案由 33 个部分组成:

    • {an+1}\{ a_{n+1} \} 的值。

    • fnf_n 表示的 2n12^n - 1 个子集的值。

    • fnf_n 表示的 2n12^n - 1 个子集分别加上元素 an+1a_{n+1} 的值。

    那么我们分别考虑这 33 个部分可以得到:

    $\begin{aligned}f_{n+1} & = a_{n+1}^2 + f_n + \sum \limits _{i=1} ^ {2^n-1} (w_i+a_{n+1}) \times (b_i \times a_{n+1}) \\ & = a_{n+1}^2 + f_n + \sum \limits _{i=1} ^ {2^n-1} (w_i \times b_i \times a_{n+1} + b_i \times a_{n+1}^2 ) \\ & = a_{n+1}^2 + f_n + f_n \times a_{n+1} + g_n \times a_{n+1}^2 \\ & = a_{n+1}^2 \times (g_n + 1) + f_n \times ( a_{n+1} + 1 ) \end{aligned}$

    关于 gng_n,根据乘法结合律,同理考虑 33 个部分易得:

    $\begin{aligned}g_{n+1} &= a_{n+1} + g_n + g_n \times a_{n+1} \\ & = a_{n+1} \times (g_n+1) + g_n \end{aligned}$

    $f_{n+1} = a_{n+1}^2 \times ( g_n + 1) + f_n \times ( 1 + a_{n+1} )$

    于是我们就可以 O(n)O(n) 同时递推两个式子,答案即为 fnf_n

    而现在我们需要考虑去掉不合法的序列。

    改设 fi,jf_{i,j}ip1pxji \le p_1 \le p_x \le j 的所有子序列的值之和, gi,jg_{i,j} 同理。

    发现由上式的原理可以由 fi,jf_{i,j} 推至 fi1,jf_{i-1,j}fi,j+1f_{i,j+1}gi,jg_{i,j} 同理。

    根据基本容斥原理,答案为 $\sum \limits _{i=1} ^{n-k} f_{i,i+k} - \sum \limits _{i=2} ^{n-k} f_{i,i+k-1}$。


    数据点 151715\sim 17O(nlogn)O(nlogn) 逆元。

    根据前文中推出的式子,同理:

    $f_{i,j} = f_{i+1,j} \times (a_i + 1 ) + a_i ^ 2 \times g_{i+1,j} $

    移项得:

    $f_{i+1,j} = ( f_{i,j} - a_i ^ 2 \times g_{i+1,j} ) / ( a_i + 1 )$

    同理 gi,jg_{i,j} 可推得 gi+1,jg_{i+1,j}

    由于 mod=109+7mod = 10^9+7, 是质数,我们只需用逆元即可运用类似双指针的方法推得答案。


    数据点 182218\sim 22O(nlogn)O(nlogn) 合并区间 &\&倍增。

    我们是否能由 fi,lf_{i,l}fl+1,jf_{l+1,j} 合并得 fi,jf_{i,j} 呢?

    方法与由 fi,jf_{i,j} 推至 fi,j+1f_{i,j+1} 是一样的,同理可推得:

    $f_{i,j} = f_{i,l} \times (g_{l+1,j} + 1 ) + f_{l+1,j} \times (g_{i,l} + 1 ) $

    $g_{i,j} = g_{i,l} + g_{l+1,j} + g_{i,l} \times g_{l+1,j} $

    接下来只要利用倍增的思想,预处理出 fi,i+2j1,gi,i+2j1f_{i,i+2^j-1},g_{i,i+2^j-1} ,再倍增求出 fi,i+kf_{i,i+k}fi+1,i+kf_{i+1,i+k} 即可。

    时空复杂度都是 O(nlogn)O(nlogn)


    数据点 232523\sim 25

    正解 O(n)O(n),但由于这是乐多赛制缺少试错的机会,且作为信心赛题目,良心出题人放了很多略加优化的 O(nlogn)O(nlogn) 乱搞做法通过。

    • 乱搞一:

    仍然倍增,但空间会炸,于是我们类似分块,以 1010 为块大小,再倍增记录每块的信息即可。空间上足够了,但是由于常数巨大,经测试,#23 很难卡过去。

    • 乱搞二:

    据说有方法可以实现膜数非质数逆元,不太了解这里就不讲了,但时间复杂度仍然要 O(nlogn)O(nlogn)

    • 乱搞三:

    区间合并,区间查询,显然可以线段树,时间复杂度 O(logn)O(logn),空间复杂度 O(n)O(n)

    注意由于要同时合并 fi,j,gi,jf_{i,j},g_{i,j},求和的时候要是分两个函数求解,则会产生重复递归(和 T1 不加 map 记忆化搜索是一个道理 ),时间复杂度会退化成 O(n2)O(n^2)

    我的方法是直接用 pair<> 作函数类型,如果有其它方法欢迎评论提出。

    但由于线段树巨大的常数还是有一定风险的,事实上出题人写的线段树这 33 个点一个都过不去。

    • 正解:分块。

    大家都知道分块一般是 n1.5n^{1.5} 的,但这里不太一样。

    因为我们要求的区间长度只有 kkk+1k+1

    所以我们可以以 kk 为块的大小,设 ii 所在块左边界为 lil_i,右边界为 rir_i

    则我们只需预处理出 fli,i,fi,rif_{l_i,i},f_{i,r_i},则计算答案的时候每次也只要合并两个区间 fi,ri,flj,jf_{i,r_i},f_{l_j,j} 即可。

    Code:

    #include<iostream>
    #include<cstdio>
    #include<algorithm>
    #include<cstring>
    #include<cmath>
    #include<map>
    using namespace std;
    #define G() Cr = getchar()
    #define LL long long
    LL Xr, Fr; char Cr;
    inline LL rd() {
    	Xr = 0, Fr = 1, G();
    	while(Cr < '0' || Cr > '9') {if(Cr == '-') Fr = -1; G();}
    	while(Cr >= '0' && Cr <= '9') Xr = (Xr<<1) + (Xr<<3) + (Cr&15), G();
    	return Xr * Fr;
    }
    #define MAX_N 1000005
    LL n, k, mod;
    LL va[MAX_N], f[MAX_N][3], b[MAX_N][3], l[MAX_N], r[MAX_N], ans, s; // 0 -> from left ; 1 -> from right
    
    LL work( LL L, LL R ) {
    	if( L == R ) return va[L] * va[L] % mod;
    	if( l[L] == l[R] ) return f[R][0];
    	LL x, y, A, B;
    	x = f[L][1], y = f[R][0];
    	A = b[L][1], B = b[R][0];
    	return ( x + y + A * y % mod + B * x % mod ) % mod;
    }
    
    int main() {
    	n = rd(), k = rd(), mod = rd();
    	for( int i = 1; i <= n; i++ ) va[i] = rd();
    	if(!k) {
    		for( int i = 1; i <= n; i++ ) ans = ( ans + va[i] * va[i] ) % mod;
    		cout << ans;
    		return 0;
    	}
    	for( int i = 1; i <= n; i++ ) l[i] = ( i - 1 ) / k * k + 1, r[i] = ( ( i - 1 ) / k + 1 ) * k;
    	for( int i = 1; i <= n; i++ ) {
    		if( i == l[i] ) b[i][0] = va[i], f[i][0] = va[i] * va[i] % mod;
    		else {
    			f[i][0] = (va[i] * va[i] % mod * ( 1 + b[i-1][0] ) % mod + f[i-1][0] * ( 1 + va[i] ) % mod ) % mod;
    			b[i][0] = ( b[i-1][0] * ( 1 + va[i] ) + va[i] ) % mod;
    		}
    	}
    	for( int i = n; i >= 1; i-- ) {
    		if( i == r[i] ) b[i][1] = va[i], f[i][1] = va[i] * va[i] % mod;
    		else {
    			f[i][1] = (va[i] * va[i] % mod * ( 1 + b[i+1][1] ) % mod + f[i+1][1] * ( 1 + va[i] ) % mod ) % mod;
    			b[i][1] = ( b[i+1][1] * ( 1 + va[i] ) + va[i] ) % mod;
    		}
    	}
    	for( int i = k + 1; i < n; i++ )
    		ans = ( ans + work(i-k,i) - work(i-k+1,i) + mod ) % mod;
    	ans = ( ans + work(n-k,n) ) % mod;
    	cout << ans;
    }
    
    • 1

    信息

    ID
    5991
    时间
    1000ms
    内存
    128MiB
    难度
    5
    标签
    递交数
    0
    已通过
    0
    上传者