洛谷 P2671 [NOIP2015 普及组] 求和(前缀和,数学)

传送门


解题思路

比较巧的一道题。
(y−x=z−y)化简一下得(x+z=2y),这要求x和z一定要同为奇数或偶数。
所以很明显我们首先要对他们分类:

  1. 按照奇数偶数点
  2. 按照颜色

这样就化成了若干个集合,而我们需要快速求出每个集合对答案的贡献。
把数学式子列一下:

[sum_{i=1}^{n}sum_{j=i+1}^{n}(d[i]+d[j]) imes(num[d[i]]+num[d[j]]) ]

其中 (n) 为集合元素个数,(d) 中存的是集合中每个元素的编号,(num) 和题目中一致。
然后化简式子(去括号),得:

[sum_{i=1}^{n}sum_{j=i+1}^{n}(d[i] imes num[d[i]]+d[j] imes num[d[j]]+d[i] imes nun[d[j]]+num[d[i]] imes d[j]) ]

在这个式子上做文章就很简单了。
对于每个 (i),对答案的贡献为 ((n-1) imes d[i] imes num[i]+d[i] imes(b[n]-b[i])+num[d[i]] imes(a[n]-a[i]))
其中 (a)(d[i]) 的前缀和,(b)(num) 的前缀和。

AC代码

#include<iostream>
#include<cstring>
#include<vector>
using namespace std;
const int maxn=1e5+5;
const int mod=10007;
int n,m,color[maxn];
long long a[maxn],b[maxn],num[maxn],d[maxn]; 
long long ans1,ans2; 
vector<int> v[maxn];
int main()
{
    ios::sync_with_stdio(false);
    cin>>n>>m;
    for(int i=1;i<=n;i++) cin>>num[i];
    for(int i=1;i<=n;i++) cin>>color[i];
    for(int i=1;i<=n;i+=2){
    	v[color[i]].push_back(i);
	}
	for(int i=1;i<=m;i++){
		if(v[i].size()>1){
			int n=v[i].size();
			for(int j=1;j<=n;j++){
				d[j]=v[i][j-1];
				a[j]=a[j-1]+d[j];
				b[j]=b[j-1]+num[d[j]];
			}
			for(int j=1;j<=n;j++){
				ans1=(ans1+d[j]*(b[n]-b[j])%mod+num[d[j]]*(a[n]-a[j])%mod)%mod;
				ans1=(ans1+(n-1)*d[j]*num[d[j]]%mod)%mod;
			}
		}
		v[i].clear();
	}
	for(int i=2;i<=n;i+=2){
    	v[color[i]].push_back(i);
	}
	for(int i=1;i<=m;i++){
		if(v[i].size()>1){
			int n=v[i].size();
			for(int j=1;j<=n;j++){
				d[j]=v[i][j-1];
				a[j]=a[j-1]+d[j];
				b[j]=b[j-1]+num[d[j]];
			}
			for(int j=1;j<=n;j++){
				ans2=(ans2+d[j]*(b[n]-b[j])%mod+num[d[j]]*(a[n]-a[j])%mod)%mod;
				ans2=(ans2+(n-1)*d[j]*num[d[j]]%mod)%mod;
			}
		}
		v[i].clear();
	}
	cout<<(ans1+ans2)%mod;
    return 0;
}

//NOIP2015普及组 t3

原文地址:https://www.cnblogs.com/yinyuqin/p/15007568.html