题解:[NOIP 2015 普及组] 求和

洛谷P2671

Posted by TH911 on March 28, 2025

题目传送门

题意分析

对于三元组 $(x,y,z)$,显然其对于答案的贡献与 $y$ 无关,因此可以考虑消去 $y$。$y-x=z-y$ 即 $x+z=2y$,也就是说 $x,z$ 奇偶性相等。

$\text{50pts}$

$\mathcal O(n^2)$ 枚举 $x,z$,判断 $color$ 值是否相等后统计答案即可。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
//#include<bits/stdc++.h>
#include<algorithm>
#include<iostream>
#include<cstring>
#include<iomanip>
#include<cstdio>
#include<string>
#include<vector>
#include<cmath>
#include<ctime>
#include<deque>
#include<queue>
#include<stack>
#include<list>
using namespace std;
constexpr const int N=100000,M=100000,P=10007;
int n,m,number[N+1],color[M+1];
int main(){
	/*freopen("test.in","r",stdin);
	freopen("test.out","w",stdout);*/
	
	scanf("%d %d",&n,&m);
	for(int i=1;i<=n;i++){
		scanf("%d",number+i);
	}
	for(int i=1;i<=n;i++){
		scanf("%d",color+i);
	}
	int ans=0;	
	for(int l=1;l<=n;l++){
		for(int r=l+2;r<=n;r+=2){
			if(color[l]==color[r]){
				ans=(ans+1ll*(l+r)*(number[l]+number[r]))%P;
			}
		}
	}
	printf("%d\n",ans);
	
	/*fclose(stdin);
	fclose(stdout);*/
	return 0;
}

$\text{90pts}$

考虑对每一个格子按照颜色奇偶性进行分类,枚举 $x$ 的时候枚举 $z$ 即可。

注意 $x=z$ 时不要统计,同时会重复统计,答案需要除以 $2$,模数 $10007$ 为质数,使用逆元即可。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
//#include<bits/stdc++.h>
#include<algorithm>
#include<iostream>
#include<cstring>
#include<iomanip>
#include<cstdio>
#include<string>
#include<vector>
#include<cmath>
#include<ctime>
#include<deque>
#include<queue>
#include<stack>
#include<list>
using namespace std;
constexpr const int N=100000,M=100000,P=10007;
int n,m,number[N+1],color[M+1];
vector<int>a[M+1][2]; 
int qpow(int a,int n){
	if(!n){
		return 1;
	}
	int t=qpow(a,n>>1);
	t=1ll*t*t%P;
	if(n&1){
		t=1ll*t*a%P;
	}
	return t;
}
int main(){
	/*freopen("test.in","r",stdin);
	freopen("test.out","w",stdout);*/
	
	scanf("%d %d",&n,&m);
	for(int i=1;i<=n;i++){
		scanf("%d",number+i);
	}
	for(int i=1;i<=n;i++){
		scanf("%d",color+i);
		a[color[i]][i&1].push_back(i);
	}
	int ans=0;	
	for(int l=1;l<=n;l++){
		for(int r:a[color[l]][l&1]){
			if(r==l){
				continue;
			}
			ans=(ans+1ll*(l+r)*(number[l]+number[r]))%P;
		}
	}
	printf("%d\n",1ll*ans*qpow(2,P-2)%P);
	
	/*fclose(stdin);
	fclose(stdout);*/
	return 0;
}

$\text{100pts TLE}$

考虑在根据颜色、奇偶性分类后统计时先二分出 $x$ 可行的 $z$ 的左边界,减小常数。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
//#include<bits/stdc++.h>
#include<algorithm>
#include<iostream>
#include<cstring>
#include<iomanip>
#include<cstdio>
#include<string>
#include<vector>
#include<cmath>
#include<ctime>
#include<deque>
#include<queue>
#include<stack>
#include<list>
using namespace std;
constexpr const int N=100000,M=100000,P=10007;
int n,m,number[N+1],color[M+1];
vector<int>a[M+1][2]; 
int qpow(int a,int n){
	if(!n){
		return 1;
	}
	int t=qpow(a,n>>1);
	t=1ll*t*t%P;
	if(n&1){
		t=1ll*t*a%P;
	}
	return t;
}
int main(){
	/*freopen("test.in","r",stdin);
	freopen("test.out","w",stdout);*/
	
	scanf("%d %d",&n,&m);
	for(int i=1;i<=n;i++){
		scanf("%d",number+i);
	}
	for(int i=1;i<=n;i++){
		scanf("%d",color+i);
		a[color[i]][i&1].push_back(i);
	}
	int ans=0;	
	for(int l=1;l<=n;l++){
		int p=upper_bound(a[color[l]][l&1].begin(),a[color[l]][l&1].end(),l)-a[color[l]][l&1].begin();
		for(int r=p;r<a[color[l]][l&1].size();r++){
			ans=(ans+1ll*(l+a[color[l]][l&1][r])*(number[l]+number[a[color[l]][l&1][r]]))%P;
		}
	}
	printf("%d\n",ans);
	
	/*fclose(stdin);
	fclose(stdout);*/
	return 0;
}

$\text{100pts AC}$

考虑拆式子。

对于 $x$,令 $l,r$ 分别表示分类后数组 $pl$ 中可行 $z$ 的左右边界。

则 $x$ 对于答案的贡献为:

\(\begin{aligned} \sum_{i=l}^r[(x+pl_i)(number_x+number_{pl_i})]&=\sum_{i=l}^r[x\cdot number_x+x\cdot number_{pl_i}+pl_i\cdot number_x+pl_i\cdot number_{pl_i}]\\ &=(r-l+1)(x\cdot number_x)+x\sum_{i=l}^rnumber_{pl_i}+number_x\sum_{i=l}^rpl_i+\sum_{i=l}^rpl_i\cdot number_{pl_i} \end{aligned}\) 则对于每一个 $pl$,处理出 $number_{pl_i},pl_i,pl_i\cdot number_{pl_i}$ 的前缀和即可。

注意取模。

时间复杂度:$\mathcal O(n\log n)$。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
//#include<bits/stdc++.h>
#include<algorithm>
#include<iostream>
#include<cstring>
#include<iomanip>
#include<cstdio>
#include<string>
#include<vector>
#include<cmath>
#include<ctime>
#include<deque>
#include<queue>
#include<stack>
#include<list>
using namespace std;
constexpr const int N=100000,M=100000,P=10007;
int n,m,number[N+1],color[M+1];
struct node{
	int i;
	int numberSum,plSum,plNumberSum;
	node(){
		i=numberSum=plSum=plNumberSum=0;
	}
};
bool operator <(const int &a,const node &b){
	return a<b.i;
}
vector<node>a[M+1][2];
int main(){
	/*freopen("test.in","r",stdin);
	freopen("test.out","w",stdout);*/
	
	scanf("%d %d",&n,&m);
	for(int i=1;i<=n;i++){
		scanf("%d",number+i);
	}
	for(int i=1;i<=n;i++){
		scanf("%d",color+i);
		auto &pl=a[color[i]][i&1];
		node x;
		x.i=i;
		if(pl.size()){
			x.numberSum=pl.back().numberSum;
			x.plSum=pl.back().plSum;
			x.plNumberSum=pl.back().plNumberSum;
		}
		x.numberSum=(1ll*x.numberSum+number[i])%P;
		x.plSum=(1ll*x.plSum+i)%P;
		x.plNumberSum=(1ll*x.plNumberSum+1ll*i*number[i])%P;
		pl.push_back(x);
	}
	int ans=0;	
	for(int x=1;x<=n;x++){
		auto &pl=a[color[x]][x&1];
		int l=upper_bound(pl.begin(),pl.end(),x)-pl.begin(),r=pl.size()-1;
		int size=r-l+1;
		ans=(ans+(1ll*size*x%P*number[x]%P+1ll*x*(pl[r].numberSum-pl[l-1].numberSum)%P+1ll*number[x]*(pl[r].plSum-pl[l-1].plSum)%P+1ll*(pl[r].plNumberSum-pl[l-1].plNumberSum)%P))%P;
	}
	printf("%d\n",(ans>0?ans:ans+P));
	
	/*fclose(stdin);
	fclose(stdout);*/
	return 0;
}