P2671 求和 题解
这道题有一点数学的味道,思路想清楚,代码其实并不难写。
由题意,满足条件的三元组 \((x,y,z)\) 必须满足 \(y-x=z-y\) ,变形可得 \(x+z=2y\) ,又因为 \(x,y,z\) 都是整数,所以 \(x,z\) 同奇偶。又因为 \(color_x=color_z\) ,统计答案时也只需要 \(x,z\) ,所以其实最后和 \(y\) 没什么关系。
考虑到 \(color_x=color_z\) 并且 \(x,z\) 同奇偶,所以我们可以先将同一种颜色的格子归类(注意在存储 \(number\) 和 \(color\) 时,不要忘记存下原本的编号,我使用 \(under\) 表示),然后按照奇偶归类,最后统计答案。
如何统计答案呢?接下来是数学推导。
设最后归类之后,颜色相同且奇偶性相同的格子编号分别为 \(a_1,a_2,...,a_n\), \(number\) 为 \(b_1,b_2,...,b_n\) ( \(N > 1\)),令答案为 \(S\) ,有:
\(S=(a_1+a_2)(b_1+b_2)+(a_1+a_3)(b_1+b_3)+...+(a1+a_n)(b_1+b_n)+(a_2+a_3)(b_2+b_3)+...+(a_2+a_n)(b_2+b_n)+...+(a_{n-1}+a_n)(b_{n-1}+b_n)\)
\(=\sum\limits_{i=1}^{n-1}a_ib_i+a_1\sum\limits_{i=1}^nb_i-a_1b_1+a_2\sum\limits_{i=1}^nb_i-a_2b_2+...+a_n\sum\limits_{i=1}^nb_i-a_nb_n\)
\(=\sum\limits_{i=1}^{n-2}a_ib_i+\sum\limits_{i=1}^na_i\times\sum\limits_{i=1}^nb_i\)(如果以上几步不能理解,建议手动推导一遍或者是取特殊值找规律)
- N=1时, \(S=-a_1b_1+a_1b_1=0\) ,不会有影响。
这就好办了!分类完之后,统计这些答案我们可以直接 \(O(n)\) 扫一遍即可。
代码如下:
#include<bits/stdc++.h>
using namespace std;
const int MAXN=100000+10,P=10007;
typedef long long LL;
LL n,m,ji[MAXN],ou[MAXN],ans;
struct node
{
LL number,color,under;
}a[MAXN];//结构体存储
bool cmp(const node &fir,const node &sec)
{
if(fir.color!=sec.color) return fir.color<sec.color;
return fir.under<sec.under;
}
int main()
{
scanf("%d %d",&n,&m);
for(int i=1;i<=n;i++) a[i].under=i;//处理下标
for(int i=1;i<=n;i++) scanf("%d",&a[i].number);
for(int i=1;i<=n;i++) scanf("%d",&a[i].color);
sort(a+1,a+n+1,cmp);//按颜色分类
for(int i=1;i<=n;i++)
{
if(a[i].under&1) ji[++ji[0]]=i;
else ou[++ou[0]]=i;
}//按奇偶分类
LL sgmai=a[ji[1]].under%P,sgmbi=a[ji[1]].number%P,sgmab=a[ji[1]].under*a[ji[1]].number%P,ls=a[ji[1]].color,cnt=1;
for(int i=2;i<=ji[0];i++)
{
if(a[ji[i]].color!=ls)
{
ans=(ans+sgmai*sgmbi%P+(cnt-2)*sgmab%P)%P;
sgmai=a[ji[i]].under%P;
sgmbi=a[ji[i]].number%P;
sgmab=a[ji[i]].under*a[ji[i]].number%P;
ls=a[ji[i]].color;
cnt=1;
}
else
{
cnt++;
sgmai=(sgmai+a[ji[i]].under)%P;
sgmbi=(sgmbi+a[ji[i]].number)%P;
sgmab=(sgmab+a[ji[i]].under*a[ji[i]].number%P)%P;
}
}//统计奇数答案,sgmai为ai的和,sgmbi为bi的和,sgmab为ai*bi的和,ls表示当前处理的颜色,cnt为个数。
ans=(ans+sgmai*sgmbi%P+(cnt-2)*sgmab%P)%P;//千万不要忘记在扫完后统计最后答案!!!否则会漏掉最后一个颜色
sgmai=a[ou[1]].under%P,sgmbi=a[ou[1]].number%P,sgmab=a[ou[1]].under*a[ou[1]].number%P,ls=a[ou[1]].color,cnt=1;
for(int i=2;i<=ou[0];i++)
{
if(a[ou[i]].color!=ls)
{
ans=(ans+sgmai*sgmbi%P+(cnt-2)*sgmab%P)%P;
sgmai=a[ou[i]].under%P;
sgmbi=a[ou[i]].number%P;
sgmab=a[ou[i]].under*a[ou[i]].number%P;
ls=a[ou[i]].color;
cnt=1;
}
else
{
cnt++;
sgmai=(sgmai+a[ou[i]].under)%P;
sgmbi=(sgmbi+a[ou[i]].number)%P;
sgmab=(sgmab+a[ou[i]].under*a[ou[i]].number%P)%P;
}
}//同理
ans=(ans+sgmai*sgmbi%P+(cnt-2)*sgmab%P)%P;
printf("%d\n",ans);
return 0;
}
一个比较容易犯的错误:这道题虽然模数 \(P=10007\) ,但是在处理 \(\sum\limits_{i=1}^{n-2}a_ib_i\) 时,由于 \(a_i\) 的最大值为 \(N\)(\(1 \leq N \leq 100000\)),\(b_i\) 的最大值为 \(number_i\) 的最大值(\(1 \leq number_i \leq 100000\)),所以在处理 \(\sum\limits_{i=1}^{n-2}a_ib_i\) 时中间过程可能会有溢出,所以最好开 long long
存储,避免数据溢出。
总结:诸如这一类的数学题目,码量一般不是很大,关键是如何利用题目条件进行推导,不断优化算法(这里就需要比较扎实的数学基础)最后达到满分。