2016NOIP普及组真题 4. 魔法阵

2024-04-16 06:36

本文主要是介绍2016NOIP普及组真题 4. 魔法阵,希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!

线上OJ:

一本通:http://ybt.ssoier.cn:8088/problem_show.php?pid=1976

本题作为第四题,想拿满分有难度。但是暴力拿些分还是做得到的。
满分需要用 前缀和 来化简for循环。

核心语句:

$ x_a < x_b < x_c < x_d $ ①
$ x_b - x_a = 2(x_d - x_c) $ ②
$ x_b - x_a < (x_c - x_b)/3 $ ③

核心思想:

由于魔法值 n 不超过 15000,但魔法球的数量 m 可达到40000。
所以选择反向枚举答案:即对所有的魔法值进行枚举(先找出 n 范围内所有符合上述三个公式的魔法组合数字),这样可避免对魔法球进行排序。

另外,由于魔法球的魔法数值可能相同,所以在计算每一个数字的出现次数时,要考虑其他数字的 组合 出现情况。如下图所示:

在这里插入图片描述

上图中,1# 魔法球在物品A中出现的次数为6次,分别为:
1#,2#,3#,4#
1#,2#,7#,4#
1#,2#,9#,4#
1#,6#,3#,4#
1#,6#,7#,4#
1#,6#,9#,4#

上图中,2# 魔法球在物品B中出现的次数为9次,分别为:
1#,2#,3#,4#
1#,2#,7#,4#
1#,2#,9#,4#
5#,2#,3#,4#
5#,2#,7#,4#
5#,2#,9#,4#
8#,2#,3#,4#
8#,2#,7#,4#
8#,2#,9#,4#

所以每一个魔法球在某个位置出现的 次数 = 剩余位置魔法球数量的乘积(组合思想)

题解代码:
解法一、
#include <bits/stdc++.h>
#define MAXN 40005
using namespace std;struct Node
{int val, id;	// 存储节点的值和 id。 id用于最终的输出
};
Node node[MAXN];int n, m;	// m 个魔法物; 魔法值不超过 n
int ge[MAXN]; // 记录val为i的魔法物的个数。用于计算组合
int cnta[MAXN], cntb[MAXN], cntc[MAXN], cntd[MAXN];		// cnta[i]表示val为i的魔法物在A位置出现的次数
// cntb[i]表示val为i的魔法物在B位置出现的次数, cntc[i]表示val为i的魔法物在C位置出现的次数 等// 暴力枚举 70% 分数。
int main()
{scanf("%d%d",&n, &m);int x;for(int i = 1; i <= m; i++){scanf("%d", &x);node[i].val = x;node[i].id = i;ge[x]++;  // 统计魔法值为x的物品个数}// 反向枚举结果:对所有的魔法值进行枚举(即先找出 n 范围内所有符合要求的魔法组合数字),可避免排序。// 不是对每个魔法球的魔法值进行枚举(每一轮都是枚举40000和枚举15000的区别)for(int ai = 1; ai <= n - 3; ai++)for(int bi = ai + 1; bi <= n - 2; bi++)for(int ci = bi + 1; ci <= n - 1; ci++)for(int di = ci + 1; di <= n; di++)if( ( (bi-ai) == 2*(di-ci) ) && ( 3*(bi-ai) < (ci-bi) ) ){	// 如果找到一组符合要求的魔法值,则更新各数字在对应位置出现的次数// 考虑到不同的魔法球会有相同的魔法值,所以在作组合计算时对其余位置作数量的乘法cnta[ai] += ge[bi] * ge[ci] * ge[di];cntb[bi] += ge[ai] * ge[ci] * ge[di];cntc[ci] += ge[ai] * ge[bi] * ge[di];cntd[di] += ge[ai] * ge[bi] * ge[ci];break;	// 1个abc只能对应1个d,如果找到了,直接退出循环}for(int i = 1; i <= m; i++){int t = node[i].val;printf("%d %d %d %d\n",cnta[t], cntb[t], cntc[t], cntd[t]);}return 0;
}

以上暴力代码比较简单,只要注意 反向枚举,就可以在考场上快速拿到70%的分数。

解法二、优化区间

接下来对解法一进行优化。
把对公式③ $ x_b - x_a < (x_c - x_b)/3 $ 的利用,从条件判断改为直接赋值给ci的范围。
因为 x b − x a < ( x c − x b ) / 3 x_b-x_a < (x_c-x_b)/3 xbxa<(xcxb)/3,移项后可得 x c > 4 x b − 3 x a x_c > 4x_b-3x_a xc>4xb3xa, 所以 x c x_c xc 4 x b − 3 x a + 1 4x_b-3x_a+1 4xb3xa+1开始取值。
所以 c i = 4 ∗ b i − 3 ∗ a i + 1 ci = 4 * bi - 3 * ai + 1 ci=4bi3ai+1
同时,由于1个abc只能对应1个d,如果找到了符合条件的d,就退出最内层的循环。

#include <bits/stdc++.h>
#define MAXN 40005
using namespace std;struct Node
{int val, id;	// 存储节点的值和 id。 id用于最终的输出
};
Node node[MAXN];int n, m;	// m 个魔法物; 魔法值不超过 n
int ge[MAXN]; // 记录val为i的魔法物的个数。用于计算组合
int cnta[MAXN], cntb[MAXN], cntc[MAXN], cntd[MAXN];		// cnta[i]表示val为i的魔法物在A位置出现的次数
// cntb[i]表示val为i的魔法物在B位置出现的次数, cntc[i]表示val为i的魔法物在C位置出现的次数 等// 暴力枚举 80% 分数。
int main()
{scanf("%d%d",&n, &m);int x;for(int i = 1; i <= m; i++){scanf("%d", &x);node[i].val = x;node[i].id = i;ge[x]++;  // 统计魔法值为x的物品个数}// 反向枚举结果:对所有的魔法值进行枚举(即先找出 n 范围内所有符合要求的魔法组合数字),可避免排序。// 不是对每个魔法球的魔法值进行枚举(每一轮都是枚举40000和枚举15000的区别)for(int ai = 1; ai <= n - 3; ai++)for(int bi = ai + 1; bi <= n - 2; bi++)for(int ci = 4 * bi - 3 * ai + 1; ci <= n - 1; ci++)for(int di = ci + 1; di <= n; di++)if( (bi-ai) == 2*(di-ci) ){   // 如果找到一组符合要求的魔法值,则更新各数字在对应位置出现的次数// 考虑到不同的魔法球会有相同的魔法值,所以在作组合计算时对其余位置作数量的乘法cnta[ai] += ge[bi] * ge[ci] * ge[di];cntb[bi] += ge[ai] * ge[ci] * ge[di];cntc[ci] += ge[ai] * ge[bi] * ge[di];cntd[di] += ge[ai] * ge[bi] * ge[ci];break;	// 1个abc只能对应1个d,如果找到了,直接退出循环}for(int i = 1; i <= m; i++){int t = node[i].val;printf("%d %d %d %d\n",cnta[t], cntb[t], cntc[t], cntd[t]);}return 0;
}

以上代码优化后可以拿到80%分数。
继续优化。

解法三、四层循环优化为三层

在这里插入图片描述

公式 ①②③ 告诉了我们几组关系,
1、如果CD之间的距离为一个步长,则AB之间的距离必为两倍步长,且BC之间的距离要大于六倍步长
2、假设步长为 k, 且已知 ai,则
● bi 可直接推断出为 = ai+2k
● ci 的左边界可以推断出 = ai + 8k + 1,从左边界向右枚举即可
● di 可直接推断出为 = ci+k = ai + 9k + 1
如此一来,之前的四层for循环就变成了三层。只需要枚举步长k,ai和ci即可。代码见下

#include <bits/stdc++.h>
#define MAXN 40005
using namespace std;struct Node
{int val, id;	// 存储节点的值和 id。 id用于最终的输出
};
Node node[MAXN];int n, m;	// m 个魔法物; 魔法值不超过 n
int ge[MAXN]; // 记录val为i的魔法物的个数。用于计算组合
int cnta[MAXN], cntb[MAXN], cntc[MAXN], cntd[MAXN];		// cnta[i]表示val为i的魔法物在A位置出现的次数
// cntb[i]表示val为i的魔法物在B位置出现的次数, cntc[i]表示val为i的魔法物在C位置出现的次数 等// 暴力枚举 90% 分数
int main()
{scanf("%d%d",&n, &m);int x;for(int i = 1; i <= m; i++){scanf("%d", &x);node[i].val = x;node[i].id = i;ge[x]++;  // 统计魔法值为x的物品个数}for(int k = 1; 9 * k < n; k++ )for(int ai = 1; ai <= n - 9*k; ai++)	// ai步长为2,可保证bi-ai为2的整数倍{int bi = ai + 2*k;for(int ci = ai + 8*k + 1; ci <= n - k; ci++)	// 根据公式3,枚举ci{				int di = ci + k;cnta[ai] += ge[bi] * ge[ci] * ge[di];cntb[bi] += ge[ai] * ge[ci] * ge[di];cntc[ci] += ge[ai] * ge[bi] * ge[di];cntd[di] += ge[ai] * ge[bi] * ge[ci];				}	}						for(int i = 1; i <= m; i++){int t = node[i].val;printf("%d %d %d %d\n",cnta[t], cntb[t], cntc[t], cntd[t]);}return 0;
}

以上代码优化后可以拿到90%分数。依然没达到满分。
继续优化。

解法四、三层循环优化为两层(前缀和+后缀和)

三层for循环依然会超时,故如果满分需要压缩到2层for循环。我们看下面这张图,会发现:

1、从右往左看,当 CD固定 时,AB可以移动,移动的 右边界 是BC距离为6k+1。
即:右边界左侧每一对 间隔 步长2kAB 都符合题意。所以对AB点的计算可以采用 前缀和

在这里插入图片描述

2、同理(图二),从左往右 看,当 AB固定时 ,CD可以移动,移动的 左边界 是BC距离为6k+1。
即:左边界右侧 的每一对间隔 步长kCD 都符合题意。所以对CD点的计算可以采用后缀和(因为从后往前计算)。

在这里插入图片描述

详细代码见下:

#include <bits/stdc++.h>
#define MAXN 40005
using namespace std;struct Node
{int val, id;	// 存储节点的值和 id。 id用于最终的输出
};
Node node[MAXN];int n, m;	// m 个魔法物; 魔法值不超过 n
int ge[MAXN]; // 记录val为i的魔法物的个数。用于计算组合
int cnta[MAXN], cntb[MAXN], cntc[MAXN], cntd[MAXN];		// cnta[i]表示val为i的魔法物在A位置出现的次数
// cntb[i]表示val为i的魔法物在B位置出现的次数, cntc[i]表示val为i的魔法物在C位置出现的次数 等// 暴力枚举 90% 分数。
// 枚举ai,bi,ci。根据公式2,如果bi-ai不满足是2的倍数,则不存在ci和di,故直枚举下一个bi。
// 由于1个ai、bi、ci只能对应1个di。所以在枚举ci时,对di进行直接计算即可。
int main()
{scanf("%d%d",&n, &m);int x;for(int i = 1; i <= m; i++){scanf("%d", &x);node[i].val = x;node[i].id = i;ge[x]++;  // 统计魔法值为x的物品个数}// 3层for循环依然会超时,故需要压缩到2层for循环// 由于当 di 固定时,ci 就固定。当 ci 固定时,bi能移动的右边界就固定,ai的右边界也就固定。所以,只要不超过右边界的每一对bi和ai都满足已知固定di和ci的魔法阵。// 所以,当di和ci固定时,只有bi和ai是变量,此时对每一对bi和ai求前缀和。同理,当ai 和 bi 固定时,对每一对ci和di求后缀和。for(int k = 1; 9 * k < n; k++ ){int sumab = 0;// 因为对ai和bi求前缀和,所以di枚举的顺序是从小到大。所以di从9*k + 2开始(9*k+1+A的起始是1)for(int di = 9*k + 2; di <= n; di++){int ci = di - k;	// 当 di 固定时,ci 也固定int bi = di - 7*k - 1; // 当ci固定时,bi的右边界可固定int ai = di - 9*k - 1; // 当bi的右边界固定时,ai的右边界也可固定sumab += ge[ai] * ge[bi];	// 由于di是从最小值枚举,所以ai和bi可以用前缀和记录每一组有效状态cntc[ci] += sumab * ge[di];cntd[di] += sumab * ge[ci];}int sumcd = 0;for(int ai = n - 9*k - 1; ai >= 1; ai--){int bi = ai + 2*k;  // 当 ai 固定时,bi 也固定int ci = ai + 8*k + 1; // 当bi固定时,ci的左边界可固定int di = ai + 9*k + 1; // 当ci的左边界固定时,di的左边届也固定sumcd += ge[ci] * ge[di];  // 由于ai是从大到小枚举,所以ci和di可以用后缀和cnta[ai] += ge[bi] * sumcd; cntb[bi] += ge[ai] * sumcd;}      }				for(int i = 1; i <= m; i++){int t = node[i].val;printf("%d %d %d %d\n",cnta[t], cntb[t], cntc[t], cntd[t]);}return 0;
}

以上第四种方法详细讲解可参见 https://www.luogu.com.cn/article/4lsb9owi

这篇关于2016NOIP普及组真题 4. 魔法阵的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



http://www.chinasem.cn/article/908036

相关文章

笔试强训,[NOIP2002普及组]过河卒牛客.游游的水果大礼包牛客.买卖股票的最好时机(二)二叉树非递归前序遍历

目录 [NOIP2002普及组]过河卒 牛客.游游的水果大礼包 牛客.买卖股票的最好时机(二) 二叉树非递归前序遍历 [NOIP2002普及组]过河卒 题里面给的提示很有用,那个马的关系,后面就注意,dp需要作为long的类型。 import java.util.Scanner;// 注意类名必须为 Main, 不要有任何 package xxx 信息publ

找不同-第15届蓝桥省赛Scratch初级组真题第4题

[导读]:超平老师的《Scratch蓝桥杯真题解析100讲》已经全部完成,后续会不定期解读蓝桥杯真题,这是Scratch蓝桥杯真题解析第183讲。 如果想持续关注Scratch蓝桥真题解读,可以点击《Scratch蓝桥杯历年真题》并订阅合集,查阅教程更方便。 第15届蓝桥杯省赛已于2024年8月24日落下帷幕,编程题一共有5题,分别如下: 猪八戒落地 游乐场 画西瓜 找不同 消

信息学奥赛初赛天天练-83-NOIP2014普及组-基础题2-输入设备、输出设备、操作系统、二进制、整数除法、while、do while循环

1 NOIP 2014 普及组 基础题2 4 以下哪一种设备属于输出设备( ) A 扫描仪 B 键盘 C 鼠标 D 打印机 5 下列对操作系统功能的描述最为完整的是( ) A 负责外设与主机之间的信息交换 B 负责诊断机器的故障 C 控制和管理计算机系统的各种硬件和软件资源的使用 D 将没有程序编译成目标程序 11 下列各无符号十进制整数中,能用八位二进制表示的数中最大的是( ) A 296

免费SSL证书大全,加速普及网站实现HTTPS加密

免费SSL证书大全,加速普及网站实现HTTPS加密   SSL 证书用于加密 HTTP 协议,实现网站通过HTTPS加密协议访问。随着国内外各大网站实现全站 HTTPS 协议,以及搜索引擎对使用 HTTPS 协议网站的更加友好,加之互联网对数据和隐私安全的加强,网站添加 SSL 证书并实现 HTTPS 加密协议访问已经成为趋势和必然。 SSL证书大全​zzzjtd.com 而对于给网站添加

讨论运维监控工具的普及程度

在讨论运维监控工具的普及程度时,加入PIGOSS BSM产品的分析是非常有意义的,因为PIGOSS BSM是一款在中国市场具有一定影响力的运维监控工具。 PIGOSS BSM运维监控工具是一款综合性的IT运维监控解决方案,它能够对多层次的IT资源进行监测,包括但不限于性能监测、事件管理、报表管理等功能模块。PIGOSS BSM的一个独特之处在于其拓扑关联配置工具,这使得用户可以根据自身的I

P2239 [NOIP2014 普及组] 螺旋矩阵

P2239 [NOIP2014 普及组] 螺旋矩阵 50分 //O(n^2)复杂度,能算n<=10000的 #include <bits/stdc++.h>using namespace std;//row当前行, column当前列, left:左边界,righ:右边界,top:上边界,bottom:下边界 int n, x, y, ans, row=1, column=0, lef,

2020计算机挑战赛Java组真题

单选题 1.下列叙述哪些是错误的(). A.final 类不可以有子类 B.构造方法是类的一种特殊方法,其方法名必须与类名相同 C.抽象类可以用new运算符创建对象 D.内存回收程序不允许程序员直接释放内存2.下列叙述哪些是错误的(). A.abstract类不可以用new和构造函数定义对象 B.构造方法的返回值类型只能是void型 C.内存回收程序负责释放无用内存 D.Java类只能是单继承的

信息学奥赛初赛天天练-80-NOIP2015普及组-基础题5-错位排列、二叉树、完全二叉树、叶子节点、完全二叉树叶子节点

NOIP 2015 普及组 基础题5 21 重新排列 1234使得每一个数字都不在原来的位置上,一共有( )种排法 22 一棵结点数为 2015的二叉树最多有( )个叶子结点 2 相关知识点 1) 错位排列 考虑一个有n个元素的排列,若一个排列中所有的元素都不在自己原来的位置上,那么这样的排列就称为原排列的一个错排。 n个元素的错排数记为D(n) 错排问题最早被尼古拉·伯努利和欧拉研究

蓝桥等级考1级c++组真题(选择)第一套(含答案解析)

纯个人整理,如有错误欢迎指正 1.以下关于C++程序描述错误的一项是() A.完整的程序必须有且仅有一个主函数,主函数的名字为main B.程序可以调用库函数,但必须要包含相对应的头文件 C.程序可以有注释行,注释行会被编译,但是不会被执行 D.程序的语句以分号结束,分号是c++程序不可缺少的组成部分 答案:C  解析: A. 程序有且只能有一个主函数,且名字必须为main B.

信息学奥赛初赛天天练-79-NOIP2015普及组-基础题4-即时通讯软件、二叉树遍历、前序遍历、中序遍历、后序遍历、算法时间复杂度

NOIP 2015 普及组 基础题4 11 下面哪种软件不属于即时通信软件( ) A QQ B MSN C 微信 D P2P 16 前序遍历序列与中序遍历序列相同的二叉树为( ) A 根结点无左子树 B 根结点无右子树 C 只有根结点的二叉树或非叶子结点只有左子树的二叉树 D 只有根结点的二叉树或非叶子结点只有右子树的二叉树 18 下列选项中不属于视频文件格式的是( ) A TXT B AV