【NOIP普及组2016】魔法阵

@魔法阵@

    • @前言@
    • @题目描述@
    • @题目分析-暴力枚举O(m^4)@
    • @开始优化-桶思想优化O(n^3)@
    • @高端操作-学不来的数学分析O(n^2)@
    • @END@


@前言@

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


@题目描述@

六十年一次的魔法战争就要开始了,大魔法师准备从附近的魔法场中汲取魔法能量。
大魔法师有m个魔法物品,编号分别为 1 , 2 , . . . , m 1,2,...,m 1,2,...,m。每个物品具有一个魔法值,我们用Xi表示编号为i的物品的魔法值。每个魔法值Xi是不超过n的正整数,可能有多个物品的魔法值相同。
大魔法师认为,当且仅当四个编号为 a , b , c , d a,b,c,d a,b,c,d的魔法物品满足 x a < x b < x c < x d , x b − x a = 2 ( x d − x c ) xa<xb<xc<xd,xb-xa=2(xd-xc) xa<xb<xc<xdxbxa=2(xdxc),并且 x b − x a < ( x c − x b ) / 3 xb-xa<(xc-xb)/3 xbxa<(xcxb)/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 ≤ X i ≤ n 1 ≤ X i ≤ n 1 \le n \le 150001≤n≤15000,1 \le m \le 400001≤m≤40000,1 \le Xi \le n1≤Xi≤n 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 A,B,C,D,判断合法。
条件反射地想到,可以剪枝!在找 A , B , C , D A,B,C,D A,B,C,D及时判断是否合法,可以省去多多的状态。
然后写出来以后,突然瞟了一眼数据范围……
真是莫名其妙。
一段十分失败的枚举代码:

#include
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 ( m 2 ) O(m^2) O(m2)你都不一定过得了。
于是我们便想到,如果**“从n入手”**行不行?
不妨按照魔法值来存储对应魔法值的物品数量,计算方案数时采用乘法原理,输出时访问魔法值对应的值。一段稍带迷雾的代码在我眼前渐渐浮现。再计算一下时间复杂度: O ( n 4 ) O(n^4) O(n4)
还能优化吗?可以。当我们确定A、B、C时,实际上根据 X b − X a = 2 ( X d − X c ) Xb-Xa=2(Xd-Xc) XbXa=2(XdXc)就已经可以确定D了。内层循环再一次被省略掉了。 O ( n 3 ) O(n^3) O(n3)
感觉考试的时候只能够撑到这里了,再深层次的话……【吐血】
得到差不多一般般的代码

#include
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
#include
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%

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

@END@

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

你可能感兴趣的:(@考试爆炸实录!@,@常用技巧看这里!@)