前缀和
前言
- 鸣谢学长Kersen
- 这两天推式子推的我挺快乐的的就,记录一下自己推数学式子的过程
- updata on :2020.12.3
修改了自己写炸的latex顺便再加一个题CF1422C难受,没有嫖到学长的学习资料还是有点小失落,但貌似我可以跟别人嫖一下
前缀和聚集地
定义一个函数
\(s(l,r) = \sum\limits_{i = l}^{r}a_i\times \sum\limits_{i = l}^{r}b_i\)
要求
\(\sum\limits_{l = 1}^{n}\sum\limits_{r = l}^{n}s(l,r)\)
1. 可以凑到一起,得到这样一个式子
2. 进一步讲里面的化简
令\(suma[]\)为\(a\)的前缀和, \(sumb[]\)为\(b\)的前缀和
将最后一个\(\sum\limits_{i = l}^{r} b_i\)变形为\(sumb[r]-sumb[l-1]\)
3.得到一个(消去一重循环)
\(sumb[]\)数组可以通过输入的时候预处理来解决,考虑再消去一重循环,发现\(\sum\limits_{i = l}^{r}a_i\)这里也可以预处理出来,就
4.进而得到了下面的式子
发现现在的时间复杂度已经由\(n^4\) 降到了\(n^2\),但是仍然不够优秀,再次考虑再去消去一重循环将括号内的式子进行处理
令
- \(qz[i]\)为\(suma[i]\times sumb[i]\)的前缀和
- \(qza[i]\) 为\(suma[i]\)的前缀和
- \(qzb[i]\) 为\(sumb[i]\)的前缀和
5.可以将给式子这样套一个括号
\(\sum\limits_{ l = 1}^{n}(\sum\limits_{r = l}^{n}(suma[r]\times sumb[r] + suma[l-1]\times sumb[l-1] - suma[r] \times sumb[l - 1] - sumb[r] \times suma[l - 1])\)
6.式子就变成了
\(\sum \limits_ { l = 1}^{n}\left[\\(qz[n] -qz[l-1]) + \\(n - l +1)\times (suma[l - 1]\times sumb[l-1]) \\- sumb[l-1] \times (qza[n]-qza[l-1])\\ - suma[l-1] \times (qzb[n]-qzb[l-1]) \right]\)
code
#include <iostream>
#include <cstdio>
#include <cstring>
#include <string>
#define int long long
using namespace std;
const int N = 5e5+100;
const int mod = 1e9+7;
int read(){
int s = 0 ,f = 1; char ch = getchar();
while(ch < '0'||ch > '9'){if(ch == '-') f = -1 ; ch = getchar();}
while(ch >= '0'&&ch <= '9'){s = s * 10 + ch - '0'; ch = getchar();}
return s * f;
}
int a[N] ,b[N];
int suma[N] ,sumb[N];
int qz[N], qza[N],qzb[N];
signed main(){
int n = read();
for(int i = 1 ; i <= n ;i++) {
a[i] = read();
suma[i] = (suma[i - 1] + a[i]) % mod;
}
for(int i = 1 ; i <= n ;i++ ) {
b[i] = read();
sumb[i] = (sumb[i - 1] + b[i]) % mod;
}
for(int i = 1 ; i <= n ;i++) {
qza[i] = (qza[i - 1] + suma[i]) % mod;
qzb[i] = (qzb[i-1] + sumb[i]) % mod;
qz[i] = (qz[i - 1] + (suma[i] * sumb[i]) % mod) % mod;
}
int ans = 0;
for(int i = 1 ; i <= n ;i++) {
int ans1 = ( (qz[n] - qz[i - 1] + mod) % mod) ;
int ans2 = ((n - i + 1) * ((suma[i - 1] * sumb[i - 1]) % mod) + mod) % mod ;
int ans3 = ( suma[i - 1] * ( (qzb[n] - qzb[i - 1] + mod) % mod ) + mod ) % mod;
int ans4 = ( sumb[i - 1] * ( (qza[n] - qza[i - 1] + mod) % mod ) + mod ) % mod;
ans = (ans + ans1 + ans2 - ans3 - ans4 + mod) % mod;
}
cout << (ans + mod) % mod;
return 0;
}
不鸽了
因为是学姐的个人题,就不粘链接了
简述题意输入一个\(n\) ,下面一行输入\(n\)个整数,求
1. 对于括号里的式子可以很简单的用完全平方公式化简
得到
2.仍然还是那个方法套个括号
令
- \(sum_i\)为\(a_i\)的前缀和
- \(pow_i\)为\(a_i^2\)的前缀和
- \(sumpow_i\)为\(pow_i\)的前缀和
化简得到
就可以得到一个\(O(n)\)的算法,但是我也不知道为啥考场写了个这么奇怪的代码
#include <iostream>
#include <cstdio>
#include <cstring>
#include <string>
#define int long long
using namespace std;
const int N = 1e6+100;
const int mod = 998244353 ;
inline int read(){
int s = 0 ,f = 1; char ch = getchar();
while(ch < '0'||ch > '9'){if(ch == '-') f = -1 ; ch = getchar();}
while(ch >= '0'&&ch <= '9'){s = s * 10 + ch - '0'; ch = getchar();}
return s * f;
}
int w[N],pow[N],sum[N],sum_pow[N];
int ans = 0;
signed main(){
int n = read() ;
for(int i = 1 ; i <= n ;i++) {
w[i] = read();
sum[i] = (sum[i] + (sum[i-1] + w[i]) % mod) % mod;
pow[i] = (w[i] % mod * w[i] % mod) % mod;
sum_pow[i] = (sum_pow[i-1] + pow[i]) % mod;
}
ans = ans + (sum_pow[n] * (n - 1) + mod) % mod ;
for(int i = 1 ; i < n ;i++){
ans += mod;
ans = (ans - 2 * w[i] * (sum[n] -sum[i] + mod)) % mod;
}
printf("%lld",(ans+ mod) % mod );
return 0;
}
学长更改题面,我读错题导致我造了一个题推出另一个题挺有意思的式子,先写在前面
我读为:
读入一个数字串,可以从前面或者后面截取任意长度的一个串求出所有情况,对长度大于1的串,除最后一个数外都乘10,求总和对 1e9+7取模
打个表:
1 2 3 4 5
就会得到这样一个表
\((1) + (10 + 2) +( 10 + 20 + 3 )+ (10 + 20 + 30 + 4)+ (10 + 20 + 30 + 40 + 5)\)
\((2) + (20 + 3) +(20 + 30 + 4 )+ (20 + 30 + 40 + 5)\)
\((3) + (30 + 40) +(30 + 40 + 5)\)
\((4) + (40 + 5)\)
\((5)\)
就会很快乐的得到这样一个式子
令
- 数字串长为\(len\)
- 每一位上的数\(\times\)之后为\(ten[i]\)
然后样例二过不去,我就知道坏事了,我读错题了,心态当场崩溃,后面的暴力也不想写了,好吧,自己还是考场心态调整的不行
放出自己读错题的代码
#include <iostream>
#include <cstdio>
#include <cstring>
#include <string>
#define int long long
using namespace std;
const int N = 2e6+100;
const int mod = 1e9+7;
int read(){
int s = 0 ,f = 1; char ch = getchar();
while(ch < '0'||ch > '9'){if(ch == '-') f = -1 ; ch = getchar();}
while(ch >= '0'&&ch <= '9'){s = s * 10 + ch - '0'; ch = getchar();}
return s * f;
}
char s[N];
int num[N];
int ten[N];
int sum[N];
int sum_10[N];
signed main(){
// freopen(".in","r",stdin);
// freopen(".out","w",stdout);
cin>>s;
int len = strlen(s);
for(int i = 0 ; i < len ; i++ ) {
num[i + 1] = s[i]-'0';
ten[i + 1] = num[i + 1] * 10 ;
}
int ans = 0 ;
for(int i = 1 ; i <= len ;i++) {
int ans1 = (num[i] * i) % mod;
int ans2 = (ten[i] * (len - i) * i) % mod;
ans = ( ans + (ans1 + ans2) % mod ) % mod;
}
cout << ans % mod;
}
然而真实的题面意思是这样的
- \(len\)仍然为这个数字串的长度
- 输入一个数字串,你可以从任意的地方抽取一块连续的部分,剩下的部分可以拼接到一起,然后每个数\(\times 10^{len-i}\) 求出所有情况的的和, 对1e9+7取模
Solution :
答案可以得到这样一个式子
令- \(f(l,r)\)表示字串\([L,R]\)组成的十进制数
考虑枚举删除的子串的最后一位\(x\)得到的十进制数的和为:
好吧我抄的学长博客
//知识点:瞎搞
/*
By:Luckyblock
*/
#include <cctype>
#include <cstdio>
#include <cstring>
#include <algorithm>
#define LL long long
const int kN = 2e6 + 10;
const LL mod = 1e9 + 7;
//=============================================================
int n;
char s[kN];
LL ans, sum, pow10[kN], suf[kN];
//=============================================================
inline int read() {
int f = 1, w = 0; char ch = getchar();
for (; !isdigit(ch); ch = getchar()) if (ch == '-') f = -1;
for (; isdigit(ch); ch = getchar()) w = (w << 3) + (w << 1) + (ch ^ '0');
return f * w;
}
//=============================================================
int main() {
scanf("%s", s + 1);
n = strlen(s + 1);
pow10[0] = 1;
for (int i = 1; i <= n; ++ i) {
pow10[i] = pow10[i - 1] * 10ll % mod;;
}
for (int i = n; i >= 1; -- i) {
suf[i] = suf[i + 1];
suf[i] += (s[i] - '0') * pow10[n - i] % mod;
suf[i] %= mod;
}
LL val = 0;
for (int i = 1; i <= n; ++ i) {
ans += sum * pow10[n - i] % mod + (i - 1) * suf[i] % mod;
ans %= mod;
val = (10ll * val % mod + s[i] - '0') % mod;
sum = (sum + val) % mod;
}
printf("%lld\n", ans);
return 0;
}
我的做法比较……难搞……我自己也不会写这个数学公式……就是前缀 + 后缀维护答案感觉自己是sb
#include <iostream>
#include <cstdio>
#include <cstring>
#include <string>
#define int long long
using namespace std;
const int N = 2e6+100;
const int mod = 1e9+7;
char a[N];
int num[N];
int pow[N];
int sum_pow[N];
int qian[N];
int hou[N];
int sum_qian[N];
signed main(){
cin>>a;
int len = strlen(a);
pow[0] = 1;
for(int i = 1 ; i <= len ;i++) {
pow[i] = (pow[i - 1] * 10) % mod;
num[i] = a[i-1] - '0';
}
for(int i = 1 ; i <= len ;i++) {
qian[i] = ((qian[i-1] * 10) % mod + num[i]) % mod;
sum_qian[i] =( sum_qian[i - 1 ] + qian[i] ) % mod;
}
for(int i = len ; i >= 1;i-- ) {
hou[i] = hou[i+1] + ( num[i] * pow[len - i] ) % mod;
}
int ans = 0;
for(int i = len ; i >= 1 ;i--) {
int number = (((sum_qian[i - 1] * pow[ len - i ]) % mod
+ (hou[i + 1] * i) % mod) % mod) % mod;
ans = (ans + number) % mod;
}
cout << ans % mod;
return 0;
}
写的前面
- \(\text{mod}\) 后出现减法算法不加 \(\text{mod}\) 出现负数修了半天我是sb。
- 在预处理递推过程中会出现负下标,就会出现一些很奇怪的错误,还有就 \(2^0 = 1\)
其实负下标在我这里没太大的影响,就是会造成\(1\)的精度缺失,因为两个\(\frac{1}{2}\)我都没加上。 - 注释里其实是有彩蛋的,自己感觉自己写的很对,但一直过不去,就 \(\text{rand()}\)了一下,中间出现了负数,如果一直调不出来可以看一看
solution
-
以三层的图为例,题目显然给出的是一颗完全二叉树
然后枚举以哪个点为\(\text{LCA}\)进行统计答案,以这个思路开始,考虑这个点的贡献。 -
发现一号点的贡献是\(1\)号点的(左儿子数 + 1) \(\times\) 右儿子数,左儿子 + 1意义是它加上它自己的贡献
然后每层依次递推,最终会得出这样一个式子。
然后就是很裸的解式子的问题了,我们可以不这个式子做到\(O(n)\)
下面的推导有部分跳步
首先不考虑 \(\sum\) , 而且看着分数很难受,\(\frac{1}{2}\) 可以删掉,把\(2^{i-1}\)乘进去
然后分配率处理
然后发现里面出现常量了,有的地方可以把\(\sum\)给提出来,最后可以得到一个这样的式子
显然可以预处理出 \(2^?\)幂以及 \(2^{2i-3}\) 还有\(2^{2i-2}\)以及 \(2\)的幂的前缀和
\(\sum\limits_{i=1}^{k}2^{2k-i-1}\)这一块就是由\(2\)的幂的前缀和得到的,可以化为\(\text{sum}_{2k-2}-\text{sum}_{k-2}\)
\(\text{sum}\)表示2的幂的前缀和
然后我们就可以做到\(O(n)\)的预处理,\(O(1)\)的查询,总的复杂度为\(O(n + T)\)
code
/*
Author:Imy_isLy
知识点: 树形结构 前缀和
*/
#include <iostream>
#include <cstdio>
#include <cmath>
#include <cstring>
#include <string>
#include <bits/stdc++.h>
#define int long long
using namespace std;
const int N = 2e6+100 * 2;
const int M = 1e6;
const int inf = 2147483647;
const int mod = 998244353;
//=============================================================
int n, k;
int POW[N], sum[N], sum2i_3[N], sum2i_2[N];
//=============================================================
int read() {
int s = 0 , f = 0 ; char ch = getchar() ;
while(!isdigit(ch)) f |= (ch == '-') , ch = getchar();
while(isdigit(ch)) s = s * 10 + (ch ^ 48) , ch = getchar();
return f ? -s : s;
}
void pre() {
POW[0] = 1;
int pp = 1;
for (int i = 1 ; i <= (M << pp); ++i) {
POW[i] = (POW[i - 1] << pp) % mod;
sum[i] = (sum[i - 1] + POW[i] ) % mod;
}
for (int i = 1 ; i <= (M << pp); ++i) {
if(2 * i - 3 >= 0)
sum2i_3[i] = (sum2i_3[i - 1] + POW[2 * i - 3]) % mod;
if(2 * i - 2 >= 0)
sum2i_2[i] = (sum2i_2[i - 1] + POW[2 * i - 2]) % mod;
}
return ;
}
signed main() {
// freopen("1.in","r",stdin);
// freopen("2.out","w",stdout);
// srand(time(0));
// int T = rand() % M;
int T = read();
pre();
while(T--) {
k = read();
if(k == 0) {
puts("0"); continue;
}
if(k == 1) {
puts("1");continue;
}
int ans =
( ( ( ( k * (POW[ 2 * k - 2] + POW[2 * k - 1] ) % mod) % mod
- (sum[2 * k - 2] % mod - sum[k - 2] % mod + mod ) % mod + mod ) % mod
+ (-sum2i_3[k] % mod - sum2i_2[k] % mod + mod) % mod
+ sum[k - 2] % mod ) + mod ) % mod;
cout << (ans + 1ll) % mod <<"\n";
}
// system("pause");
return 0;
}