洛谷 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])\times(num[d[i]]+num[d[j]]) \]

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

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

在这个式子上做文章就很简单了。
对于每个 \(i\),对答案的贡献为 \((n-1)\times d[i]\times num[i]+d[i]\times(b[n]-b[i])+num[d[i]]\times(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

posted @ 2021-07-13 17:33  尹昱钦  阅读(219)  评论(1编辑  收藏  举报