AcWing 468. 魔法阵

AcWing 468. 魔法阵

洛谷

一、题目描述

六十年一次的魔法战争就要开始了,大魔法师准备从附近的魔法场中汲取魔法能量。

大魔法师有 m 个魔法物品,编号分别为 1,2,,m

每个物品具有一个魔法值,我们用 xi 表示编号为 i 的物品的魔法值。

每个魔法值 xi 是不超过 n 的正整数,可能有多个物品的魔法值相同。

大魔法师认为,当且仅当四个编号为 a,b,c,d 的魔法物品满足 xa<xb<xc<xdxbxa=2(xdxc),并且 xbxa<(xcxb)/3 时,这四个魔法物品形成了一个魔法阵,他称这四个魔法物品分别为这个魔法阵的 A 物品,B 物品,C 物品,D 物品。

现在,大魔法师想要知道,对于每个魔法物品,作为某个魔法阵的 A 物品出现的次数,作为 B 物品的次数,作为 C 物品的次数,和作为 D 物品的次数。

输入格式
输入文件的第一行包含两个空格隔开的正整数 nm

接下来 m 行,每行一个正整数,第 i+1 行的正整数表示 xi,即编号为 i 的物品的魔法值。

保证每个 xi 是分别在合法范围内等概率随机生成的。

输出格式
共输出 m 行,每行四个整数。

i 行的四个整数依次表示编号为 i 的物品作为 A,B,C,D 物品分别出现的次数。

保证标准输出中的每个数都不会超过 109

每行相邻的两个数之间用恰好一个空格隔开。

数据范围
1n15000,1m40000,1xin

输入样例

30 8
1
24
7
28
5
29
26
24

输出样例

4 0 0 0
0 0 1 0
0 2 0 0
0 0 1 1
1 3 0 0
0 0 0 2
0 0 2 2
0 0 1 0

二、暴力40分做法

4层循环枚举每个物品,物品上限m<=40000,四层就是40000400004000040000,死的透透的,好处就是好想好做,可以骗一部分分数。

#include <bits/stdc++.h>
using namespace std;
const int N = 40010;
int n, m;
int q[N];

// 40分
bool check(int a, int b, int c, int d) {
    if (a >= b || b >= c || c >= d) return 0;
    if ((b - a) != 2 * (d - c)) return 0;
    if (3 * (b - a) >= (c - b)) return 0;
    return 1;
}
int g[N][4];

int main() {
#ifndef ONLINE_JUDGE
    freopen("468.in", "r", stdin);
#endif
    cin >> n >> m;
    // 魔法值都是不超过n的正整数,似乎没啥用
    // m个魔法物品

    for (int i = 1; i <= m; i++) cin >> q[i]; // 读入每个魔法物品的魔法值

    for (int a = 1; a <= m; a++)
        for (int b = 1; b <= m; b++)
            for (int c = 1; c <= m; c++)
                for (int d = 1; d <= m; d++)
                    if (check(q[a], q[b], q[c], q[d]))
                        g[a][0]++, g[b][1]++, g[c][2]++, g[d][3]++;

    // a这个枚举到的数字出现了一次,它是做为a位置出现的
    // 找到一组合法的a,b,c,d

    // 输出结果
    for (int i = 1; i <= m; i++)
        printf("%d %d %d %d\n", g[i][0], g[i][1], g[i][2], g[i][3]);
    return 0;
}

三、暴力65分做法

既然4层每层枚举物品的办法行不通,那能不能考虑变化一下枚举的内容呢?我们观察发现,上帝为你关上了一扇门,就会为你打开一扇窗,此题中的魔法值上限n<=15000的!

不是很大,我们能不能考虑枚举魔法数值呢?

但是如果我们枚举每个魔法数值,魔法数值有重复怎么办呢?

题目提示:每个魔法值 Xi 是不超过 n 的正整数,可能有多个物品的魔法值相同。

当然重复的信息不能丢失,需要记录下来每个魔法值有几个,这提示我们用桶,一看n<=15000,用桶来保存魔法值的个数是没有问题的,我们设cnt[N]来保存每个魔法值的个数。

继续,如果我们枚举出了一组合法的魔法值组合(a,b,c,d),那么这些魔法值(a,b,c,d)可能是哪些物品的呢?因为最后我们需要回答的是每个魔法物品在四个位置出现的次数,不能不关心是哪些物品啊!
当然是魔法值等于(a,b,c,d)的魔法物品,设为
(A,A,A),(B,B),(C),(D,D)
那么如果出现了一次(a,b,c,d),在现实物品组合中可能是
(A,B,C,D)(A,B,C,D)(A,B,C,D)...

组合数就是3212
这里还有一个小弯弯,就是人家最终问的是物品i,也就是可以理解为物品A出现的次数,那么就是32123

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

typedef long long LL;
const int N = 15010, M = 40010;
int n, m;
int x[M];
LL cnt[N];
LL num[N][4];
// 65分 4层循环,按桶的思路枚举每个魔法值,暴力枚举a,b,c,d
LL read() {
    LL x = 0, f = 1;
    char ch = getchar();
    while (ch < '0' || ch > '9') {
        if (ch == '-') f = -1;
        ch = getchar();
    }
    while (ch >= '0' && ch <= '9') {
        x = (x << 3) + (x << 1) + (ch ^ 48);
        ch = getchar();
    }
    return x * f;
}

int main() {
#ifndef ONLINE_JUDGE
    freopen("468.in", "r", stdin);
#endif
    n = read(), m = read();
    for (int i = 1; i <= m; i++) {
        x[i] = read();
        cnt[x[i]]++;
    }

    for (int a = 1; a <= n; a++)
        for (int b = a + 1; b <= n; b++)
            for (int c = b + 1; c <= n; c++)
                for (int d = c + 1; d <= n; d++) {
                    if ((b - a) & 1 || 3 * (b - a) >= (c - b)) continue;
                    if ((b - a) != 2 * (d - c)) continue;
                    LL ans = cnt[a] * cnt[b] * cnt[c] * cnt[d];
                    num[a][0] += ans;
                    num[b][1] += ans;
                    num[c][2] += ans;
                    num[d][3] += ans;
                }
    for (int i = 1; i <= n; i++)
        for (int j = 0; j < 4; j++)
            num[i][j] /= cnt[i] ? cnt[i] : 1;

    for (int i = 1; i <= m; i++) {
        for (int j = 0; j < 4; j++)

            printf("%lld ", num[x[i]][j]);
        puts("");
    }
    return 0;
}

四、暴力85分做法

要求求出满足xa<xb<xc<xd,xbxa=2(xdxc)xbxa<xcxb3a,b,c,d的数量。

为了去掉一层循环,结合以前的经验,我们知道可以通过数学办法推导一下xd=xbxa+2xc2

所以我们可以省去一维的枚举,做到O(n3)枚举,实测在洛谷上能拿到85分.

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

typedef long long LL;
const int N = 15010, M = 40010;
int n, m;     // 魔法值的上限是n,个数是m
int x[M];     // 原始的魔法值
LL cnt[N];    // 每个魔法值计数用的桶
LL num[N][4]; // 以某个魔法值i为a,b,c,d时的个数,记录在num[i][0],num[i][1],num[i][2],num[i][3]中,也就是答案

// 85分 3层循环,按桶的思路枚举每个魔法值,暴力枚举a,b,c,然后利用数学办法计算出d
// 17/20 85分
// 快读
LL read() {
    LL x = 0, f = 1;
    char ch = getchar();
    while (ch < '0' || ch > '9') {
        if (ch == '-') f = -1;
        ch = getchar();
    }
    while (ch >= '0' && ch <= '9') {
        x = (x << 3) + (x << 1) + (ch ^ 48);
        ch = getchar();
    }
    return x * f;
}

int main() {
#ifndef ONLINE_JUDGE
    freopen("468.in", "r", stdin);
#endif
    n = read(), m = read();
    for (int i = 1; i <= m; i++) {
        x[i] = read();
        cnt[x[i]]++; // 记录每个魔法值的个数
    }

    // 不再枚举每个输入的顺序,而是枚举每个魔法值,原因是魔法值的上限是固定的n
    for (int a = 1; a <= n; a++) // 枚举每个魔法值,上限是n
        for (int b = a + 1; b <= n; b++)
            for (int c = b + 1; c <= n; c++) {
                if ((b - a) & 1 || 3 * (b - a) >= (c - b)) continue; // 把已知条件反着写,符合这样要求的,直接continue掉
                int d = b - a + c * 2 >> 1;                          // d可以通过数学办法计算获得
                // 这里有一个数学的小技巧,就是先求总的个数,再除掉自己的个数
                // 现在枚举到的每个(a,b,c,d)组合都是一种合法的组合,同时,由于每个数值不止一个,根据乘法原理,需要累乘个数才是答案
                LL ans = cnt[a] * cnt[b] * cnt[c] * cnt[d];
                // if (ans) cout << a << " " << b << " " << c << " " << d << endl;
                num[a][0] += ans;
                num[b][1] += ans;
                num[c][2] += ans;
                num[d][3] += ans;
            }
    for (int i = 1; i <= n; i++)
        for (int j = 0; j < 4; j++)
            num[i][j] /= cnt[i] ? cnt[i] : 1;

    for (int i = 1; i <= m; i++) {  // 枚举每个序号
        for (int j = 0; j < 4; j++) // 此序号作为a,b,c,d分别出现了多少次
            // 举栗子:i=2,x[i]=5,也就是问你:5这个数,分别做为a,b,c,d出现了多少次?
            printf("%lld ", num[x[i]][j]);
        puts("");
    }
    return 0;
}

五、递推优化解法

依旧是对 xbxa=2(xdxc)进行分析,我们设t=xdxc,则xbxa=2t;再分析第二个条件XbXa<(XcXb)/3,我们可以得到XcXb>6t,我们给他补全成等号,就是XcXb=6t+k

所以这四个数在数轴上的排列如图所示

左边红色部分框出的AB是绑定的,右边绿色部分框出的CD也是绑定的。
因此整个系统共有三个自由度:t、红色部分、绿色部分。

同时枚举三个自由度的计算量过大。在1秒内,我们只能枚举其中两个自由度。

所以我们会有一个不成熟的思路:在1n/9范围内枚举t,把a,b,c,dt表示出来。

那么如何计算呢?枚举D。当我们枚举到一个D值的时候,与之对应的C值是确定的(不受k影响),而A值和B值却不一定。因此我们可以找到最大的与之对应的AB值。

但是有可能会存在一组A值、B值要比当前计算到的小,怎么办呢?不妨设有可能存在的比最大值小的A值为A1B值为B1,计算到的为A2B2

A1<A2&&B1<B2时,只要A2B2能组成魔法阵,A1B1一定可以(k只是大于0的数,而对k的上界没有限制,当我们把k放大时,就可以构造出A1B1了)。

由于是顺序枚举,所以我们可以 记录一下之前有多少组合法解类似于前缀和),最后再用 乘法原理 计算。

同样的方法,我们从A的上界往A的下界枚举记录 后缀和 然后计算即可。

首先枚举t。接下来并列枚举绿色部分和红色部分:
从左到右枚举绿色部分,当绿色部分固定后,则C应该累加的次数是所有满足要求的ABcnt[A]cnt[B] 的和,再乘以cnt[D]。其中cnt[A],cnt[B],cnt[D]A,B, D出现的次数。所有满足要求的AB就是整个线段左边的某个前缀,因此可以利用前缀和算法来加速计算。cnt[D]同理可得。
从右到左枚举红色部分可做类似处理。

Code

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

typedef long long LL;
const int N = 15010, M = 40010;
int n, m, x[M], num[4][N], cnt[N];

// 快读
LL read() {
    LL x = 0, f = 1;
    char ch = getchar();
    while (ch < '0' || ch > '9') {
        if (ch == '-') f = -1;
        ch = getchar();
    }
    while (ch >= '0' && ch <= '9') {
        x = (x << 3) + (x << 1) + (ch ^ 48);
        ch = getchar();
    }
    return x * f;
}

int main() {
    n = read(), m = read();
    for (int i = 1; i <= m; i++) { // m个魔法值
        x[i] = read();
        cnt[x[i]]++; // 每个魔法值对应的个数
    }
    int sum, A, B, C, D;

    for (int t = 1; t * 9 + 1 <= n; t++) { // k最小是1,那么9t+1=max(x[D])=n
        sum = 0;
        for (D = 9 * t - 1; D <= n; D++) { // 枚举D
            C = D - t;                     // 表示C
            B = C - 6 * t - 1;             // 根据C推出最大的B
            A = B - 2 * t;                 // 推出最大的A
            sum += cnt[A] * cnt[B];        // 计算当前A和B的情况
            num[2][C] += cnt[D] * sum;     // num[2][C]+=cnt[A]*cnt[B]*cnt[C]
            num[3][D] += cnt[C] * sum;     // num[3][D]+=cnt[A]*cnt[B]*cnt[D]
        }
        sum = 0;
        for (A = n - 9 * t - 1; A; A--) { // 倒序枚举A
            B = A + 2 * t;
            C = B + 6 * t + 1;         // C的最小值
            D = C + t;                 // D的最小值
            sum += cnt[C] * cnt[D];    // 计算当前C和D的情况 (涵盖了比C,D大的小所有C',D'的cnt乘积和)
            num[0][A] += cnt[B] * sum; // num[0][A]+=cnt[B]*cnt[C]*cnt[D]
            num[1][B] += cnt[A] * sum; // num[1][B]+=cnt[A]*cnt[C]*cnt[D]
        }
    }
    for (int i = 1; i <= m; i++) {
        for (int j = 0; j < 4; j++)
            printf("%d ", num[j][x[i]]);
        puts("");
    }
    return 0;
}

posted @   糖豆爸爸  阅读(56)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· 被坑几百块钱后,我竟然真的恢复了删除的微信聊天记录!
· 没有Manus邀请码?试试免邀请码的MGX或者开源的OpenManus吧
· 【自荐】一款简洁、开源的在线白板工具 Drawnix
· 园子的第一款AI主题卫衣上架——"HELLO! HOW CAN I ASSIST YOU TODAY
· Docker 太简单,K8s 太复杂?w7panel 让容器管理更轻松!
历史上的今天:
2021-09-27 AcWing 873. 欧拉函数
2021-09-27 AcWing 872. 最大公约数
2021-09-27 AcWing 871. 约数之和
2021-09-27 AcWing 870. 约数个数
2021-09-27 AcWing 869. 试除法求约数
2021-09-27 AcWing 868. 筛质数
2021-09-27 AcWing 867. 分解质因数
Live2D
点击右上角即可分享
微信分享提示