【NOIP普及组2016】&魔法阵 This is magic!&

来源:互联网 发布:天龙八部九阴神爪 知 编辑:程序博客网 时间:2024/06/13 23:46

2016压轴题-魔法阵

  • 2016压轴题-魔法阵
    • 前言
    • 题目描述
    • 题目分析-暴力枚举Om4
    • 开始优化-桶思想优化On3
    • 高端操作-学不来的数学分析On2
    • END


前言

听闻老前辈们道这道题好像很难的样子,于是我就去做了……
然后我就TLE了
于是偷偷瞟了一眼大老前辈们的博客,发现这道题好像,还是枚举,只是有用到【数学方法】优化罢了
完了完了,一提到数学,我脑子里顿时腾起了层层云雾,所以最后决定还是来写写东西。
来吧,欢迎进入MAGIC的世界


题目描述

六十年一次的魔法战争就要开始了,大魔法师准备从附近的魔法场中汲取魔法能量。
大魔法师有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物品的次数。

输入
输入文件的第一行包含两个空格隔开的正整数n和m。
接下来m行,每行一个正整数,第i+1行的正整数表示Xi,即编号为i的物品的魔法值。
保证1n150001n15000,1m400001m40000,1Xin1Xin。每个Xi是分别在合法范围内等概率随机生成的。

输出
共输出m行,每行四个整数。第i行的四个整数依次表示编号为i的物品作 为A,B,C,D物品分别出现的次数。
保证标准输出中的每个数都不会超过10^9。
每行相邻的两个数之间用恰好一个空格隔开。

样例输入
输入样例#1:
30 8
1
24
7
28
5
29
26
24
输入样例#2:
15 15
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
样例输出
输出样例#1:
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
输出样例#2:
5 0 0 0
4 0 0 0
3 5 0 0
2 4 0 0
1 3 0 0
0 2 0 0
0 1 0 0
0 0 0 0
0 0 0 0
0 0 1 0
0 0 2 1
0 0 3 2
0 0 4 3
0 0 5 4
0 0 0 5

样例说明
样例#1:
共有5个魔法阵,分别为:
物品1,3,7,6,其魔法值分别为1,7,26,29;
物品1,5,2,7,其魔法值分别为1,5,24,26;
物品1,5,7,4,其魔法值分别为1,5,26,28;
物品1,5,8,7,其魔法值分别为1,5,24,26;
物品5,3,4,6,其魔法值分别为5,7,28,29。
以物品5为例,它作为A物品出现了1次,作为B物品出现了3次,没有作为C物品或者D物品出现,所以这一行输出的四个数依次为1,3,0,0。

此外,如果我们将输出看作一个m行4列的矩阵,那么每一列上的m个数之和都应等于魔法阵的总数。所以,如果你的输出不满足这个性质,那么这个输出一定不正确。你可以通过这个性质在一定程度上检查你的输出的正确性。

题目分析-暴力枚举O(m^4)

“这么简单,当然是暴力啦~”
于是一段清晰的思路像一片Sunshine:分别枚举A,B,C,D,判断合法。
条件反射地想到,可以剪枝!在找A,B,C,D及时判断是否合法,可以省去多多的状态。
然后写出来以后,突然瞟了一眼数据范围……
真是莫名其妙。
一段十分失败的枚举代码:

#include<cstdio>int X[40005];int ansA[40005],ansB[40005],ansC[40005],ansD[40005];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++)        {            if( X[A] >= X[B] ) continue;            for(int C=1;C<=m;C++)            {                if( X[B] >= X[C] ) continue;                if( 3 * (X[B] - X[A]) >= X[C] - X[B] ) continue;                for(int D=1;D<=m;D++)                {                    if( X[C] >= X[D] ) continue;                    if( X[B] - X[A] != 2*(X[D] - X[C]) ) continue;                    ansA[A]++;ansB[B]++;ansC[C]++;ansD[D]++;                }            }        }    }    for(int i=1;i<=m;i++)        printf("%d %d %d %d\n",ansA[i],ansB[i],ansC[i],ansD[i]);}//期望35%-实得55%

开始优化-桶思想优化O(n^3)

再次扫一眼题目,我们发现n的范围要远远小于m的范围,也就是说有大部分的数据魔法值其实是相同的。从m出发的话,给你O(m2)你都不一定过得了。
于是我们便想到,如果“从n入手”行不行?
不妨按照魔法值来存储对应魔法值的物品数量,计算方案数时采用乘法原理,输出时访问魔法值对应的值。一段稍带迷雾的代码在我眼前渐渐浮现。再计算一下时间复杂度:O(n4)
还能优化吗?可以。当我们确定A、B、C时,实际上根据XbXa=2(XdXc)就已经可以确定D了。内层循环再一次被省略掉了。O(n3)
感觉考试的时候只能够撑到这里了,再深层次的话……【吐血】
得到差不多一般般的代码

#include<cstdio>int X[40005];int K[15005];int ansA[15005],ansB[15005],ansC[15005],ansD[15005];int main(){    int n,m;    scanf("%d %d",&n,&m);    for(int i=1;i<=m;i++)    {        scanf("%d",&X[i]);        K[X[i]]++;    }    for(int A=1;A<=n;A++)    {        if( K[A] == 0 ) continue;        for(int B=A+1;B<=n;B++)        {            if( K[B] == 0 ) continue;            if( ( B - A ) % 2 == 1 ) continue;            for(int C=(B-A)*3+B+1;C<=n;C++)            {                if( K[C] == 0 ) continue;                int D = C + ( B - A ) / 2;                if( D > n ) continue;                if( K[D] == 0 ) continue;                ansA[A]+=(K[B]*K[C]*K[D]);                ansB[B]+=(K[A]*K[C]*K[D]);                ansC[C]+=(K[A]*K[B]*K[D]);                ansD[D]+=(K[A]*K[B]*K[C]);            }        }    }    for(int i=1;i<=m;i++)        printf("%d %d %d %d\n",ansA[X[i]],ansB[X[i]],ansC[X[i]],ansD[X[i]]);}//期望80%-实得85%

高端操作-学不来的数学分析O(n^2)

先放代码镇镇大佬们的英魂。

#include<cstdio>#include<cstring>int X[16000],a[40005],S[16000];int A[40005],B[40005],C[40005],D[40005];int main(){    int n,m;    scanf("%d %d",&n,&m);    for(int i=1;i<=m;i++)    {        scanf("%d",&a[i]);        X[a[i]]++;    }    for(int i=1;i*9<n;i++)    {        memset(S,0,sizeof(S));        for(int j=2*i+1;j<=n-7*i-1;j++) S[j] = X[j - 2*i] * X[j];        for(int j=1;j<=n;j++) S[j] += S[j-1];        for(int j=9*i+1;j<=n;j++) D[j] += S[j - 7*i - 1] * X[j - i];        for(int j=7*i+1;j<=n-i;j++) C[j] += S[j - 6*i -1] * X[j + i];        memset(S,0,sizeof(S));        for(int j=9*i+1;j<=n;j++) S[j] = X[j - i] * X[j];        for(int j=n;j>=1;j--) S[j] += S[j+1];        for(int j=2*i+1;j<=n-7*i-1;j++) B[j] += S[j + 7*i + 1] * X[j - 2*i];        for(int j=1;j<=n-9*i-1;j++) A[j] += S[j + 9*i +1] * X[j + 2*i];    }    for(int i = 1;i<=m;i++)        printf("%d %d %d %d\n",A[a[i]],B[a[i]],C[a[i]],D[a[i]]);}//期望100%

再发一张大佬用的分析图
魔法阵图解
恩,我们所知道的条件已经全部标在图上了
啥你看不懂?那我稍微解释解释:
图中的直线【你要相信我这真的不是线段】叫做数轴【对不起我以为你不知道】,轴上的点A,B,C,D的值分别表示A,B,C,D的魔法值xa,xb,xc,xd
xa<xb<xc<xd,所以A,B,C,D是从左到右放置的
xbxa=2(xdxc)“-”的几何意义为两点间的距离。如果确定点的左右关系,绝对值可以去掉,改为右减左。设C,D=xdxc=i,所以我们可以得到A,B=xbxa=2i
xbxa<(xcxb)/3,就是这个不等关系最恶心,如果是等量关系就会简单得多。通过这个不等量关系,我们可以得到:B,C=xcxb>6i
感觉卡住了……怎么办?
还是从时间复杂度的角度入手吧:
压到O(1)?想多了
压到O(n)||O(m)?即使确定a,b,c,d,i,这里有个不等关系所以也不好办。
看来只能压到O(n2)了。
我们如果确定了Di,于是B的范围也就能确定了。
反过来,如果我们确定了在某个范围内的B,于是……?
好像有点希望了?!
a[j]=(B==j)时选择AB的方案数”,S[j]=(B<=j)AB的方案数”,则a[j]=X[j]X[j2i]显然我们有S[j]=S[j1]+a[j],于是我们便可以在O(n)的时间内算出S
可以得到确定D=t时,方案数 = S[t7i1]X[ji],即A、B的方案数与C点的方案数累乘。O(n)
同理,也可在O(n)内求出A=t,B=t,C=t的方案数。
woc这么高大上的吗!
果然NOIP普及压轴题都是思维复杂,挑战脑力,代码极其* *的题吗!

END

THANKS FOR READING THERE!
就是这样,新的一天里,也请多多关照哦(ノω<。)ノ))☆.。~

原创粉丝点击