NOIP2016普及组复赛——T4魔法阵

题目描述

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

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

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

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

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

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

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

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

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

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

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

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

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

数据范围
1 ≤ n ≤ 15000 1≤n≤15000 1n15000,
1 ≤ m ≤ 40000 1≤m≤40000 1m40000,
1 ≤ x i ≤ n 1≤x_i≤n 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

算法1

算法思想:暴力枚举(40分)

依次将每件物品作为魔法阵的A、B、C、D类物品进行枚举,选择符合以下条件的方案:

  1. x a < x b < x c < x d x_a<x_b<x_c<x_d xa<xb<xc<xd
  2. x b − x a = 2 × ( x d − x c ) x_b−x_a=2\times(x_d−x_c) xbxa=2×(xdxc)
  3. x b − x a < ( x c − x b ) / 3 x_b−x_a<(x_c−x_b)/3 xbxa<(xcxb)/3

时间复杂度

O ( m 4 ) O(m^4) O(m4)

C++ 代码

#include <iostream>
#include <cstdio>

using namespace std;

const int N = 40010;

int x[N], sum[N][5];

int main()
{
    
    
    int n, m;
    scanf("%d%d", &n, &m);
    
    for(int i = 1; i <= m; i ++) scanf("%d", &x[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((x[a] < x[b]) && (x[b] < x[c]) && (x[c] < x[d]))
                    {
    
    
                        //条件二:xb−xa=2(xd−xc)
                        if((x[b] - x[a]) != 2 * (x[d] - x[c])) continue;
                        //条件三:xb−xa<(xc−xb)/3
                        //处理不能整除的情况
                        int t = (x[c] - x[b]) / 3;
                        if((x[c] - x[b]) % 3) t ++;
                        if((x[b] - x[a]) < t)
                        {
    
    
                            sum[a][1] ++, sum[b][2] ++, sum[c][3] ++, sum[d][4] ++;
                        }
                    }

    for(int i = 1; i <= m; i ++)
    {
    
    
        printf("%d %d %d %d\n", sum[i][1], sum[i][2], sum[i][3], sum[i][4]); 
    }
    
    return 0;
}

算法2

算法思想:桶排序,前缀和,数学,组合计数,乘法原理,加法原理(100分)

分析物品满足魔法阵的三个条件:

  1. x a < x b < x c < x d x_a<x_b<x_c<x_d xa<xb<xc<xd
  2. x b − x a = 2 × ( x d − x c ) x_b−x_a=2\times(x_d−x_c) xbxa=2×(xdxc)
  3. x b − x a < ( x c − x b ) / 3 x_b−x_a<(x_c−x_b)/3 xbxa<(xcxb)/3

为了减少枚举次数,必须找到A、B、C、D类物品魔法值之间的关系,这里 ( x d − x c ) (x_d−x_c) (xdxc)最小,不妨设 t = ( x d − x c ) t = (x_d−x_c) t=(xdxc),那么:

  • x b − x a = 2 t x_b−x_a=2t xbxa=2t
  • 2 t < ( x c − x b ) / 3 2t<(x_c−x_b)/3 2t<(xcxb)/3,整理得 x c − x b > 6 t x_c−x_b>6t xcxb>6t;那么存在正整数 k k k,使得 x c − x b = 6 t + k x_c−x_b=6t+k xcxb=6t+k

那么A、B、C、D类物品魔法值在数轴上满足如下关系:
在这里插入图片描述

t t t的范围

根据题目描述,每个魔法值 x i x_i xi是不超过 n n n的正整数,那么A类物品的最小值为 1 1 1,D类物品的最大值为 n n n,线段AD的最大长度为 n − 1 n-1 n1。而由图可知 A D = 2 t + 6 t + k + t AD=2t+6t+k+t AD=2t+6t+k+t 9 t + k < = n − 1 9t+k<=n-1 9t+k<=n1,而 k k k是正整数,最小值为1,所以 9 t < n − 1 9t<n-1 9t<n1,记作 t < n − 1 9 t<\frac{n-1}{9} t<9n1。即:
1 ≤ t < n − 1 9 1\le t < \frac{n-1}{9} 1t<9n1
确定了t的范围,就可以对红色部分和蓝色部分进行枚举,。

枚举D类物品的魔法值

通过题目描述, D D D的最大值为 n n n,从数轴上看最小值为 9 t + 2 9t+2 9t+2(即 A = 1 A=1 A=1 k = 1 k=1 k=1),由此可以枚举 D D D类物品的魔法值,计算出 C C C的值:
C = D − t C=D-t C=Dt

因为使 A , B , C , D A,B,C,D A,B,C,D满足条件的 k k k的最小值为1,所以对于当前的 C C C D D D A A A的最大值和 B B B的最大值分别为:
A = D − 9 t − 1 A=D-9t-1 A=D9t1 B = 2 t B=2t B=2t
由此根据乘法原理,通过cnt[x](表示魔法值为x的物品的出现次数),计算在当前D值的情况下,

  • 魔法值为 C C C的物品作为魔法阵中编号为c的物品个数:
    c = c n t [ A ] × c n t [ B ] × c n t [ D ] c=cnt[A]\times cnt[B]\times cnt[D] c=cnt[A]×cnt[B]×cnt[D]
  • 魔法值为 D D D的物品作为魔法阵中编号为d的物品个数:
    d = c n t [ A ] × c n t [ B ] × c n t [ C ] d=cnt[A]\times cnt[B]\times cnt[C] d=cnt[A]×cnt[B]×cnt[C]

在其他条件不变的情况下,如果 A A A B B B比最大值小的话,只要 C C C B B B满足 X c − X b > 6 t X_c-X_b>6t XcXb>6t,那么 A , B , C , D A,B,C,D A,B,C,D一定能组成魔法阵。即如果有 a 1 < a 2 , b 1 < b 2 a_1<a_2,b_1<b_2 a1<a2,b1<b2,且 a 2 , b 2 a_2,b_2 a2,b2能够和 C , D C,D C,D组成魔法阵,那么 a 1 , b 1 a_1,b_1 a1,b1也一定能够和 C , D C,D C,D组成魔法阵。因此可以使用前缀和的思想累加cd的值,c[C]d[D]的累加的次数是所有满足要求的 A A A B B Bcnt[A] * cnt[B] 的和,再分别乘以cnt[D]cnt[C],即:
s u m + = c n t [ A ] × c n t [ B ] sum+=cnt[A] \times cnt[B] sum+=cnt[A]×cnt[B] c [ C ] + = s u m × c n t [ D ] c[C]+=sum \times cnt[D] c[C]+=sum×cnt[D] d [ D ] + = s u m × c n t [ C ] d[D]+=sum\times cnt[C] d[D]+=sum×cnt[C]

枚举A类物品的魔法值

同理可以枚举A类物品的魔法值。 A A A的最小值为1,最大值为 n − 9 t − 1 n-9t-1 n9t1(即 D = n D=n D=n k = 1 k=1 k=1)。此时:
B = A + 2 t B=A+2t B=A+2t
D D D C C C的最大值分别为:
D = A + 9 t + 1 D=A+9t+1 D=A+9t+1 C = D − t C=D-t C=Dt
根据乘法原理和前缀和思想:
s u m + = c n t [ C ] × c n t [ D ] sum+=cnt[C]\times cnt[D] sum+=cnt[C]×cnt[D] a [ A ] + = s u m × c n t [ B ] a[A]+=sum\times cnt[B] a[A]+=sum×cnt[B] b [ B ] + = s u m × c n t [ A ] b[B]+=sum\times cnt[A] b[B]+=sum×cnt[A]

时间复杂度

由于 A , B , C , D A, B, C, D A,B,C,D均在1到n之间,因此 1 ≤ t ≤ n − 2 9 1≤t≤\frac{n−2}{9} 1t9n2。所以总枚举次数大约是: n − 2 9 × n = O ( n 2 9 ) = 2.5 × 1 0 7 \frac{n−2}{9}\times n=O(\frac{n^2}{9})=2.5×10^7 9n2×n=O(9n2)=2.5×107

参考文献

魔法阵 NOIP2016T4

C++ 代码

#include <iostream>
#include <cstdio>

using namespace std;

const int N = 15010, M = 40010;
//x[i]表示第i件物品的魔法值
//cnt[i]表示魔法值为i的物品的出现次数
//a[i]表示魔法值为i的作为魔法阵中a物品的次数
int x[M], cnt[N], a[N], b[N], c[N], d[N];

int main()
{
    
    
    int n, m;
    scanf("%d%d", &n, &m);
    
    for(int i = 1; i <= m; i ++) 
    {
    
    
        scanf("%d", &x[i]);
        cnt[x[i]] ++;
    }
    
    //枚举t
    for(int t = 1; 9 * t < n - 1; t ++)
    {
    
    
        int sum = 0;
        //枚举右边区域中D的值
        for(int D = 9 * t + 2; D <= n; D ++)
        {
    
    
            int C = D - t;
            int A = D - 9 * t - 1; //满足条件的A的最大值
            int B = A + 2 * t;
            sum += cnt[A] * cnt[B]; //前缀和累加
            c[C] += sum * cnt[D]; 
            d[D] += sum * cnt[C];
        }
        sum = 0;
        //枚举左边区域A的值
        for(int A = n - 9 * t - 1; A >= 1; A --)
        {
    
    
            int B = A + 2 * t;
            int D = A + 9 * t + 1; //满足条件的D的最大值
            int C = D - t;
            sum += cnt[C] * cnt[D]; //前缀和累加
            a[A] += sum * cnt[B]; 
            b[B] += sum * cnt[A];
        }
    }   
    
    for(int i = 1; i <= m; i ++)
    {
    
    
        printf("%d %d %d %d\n", a[x[i]], b[x[i]], c[x[i]], d[x[i]]); 
    }
    
    return 0;
}

猜你喜欢

转载自blog.csdn.net/qiaoxinwei/article/details/108615141
今日推荐