CSP历年复赛题-P2119 [NOIP2016 普及组] 魔法阵

原题链接:https://www.luogu.com.cn/problem/P2119

题意解读:在一组数里找出所有的Xa,Xb,Xc,Xd的组合,使得满足Xa<Xb<Xc<Xd, Xb-Xa=2(Xd-Xc), Xb-Xa<(Xc-Xb)/3,并统计出每个数作为A,B,C,D出现的次数。

解题思路:

1、枚举(O(n^4))

首先想到的是通过4重循环枚举所有可能的Xa,Xb,Xc,Xd,然后判断是否符合需要满足的条件,如果符合,就将这一组Xa,Xb,Xc,Xd所谓有效数字累计到答案A[Xa]++, B[Xb]++, C[Xc]++, D[Xd]++,A,B,C,D数组记录各个魔法值作为A,B,C,D出现的次数。显然已知4个变量之间的等式,没有必要4重循环枚举,只需要枚举3个变量即可,所以此代码就不写了。

2、枚举(O(n^3))

根据题目要求,魔法值一共有40000个,但每个魔法值不超过15000,也就是有很多同样的魔法值,那么相同的魔法值在形成Xa,Xb,Xc,Xd的组合时效果是一样的,没必要一一枚举,可以考虑去重。

我们设int magic[M]存储每种物品的魔法值,int h[N]记录每种魔法值对应的物品数,vector<int> magic2为去重排序后的魔法值

h[N]是桶数组,一方面可以记录每种魔法值的数量,另一方面可以起到计数排序的作用,因此我们对去重排序后的魔法值magic2进行枚举即可。

先看Xa<Xb<Xc<Xd, 枚举是可以先枚举Xa,再枚举Xb,然后枚举Xc,Xd可以计算得来

再看Xb-Xa=2(Xd-Xc), 可以有两个结论:a、Xb-Xa是偶数,这样在枚举中可以提前判断 b、Xd = (Xb - Xa + 2 * Xc) / 2

最后Xb-Xa<(Xc-Xb)/3,经过移项可得:Xc > 4 * Xb - 3 * Xa,可以在枚举到Xc时进行提前判断

由于每种魔法值有多个,而枚举对象的已经去重之后的魔法值,所以在找到一组有效的Xa, Xb, Xc, Xd时,如何计算作为A,B,C,D出现的次数呢?

根据乘法原理:

Xa作为A出现的次数是由Xb,Xc,Xd的个数决定的,从h[Xb]个Xb中选1个,从h[Xc]个Xc中选1个,从h[Xd]个Xd中选1个都可以和Xa组成有效的组合,

所以有A[Xa] += h[Xb] * h[Xc] * h[Xd]

同理,

B[Xb] += h[Xa] * h[Xc] * h[Xd]
C[Xc] +=h[Xa] *h[Xb] *h[Xd]
D[Xd] +=h[Xa] *h[Xb] *h[Xc]
最后,按输入顺序枚举每一种魔法值,输出其作为A,B,C,D出现的次数
这样编写的代码就能得到90分,如果在考试中,够用了。

90分代码:

#include <bits/stdc++.h>
using namespace std;

const int N = 15005, M = 40005;
int n, m;
int A[N], B[N], C[N], D[N]; //每种魔法值作为A、B、C、D的次数
int magic[M]; //每种物品的魔法值
int h[N]; //每种魔法值对应的物品数
vector<int> magic2; //去重排序后的魔法值

int main()
{
    cin >> n >> m;
    for(int i = 1; i <= m; i++)
    {
        cin >> magic[i];
        h[magic[i]]++;
    }
    for(int i = 1; i <= N; i++)
    {
        if(h[i] > 0) magic2.push_back(i);
    }
    for(int i = 0; i < magic2.size(); i++) //枚举Xa
    {
        int Xa = magic2[i];
        for(int j = i + 1; j < magic2.size(); j++) //枚举Xb
        {
            int Xb = magic2[j];
            if((Xb - Xa) % 2 == 1) continue; //Xb-Xa=2(Xd-Xc),所以必须是偶数
            for(int k = j + 1; k < magic2.size(); k++) //枚举Xc
            {
                int Xc = magic2[k];
                if(Xc <= 4 * Xb - 3 * Xa) continue; //Xa、Xb、Xc要符合关系
                int Xd = (Xb - Xa + 2 * Xc) / 2; //根据公式计算Xd
                if(h[Xd])
                {
                    //Xa,Xb,Xc,Xd魔法值作为A,B,C,D的个数,要取决于其他魔法值的个数,根据乘法原理相乘
                    A[Xa] += h[Xb] * h[Xc] * h[Xd];
                    B[Xb] += h[Xa] * h[Xc] * h[Xd];
                    C[Xc] += h[Xa] * h[Xb] * h[Xd];
                    D[Xd] += h[Xa] * h[Xb] * h[Xc];
                }
            }
        }
    }

    for(int i = 1; i <= m; i++)
    {
        cout << A[magic[i]] << " " << B[magic[i]] << " " << C[magic[i]] << " " << D[magic[i]] << endl;
    }

    return 0;
}

3、枚举(O(n^2/9))

此题要得到100分,就需要换一种思维方式

已知:

Xa<Xb<Xc<Xd,

Xb-Xa=2(Xd-Xc),

Xb-Xa<(Xc-Xb)/3   =>   Xc-Xb > 3(Xb-Xa)  

我们设t = Xd-Xc,则有Xb-Xa=2t,Xc-Xb > 6t,所以Xa,Xb,Xc,Xd的大致分布为:

因此,我们在统计所有符合要求的Xc,Xd时只需要枚举两个变量:t、Xd

9t < n - 1,即9t + 2 <= n

Xd > Xa + 9t,所以 1 + 9t < Xd <= n

Xc = Xd - t

所以,从小到大枚举所有的t、Xd,Xc可以计算出来,而符合要求的Xa,Xb就是Xc,Xd左边所有能放得下的Xa,Xb

这里可以不用一一枚举xa,xb的位置,只需要限定Xb与Xc的距离,最小为6t+1,这样随着Xd,Xc的枚举右移,Xa,Xb也往右移动,

将Xa,Xb对结果的贡献用前缀和记录下来,每次更新

sum_ab += h[Xa] * h[Xb]

C[Xc] += h[Xd] * sum_ab

D[Xd] += h[Xc] * sum_ab

同理,在统计所有符合要求的Xa,Xb是只需要枚举两个变量:t、Xa

这次要从右往左枚举Xa

9t + 2 <= n

1<=Xa<=n-9t-1

sum_cd += h[Xc] * h[Xd]

C[Xc] += h[Xd] * sum_cd

D[Xd] += h[Xc] * sum_cd

100分代码:

#include <bits/stdc++.h>
using namespace std;

const int N = 15005, M = 40005;
int n, m;
int A[N], B[N], C[N], D[N]; //每种魔法值作为A、B、C、D的次数
int magic[M]; //每种物品的魔法值
int h[N]; //每种魔法值对应的物品数
vector<int> magic2; //去重排序后的魔法值

int main()
{
    cin >> n >> m;
    for(int i = 1; i <= m; i++)
    {
        cin >> magic[i];
        h[magic[i]]++;
    }
    for(int i = 1; i <= N; i++)
    {
        if(h[i] > 0) magic2.push_back(i);
    }
    for(int t = 1; 9 * t + 2 <= n; t++)
    {
        int sum_ab = 0;
        for(int Xd = 9 * t + 1; Xd <= n; Xd++)
        {
            int Xa = Xd - 9 * t - 1;
            int Xb = Xa + 2 * t;
            int Xc = Xd - t;
            sum_ab += h[Xa] * h[Xb];
            C[Xc] += h[Xd] * sum_ab;
            D[Xd] += h[Xc] * sum_ab;
        }

        int sum_dc = 0;
        for(int Xa = n - 9 * t - 1; Xa >= 1; Xa--){ //注意顺序
            int Xb = Xa + t * 2;
            int Xd = Xa + 9 * t + 1;
            int Xc = Xd - t;
            sum_dc += h[Xc] * h[Xd];
            A[Xa] += sum_dc * h[Xb];
            B[Xb] += sum_dc * h[Xa];
        }
    }

    for(int i = 1; i <= m; i++)
    {
        cout << A[magic[i]] << " " << B[magic[i]] << " " << C[magic[i]] << " " << D[magic[i]] << endl;
    }

    return 0;
}

 

posted @ 2024-06-07 10:05  五月江城  阅读(77)  评论(0编辑  收藏  举报