您的位置:首页 > 其它

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

2017-10-20 13:42 148 查看

2016压轴题-魔法阵

2016压轴题-魔法阵
前言

题目描述

题目分析-暴力枚举Om4

开始优化-桶思想优化On3

高端操作-学不来的数学分析On2

END

前言

听闻老前辈们道这道题好像很难的样子,于是我就去做了……

然后我就TLE了

于是偷偷瞟了一眼大老前辈们的博客,发现这道题好像,还是枚举,只是有用到【数学方法】优化罢了

完了完了,一提到数学,我脑子里顿时腾起了层层云雾,所以最后决定还是来写写东西。

来吧,欢迎进入MAGIC的世界

题目描述

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

大魔法师有m个魔法物品,编号分别为1,2,...,m。每个物品具有一个魔法值,我们用Xi表示编号为i的物品的魔法值。每个魔法值Xi是不超过n的正整数,可能有多个物品的魔法值相同。

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

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

输入

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

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

保证1≤n≤150001≤n≤15000,1≤m≤400001≤m≤40000,1≤Xi≤n1≤Xi≤n。每个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 ) 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)你都不一定过得了。

于是我们便想到,如果[b]“从n入手”
行不行?

不妨按照魔法值来存储对应魔法值的物品数量,计算方案数时采用乘法原理,输出时访问魔法值对应的值。一段稍带迷雾的代码在我眼前渐渐浮现。再计算一下时间复杂度:O(n4)

还能优化吗?可以。当我们确定A、B、C时,实际上根据Xb−Xa=2(Xd−Xc)就已经可以确定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 == 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是从左到右放置的

xb−xa=2(xd−xc),[b]“-”
的几何意义为两点间的距离。如果确定点的左右关系,绝对值可以去掉,改为右减左。设C,D两点间的距离=xd−xc=i,所以我们可以得到A,B两点间的距离=xb−xa=2∗i

xb−xa<(xc−xb)/3,就是这个不等关系最恶心,如果是等量关系就会简单得多。通过这个不等量关系,我们可以得到:B,C两点间的距离=xc−xb>6∗i。

感觉卡住了……怎么办?

还是从时间复杂度的角度入手吧:

压到O(1)?想多了

压到O(n)||O(m)?即使确定a,b,c,d,i,这里有个不等关系所以也不好办。

看来只能压到O(n2)了。

我们如果确定了D与i,于是B的范围也就能确定了。

反过来,如果我们确定了在某个范围内的B,于是……?

好像有点希望了?!

令a[j]=”(B==j)时选择A、B的方案数”,S[j]=”(B<=j)时A、B的方案数”,则a[j]=X[j]∗X[j−2∗i],显然我们有S[j]=S[j−1]+a[j],于是我们便可以在O(n)的时间内算出S数组

可以得到确定D=t时,方案数 = S[t−7∗i−1]∗X[j−i],即A、B的方案数与C点的方案数累乘。O(n)。

同理,也可在O(n)内求出A=t,B=t,C=t的方案数。

woc这么高大上的吗!

果然NOIP普及压轴题都是思维复杂,挑战脑力,代码极其* *的题吗!

END

THANKS FOR READING THERE!

就是这样,新的一天里,也请多多关照哦(ノω<。)ノ))☆.。~
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: