【題解】Luogu P2671 【求和】

因爲人傻常數大寫了一天的題目。

原題傳送門

題目意思另一種表達:

定義特殊二元組\((x,z)\)

1.\(x<z\)

2.\(x\)\(z\)要麼都爲奇數要麼都爲偶數。

(即\(x \ mod \ 2 = z \ mod \ 2\)

3.\(c_x=c_z\)

4.該二元組的分數爲\((x+z)\times(a_x+a_z)\)

給定所有\(c_i\)\(a_i\),求滿足條件的二元組的分數和。

以上的\(x\)\(z\)在下文表述爲\(id_x\)\(id_y\)。(\(y\)代替\(z\)


80分做法

\(v[i][0]\)來存儲顏色爲\(i\)的所有偶數下標的\(num_i\)\(a_i\)\(v[i][1]\)來存儲奇數下標。

有以下結論:

對於所有的\((x,y)\)\(v[i][j][x]\)\(v[i][j][y]\)都兩兩爲合法二元組。

然後枚舉所有顏色\(i\),接着枚舉\(x\)\(y\)暴力統計答案就可以了。

時間複雜度應該是\(O(n^2)\),不知道爲啥有\(80pts\)


\(80pts:\)

#include <algorithm>
#include <iostream>
#include <cstring>
#include <cstdio>
#include <cmath>
#include <vector>
using namespace std;
#define MAXN (int)(1e5+233)
#define int long long
struct qwq
{
    int a,c,id;
}e[MAXN];
vector<qwq> v[MAXN][2];
#define mod (10007)
signed main()
{
    int n,m;
    scanf("%lld%lld",&n,&m);
    for (int i=1;i<=n;i++) scanf("%lld",&e[i].a);
    for (int i=1;i<=n;i++) scanf("%lld",&e[i].c),e[i].id=i;
    for (int i=1;i<=n;i++)
    {
        v[e[i].c][i%2].push_back(e[i]);
    }
    int ans=0;
    for (int i=1;i<=m;i++)
    {
        for (int j=0;j<v[i][0].size();j++)
        {
            for (int k=j+1;k<v[i][0].size();k++)
            {
                ans+=((v[i][0][j].id+v[i][0][k].id)*(v[i][0][j].a+v[i][0][k].a))%mod;
                ans%=mod;
            }
        }
        for (int j=0;j<v[i][1].size();j++)
        {
            for (int k=j+1;k<v[i][1].size();k++)
            {
                ans+=((v[i][1][j].id+v[i][1][k].id)*(v[i][1][j].a+v[i][1][k].a))%mod;
                ans%=mod;
            }
        }
    }
    printf("%lld\n",ans%mod);
    return 0;
}

100分做法

\(80\)分做法中提到的\(v[i][j][]\)這樣的一個數組,我們簡寫爲\(s[]\),考慮每次往數組後面加一個數,這個數對答案的貢獻。

拆式(題目中的貢獻式)

\[(id_x+id_y)\times(a_x+a_y)\]

\[(id_x \times a_x)+(id_x \times a_y)+(id_y \times a_x)+(id_y\times a_y)\]

於是原式就拆爲四個式子的和。

反思結論(\(x\)\(y\)指在統計數組中的下標)

對於所有的\((x,y)\)\(s[x]\)\(s[y]\)都兩兩爲合法二元組。

那麼每在\(s[]\)末尾位置\(y\)多加入一個值,這個\(y\)就要與所有\(0 < x < y\)相匹配並累計貢獻。(說簡單點就和\(y\)之前的所有數匹配爲合法二元組)

分別考慮拆式中四個式子

1.\((id_x \times a_x)\)

對於所有\(x\),答案加上\(id_x \times a_x\)。用\(sumul\)數組記錄\(\sum_{x=1}^{y-1}id_x \times a_x\)的和,累計進答案貢獻。

2.\((id_x \times a_y)\)

\(sumid\)記錄\(\sum_{x=1}^{y-1}{id_x}\),答案貢獻加上\(sumid \times a_y\)

3.\((id_y \times a_x)\)

\(suma\)記錄\(\sum_{x=1}^{y-1}{a_x}\),答案貢獻加上\(suma \times id_y\)

4.\((id_y \times a_y)\)

我們知道在末尾位置\(y\)加一個數就增加了\((y-1)\)個合法二元組。那麼也就是會增加\((y-1)\)\((id_y \times a_y)\)。用一個\(sum\)數組來記錄末尾下標\(y\)

5.總結貢獻

\(ans+=(sumul+sumid \times a_y +suma \times id_y +sum \times (id_y \times a_y))\)


\(100pts:\)

#include <vector>
#include <cstdio>
#include <cstring>
#include <iostream>
#include <algorithm>
#define int long long
#define MAXN (int)(1e5+233)

using namespace std;

int a[MAXN],c[MAXN],suma[MAXN][2],sumid[MAXN][2],sumul[MAXN][2],sum[MAXN][2];
struct qwq {
    int a,c,id;
}e[MAXN];
#define mod (10007)
signed main() {

    int n,m;
    scanf("%lld%lld",&n,&m);
    for (int i=1;i<=n;i++) scanf("%lld",&e[i].a);
    for (int i=1;i<=n;i++) scanf("%lld",&e[i].c),e[i].id=i;
    int ans=0;
    for (int i=1;i<=n;i++)
    {
        ans+=sumul[e[i].c][i&1]+i*e[i].a*sum[e[i].c][i&1]+sumid[e[i].c][i&1]*e[i].a+suma[e[i].c][i&1]*i;
        
        sumul[e[i].c][i&1]+=i*e[i].a;//處理a_i*i前綴和
        
        suma[e[i].c][i&1]+=e[i].a;//處理a_i前綴和
        
        sumid[e[i].c][i&1]+=i;//處理number_i前綴和
        
        sum[e[i].c][i&1]++;//處理(不存在的)數組末尾下標
        
        ans%=mod;
    }
   printf("%lld\n",ans);
    return 0;
}
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章