2019.6.27 一场彻底让人自闭的考试【including 入门OJ·卡片游戏,泡泡浴,树的合并

本文主要是介绍2019.6.27 一场彻底让人自闭的考试【including 入门OJ·卡片游戏,泡泡浴,树的合并,希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!

初见,安。

这篇博客的标题是一个月前zy就提出来的。现在终于派上用场了呢。

怎么就自闭了呢?因为想到了但是总有没想到的地方,或者写不出代码,或者是根本就没有往正确的方向去想……

题目的包装很棒啊,前两个都是那种第一反应得到的算法并不是正解的那种。

本以为第一题至少还是可以得个100凑个三位数的,结果就这样又考虑漏了一些东西,丢了20分。后面俩题都WA完了……

卡片游戏

传送门:入门OJ P2056

Description

有N只moreD在玩一个卡片游戏:
首先,桌子上有M张卡片,这M张卡片分别有一个唯一的1~M的编号。N只moreD在桌子上抢牌
。每个人最后的得分是所得的所有卡片编号的乘积(如果一张卡片都没取,得分为1)。
这N只moreD最后报出了自己的得分。你的任务是写一个程序,判断有没有人说谎。

Input

输入第一行一个整数T,表示T组测试数据。
对于每组测试数据:
第一行:两个用空格隔开的整数:N和M,表示moreD的数量和卡片的数量
第二行:有N个正整数Ai,表示每只moreD报出的得分。
N<=5  M<=100 Ai<=50000 t<=10

Output

输出T行,每行输出'Yes'或'No',表示'Yes'表示不可能没有人说谎 , 'No'表示可能没有人
说谎。

Sample Input

3
2 3
2 3
2 3
3 6
2 5
4 6

Sample Output

No
Yes
No
//对于第一个数据,存在第一个人抢到编号为2的卡片,第二个人抢到编号为3的卡片就可以满
足这样的情形了,所以可能没有人说谎。
对于第二个数据,不存在任何一种抢牌方案使得两人的得分满足这样的情形,所以不可能没
有人说谎。

Sol

先说一下80分的做法——因为很好想到,就是分解质因数,统计每个数分解下来各个质因数出现的次数再前缀和累加起来,再分解当前的每个人的得分,看是否有哪个质因数出现的次数大于我们预处理的结果,有则有人说谎。最后下来如果都没有超过的,那么就没有人说谎

但这样的思路有一个漏洞,就是说如果分解过后剩下来的质因数并不能组成一个m以内的没有出现过的数呢?就比如这组数据:

1
2 7
36 70

36 = 2^2*3^2,70 = 2 *5*7,7以内各个质因数出现次数为:2——4次,3——2次,5和7各一次。也就是说最后会剩下一个2没有被用到,但是如果我把这两个数当做卡片拆开的话就是:36=2*3*6,70=2*5*7,会发现怎么都不能不重复地划分。所以其实是不合法的。也就是说我们只能每个数作为整体考虑。

那么正解怎么做呢——爆搜。【??!?!是的我没骗你,爆搜……你看n和m的范围这么小直接爆搜枚举当前这个人的得分能不能整除当前枚举到的卡片,能则除,递归,再递归一个不除的。

中途几乎不需要剪枝,特判一下如果答案已经确定为找得到合法解了,那就不用搜了。

#include<bits/stdc++.h>
using namespace std;
int T, n, a[10], m;
bool flag = false, vis[104];
void dfs(int x, int y, int ans) {//x为当前人,y为当前卡片,ans为当前人整除某些卡片过后剩下的部分if(flag) return;//已经有正解了就不用搜了if(x > n) {flag = true; return;}//顺利扫完了n个人,解合法。if(y > m) {//卡片扫完了,那就从头再来……就算是O(nm)也不会爆if(ans == 1) dfs(x + 1, 1, a[x + 1]);return;}if(!vis[y] && ans % y == 0) {//这张卡片没有用过并且可以存在于这个人的得分中vis[y] = true;dfs(x, y + 1, ans / y);vis[y] = false;//dfs还原}dfs(x, y + 1, ans);
}int main() {scanf("%d", &T);while(T--) {flag = false;memset(vis, 0, sizeof vis);scanf("%d%d", &n, &m);for(int i = 1; i <= n; i++) scanf("%d", &a[i]);dfs(1, 1, a[1]);if(flag) puts("No");else puts("Yes");}
}

代码是参考了某篇博客,现在找不到了QwQ

泡泡浴

传送门:入门OJ P2057

Description

其实moreD是一个十分爱干净的孩子,最近moreD迷恋上了泡泡浴。迷恋泡泡浴的原因除了泡泡特别好玩之外,还由
于洗泡泡浴的浴缸十分奇怪,这是个地面参差不齐的浴缸。我们可以把浴缸看成N*M的矩阵,那么矩阵上的每个元
素为该位置浴缸的高度。由于水往低处流,相邻的格子的水面高度不同,高处的水会往低处去。浴缸当然要有出水
口,出水口在某个格子上。而且浴缸的边缘可以看作无穷高。然后大家肯定会发现,洗完泡泡浴后,把水放掉后,
可能有些地方会积水。即水不能通过出水口流出。moreD就是喜欢看看这些积水。moreD测量了一下,自己放水之前
浴缸的所有位置的水面高度均为K(除了浴缸底部高度大于K的格子)。如果浴缸底部高度大于K,那么这个格子没有
水,水面高度可以当作是浴缸底部高度。然后moreD打开了出水口。请你帮moreD预测一下,当出水口不再出水了,
浴缸每个格子的剩余的水高度是多少。(剩余的水的高度=水面高度-浴缸底部高度)

Input

输入第一行仅包含三个整数N,M,K,表示浴缸的大小与初始每个格子的水面高度。
接下来N行,每行M个整数,表示每个格子浴缸底部的高度A[i,j]。
接下来一行两个数r和c,表示出水口的行和列。
N,M<=800  A[I,j]<=10000

Output

输出N行,每行包含M个整数,表示每个格子的剩余水的高度。

Sample Input

3 3 66
6 9 1
7 8 1
6 8 1
3 2

Sample Output

2 0 7
1 0 7
2 0 7

Sol

第一反应——bfs!从排水口开始bfs!计算每个位置的水量!然后,样例过了,测试数据全部WA掉了……

我们这样考虑——对于每一个格子的水会被排多少,会剩下多少,我们就先从大局上只考虑最后每个格子相对与地面的水位线有多高,而并非对于每个格子的高度来说水位线有多高。这样的话,我们只需要得到每个格子的水位线,再减去这个格子的高度就可以了

对于每个格子的水位线高度【相对与地面】,取决于所有路径上最高的那个格子的最矮值。因为对于每一条路径都可以看做是一维的,而高的格子会挡住去路,让水积蓄;如果有别的路径可以放水并且最高的那个格子的高度更低,那么最后剩的水就会取决于那个更低的路径。

所以我们需要维护的就是:从出水口到每个格子的所有路径上的最高点的最矮点。好像也可以直接bfs啊!!!但是比bfs还要偏向另一个算法——最短路。可以用SPFA——这个基于BFS的最短路算法变形维护。既然SPFA可以,同理Dijkstra就也可以了。【????】所以最后代码写出来是一个bfs和最短路的结合做法【感觉上哪个都不算……】

甚至是可以用最短路的堆优化……让最高点更低的路径优先……

题目未免包装的太好了……

#include<algorithm>
#include<iostream>
#include<cstring>
#include<cstdio>
#include<cmath>
#include<queue>
#define maxn 805
using namespace std;
int read() {int x = 0, f = 1, ch = getchar();while(!isdigit(ch)) {if(ch == '-') f = -1; ch = getchar();}while(isdigit(ch)) x = (x << 1) + (x << 3) + ch - '0', ch = getchar();return x * f;
}int n, m, r, c, water, w[maxn][maxn], h[maxn][maxn], dis[maxn][maxn];
int dir[4][2] = {{0, 1}, {-1, 0}, {1, 0}, {0, -1}};//形如bfs的设定……
struct node {int x, y, dis;//dis和dis数组含义同node() {}node(int xx, int yy, int dd) {x = xx, y = yy, dis = dd;}bool operator < (const node &x) const {return x.dis < dis;}//从小到大排序【优先队列反着重定义
}now;bool in(int x, int y) {return 0 < x && x <= n && 0 < y && y <= m;}
void dij() {memset(dis, 0x3f, sizeof dis);dis[r][c] = h[r][c];//dis为到点(i,j)所有路径的最高点的最矮值priority_queue<node> q;register int tx, ty;q.push(node(r, c, 0)); node now;while(q.size()) {now = q.top(); q.pop();for(int i = 0; i < 4; i++) {tx = now.x + dir[i][0], ty = now.y + dir[i][1];if(!in(tx, ty)) continue;if(max(dis[now.x][now.y], h[tx][ty]) < dis[tx][ty]){//路径上的最高点比目前的最高点要低dis[tx][ty] = max(dis[now.x][now.y], h[tx][ty]);//更新if(dis[tx][ty] < water) q.push(node(tx, ty, dis[tx][ty]));//高于初始水位线就不能更新别人了。一定是负数的值。}}}
}signed main() {n = read(), m = read(), water = read();for(int i = 1; i <= n; i++)for(int j = 1; j <= m; j++) {h[i][j] = read();//h是高度}r = read(), c = read();dij();for(int i = 1; i <= n; i++) {for(int j = 1; j <= m; j++)printf("%d ", max(0, min(water, dis[i][j]) - h[i][j]));//注意要和0取max,避免负数puts("");}return 0;
}

树的合并

传送门:入门OJ P2058

Description

话说moreD经过不懈努力,终于背完了循环整数,也终于完成了他的蛋糕大餐。
但是不幸的是,moreD得到了诅咒,受到诅咒的原因至今无人知晓。
moreD在发觉自己得到诅咒之后,决定去寻找闻名遐迩的术士CD帮忙。
话说CD最近在搞OI,遇到了一道有趣的题目:
给定两棵树,则总共有N*M种方案把这两棵树通过加一条边连成一棵树,那这N*M棵树的直径
(树的直径指的是树上的最长简单路径)
大小之和是多少呢?
CD为了考验moreD是否值得自己费心力为他除去诅咒,于是要他编程回答这个问题,但是这m
oreD早就被诅咒搞晕了头脑,就只好请你帮助他了。

Input

第一行两个正整数N,M,分别表示两棵树的大小。
接下来N-1行,每行两个正整数ai,bi,表示第一棵树上的边。
接下来M-1行,每行两个正整数ci,di,表示第二棵树上的边。
N<=10^5,M<=10^5,1<=ai,bi<=N,1<=ci,di<=M

Output

一行一个整数,表示答案。

Sample Input

4 3
1 2
2 3
2 4
1 3
2 3

Sample Output

53

Sol

一看就是树形dp的经典题。给你两棵树,可以连一条边合并起来,求所有合并方式的直径和。

两步——1.预处理出每个点可向上or向下延伸的最长长度

————2.累加答案。

所以难点就在——1.如何预处理

————————2.如何在O(nlogn)以内累加答案。因为方案数明显是m*n种。

先处理第一个问题——随手画个图。

首先处理向下延伸——很好处理,设当前节点为u,可以直接设定为以u为根的子树最深的节点的深度 - u的深度。我们设这个数组为f[u]

再者就是向上延伸——就比如上图对于点4,有两种情况:1.从点2的向上延伸的最长长度+1转移过来;2.从同为点2的子树的别的子节点向上走到点2再往下走转移过来。我们设dp[u]为u向上延伸的最长长度,则对于u的子节点v,第一种情况为:

dp[v] = max(dp[v], dp[u] +1)

那么第二种情况呢?似乎不能直接用f[u]的值呢,因为如果就像点4一样,就刚好属于f[u]所指的子树,这就不是一条合法路径了。所以我们还要维护一个变量g[u]表示不和f[u]走相同的u的儿子的另一条最长的向下延伸的路径。可以说是所谓的次长路径,当然在维护的时候还是有所不同的——因为不能和f[u]走相同的u的儿子。

这样一来就可以处理第二种情况了。如果有f[u] == f[v] + 1,那么v就是f[u]所指的路径【之一。这里多个儿子被判定为f[u]的路径也没关系,因为既然有多次,那么g[u]的值就和f[u]是一样的。】,即dp[v] = max(dp[v], g[u] + 1);否则可以直接为dp[v] = max(dp[v], f[u]+1)

那么第一个难点就解决完啦!对于第二个难点,同样有三种情况——取第一棵树的直径,取第二棵树的直径和各取一部分,让直径走新连上的边。这里我们可以用到这么一个不怎么容易想到但是可以靠积累的技巧——因为对于每个点来说,它的树上可以延伸的最长长度就是max(f[i], dp[i]),也就是说对于直径某一端的节点,取出来的max就是这棵树的直径,所以可以先得到两棵树的最大直径是多少。很明显,如果对于某两个点来说,连上一条新的边走的长度还不如树内部的最大的直径,那么对答案的贡献就是这个最大的直径。所以我们对于两棵树得到的每个点的最长延伸长度a和b分别从小到大排序,则我们的内部最长直径ma = max(a[n], b[m])。从后往前考虑每一个b[i],从前往后考虑每一个a[i],让b[i]递减,a[i]递增,看这样组合的答案是否优于ma。而且有一个小结论——b[i]递减,所以能让比b[i]要大的b[i + 1]都必须加上才能优于ma的a[j],就可以毫不犹豫地把a[j]前面的所有a的部分考虑为加上了不会比ma优,直接记为用ma替代的范围。

可能对于这个技巧,文字描述还不如代码里的那两三行好理解……所以上代码了……

代码参考:https://blog.csdn.net/liankewei123456/article/details/81905808

#include<algorithm>
#include<iostream>
#include<cstring>
#include<cstdio>
#include<cmath>
#define maxn 100005
//#define int long long
using namespace std;
typedef long long ll;
int read() {int x = 0, f = 1, ch = getchar();while(!isdigit(ch)) {if(ch == '-') f = -1; ch = getchar();}while(isdigit(ch)) x = (x << 1) + (x << 3) + ch - '0', ch = getchar();return x * f;
}struct edge {int to, nxt; edge() {}edge(int tt, int nn) {to = tt, nxt = nn;}
}e[maxn << 1];int head[maxn], k = 0;
void add(int u, int v) {e[k] = edge(v, head[u]); head[u] = k++;}int n, m;
int f[maxn], g[maxn], dp[maxn];//向下的最长,次长,向上延伸的最长 
ll a[maxn], b[maxn], sum[maxn], ans = 0, ma = 0;
void dfs1(int u, int fa) {register int v, Max = -1e9, max2 = -1e9;//最长和次长for(int i = head[u]; ~i; i = e[i].nxt) {v = e[i].to; if(v == fa) continue;dfs1(v, u);if(f[v] > Max) max2 = Max, Max = f[v];//考虑每一个儿子节点,所以最长次长是不会重复对应子节点的。else max2 = max(max2, f[v]);}if(Max == -1e9) f[u] = 0, g[u] = -1e9;else f[u] = Max + 1, g[u] = max2 + 1;//存结果。+1是因为max和max2存的是相对于子节点的,到点u还要+1.
}void dfs2(int u, int fa) {register int v;for(int i = head[u]; ~i; i = e[i].nxt) {v = e[i].to; if(v == fa) continue;dp[v] = max(dp[v], dp[u] + 1);//直接从上面过来——情况1if(f[v] == f[u] - 1) dp[v] = max(dp[v], g[u] + 1);//从同父的别的子树过来——情况2else dp[v] = max(dp[v], f[u] + 1);dfs2(v, u);}
}signed main() {
//  freopen("in.txt", "r", stdin);memset(head, -1, sizeof head);n = read(), m = read();register int u, v;for(int i = 1; i < n; i++) u = read(), v = read(), add(u, v), add(v, u);dfs1(1, 0), dfs2(1, 0);for(int i = 1; i <= n; i++) a[i] = max(f[i], dp[i]);//因为可以重复利用f、g、dp数组。整合到a和b过后就没用了。memset(e, 0, sizeof e); memset(head, -1, sizeof head); k = 0;memset(dp, 0, sizeof dp); memset(f, 0, sizeof f); memset(g, 0, sizeof g);for(int i = 1; i < m; i++) u = read(), v = read(), add(u, v), add(v, u);dfs1(1, 0), dfs2(1, 0);for(int i = 1; i <= m; i++) b[i] = max(f[i], dp[i]);sort(a + 1, a + 1 + n); sort(b + 1, b + 1 + m);for(int i = 1; i <= n; i++) sum[i] = sum[i - 1] + a[i];//sum存个前缀,方便用ma = max(a[n], b[m]);register int l = 1;for(int i = m; i > 0; i--) {//这里枚举a或者b都可以,只是后面所有的操作都要反转一下的。while(b[i] + a[l] + 1 < ma && l <= n) l++;//l以前的所有a[]都是和这个b[i]匹配对答案没有贡献的ll num = n - l + 1;//对答案有贡献的部分ans += b[i] * num + sum[n] - sum[l - 1] + num + ma * (n - num);//ans += b[i]num次贡献 + a[l] ~ a[n]对答案的贡献 + 走num次新连上的边 + ma * 被ma替代的次数}printf("%lld\n", ans);return 0;
}

完结!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!写了超级久的。

 

迎评:)
——End——

这篇关于2019.6.27 一场彻底让人自闭的考试【including 入门OJ·卡片游戏,泡泡浴,树的合并的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



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

相关文章

Spring Security 从入门到进阶系列教程

Spring Security 入门系列 《保护 Web 应用的安全》 《Spring-Security-入门(一):登录与退出》 《Spring-Security-入门(二):基于数据库验证》 《Spring-Security-入门(三):密码加密》 《Spring-Security-入门(四):自定义-Filter》 《Spring-Security-入门(五):在 Sprin

hdu2241(二分+合并数组)

题意:判断是否存在a+b+c = x,a,b,c分别属于集合A,B,C 如果用暴力会超时,所以这里用到了数组合并,将b,c数组合并成d,d数组存的是b,c数组元素的和,然后对d数组进行二分就可以了 代码如下(附注释): #include<iostream>#include<algorithm>#include<cstring>#include<stack>#include<que

2024年流动式起重机司机证模拟考试题库及流动式起重机司机理论考试试题

题库来源:安全生产模拟考试一点通公众号小程序 2024年流动式起重机司机证模拟考试题库及流动式起重机司机理论考试试题是由安全生产模拟考试一点通提供,流动式起重机司机证模拟考试题库是根据流动式起重机司机最新版教材,流动式起重机司机大纲整理而成(含2024年流动式起重机司机证模拟考试题库及流动式起重机司机理论考试试题参考答案和部分工种参考解析),掌握本资料和学校方法,考试容易。流动式起重机司机考试技

hdu 2093 考试排名(sscanf)

模拟题。 直接从教程里拉解析。 因为表格里的数据格式不统一。有时候有"()",有时候又没有。而它也不会给我们提示。 这种情况下,就只能它它们统一看作字符串来处理了。现在就请出我们的主角sscanf()! sscanf 语法: #include int sscanf( const char *buffer, const char *format, ... ); 函数sscanf()和

day-51 合并零之间的节点

思路 直接遍历链表即可,遇到val=0跳过,val非零则加在一起,最后返回即可 解题过程 返回链表可以有头结点,方便插入,返回head.next Code /*** Definition for singly-linked list.* public class ListNode {* int val;* ListNode next;* ListNode() {}*

数论入门整理(updating)

一、gcd lcm 基础中的基础,一般用来处理计算第一步什么的,分数化简之类。 LL gcd(LL a, LL b) { return b ? gcd(b, a % b) : a; } <pre name="code" class="cpp">LL lcm(LL a, LL b){LL c = gcd(a, b);return a / c * b;} 例题:

软考系统规划与管理师考试证书含金量高吗?

2024年软考系统规划与管理师考试报名时间节点: 报名时间:2024年上半年软考将于3月中旬陆续开始报名 考试时间:上半年5月25日到28日,下半年11月9日到12日 分数线:所有科目成绩均须达到45分以上(包括45分)方可通过考试 成绩查询:可在“中国计算机技术职业资格网”上查询软考成绩 出成绩时间:预计在11月左右 证书领取时间:一般在考试成绩公布后3~4个月,各地领取时间有所不同

Java 创建图形用户界面(GUI)入门指南(Swing库 JFrame 类)概述

概述 基本概念 Java Swing 的架构 Java Swing 是一个为 Java 设计的 GUI 工具包,是 JAVA 基础类的一部分,基于 Java AWT 构建,提供了一系列轻量级、可定制的图形用户界面(GUI)组件。 与 AWT 相比,Swing 提供了许多比 AWT 更好的屏幕显示元素,更加灵活和可定制,具有更好的跨平台性能。 组件和容器 Java Swing 提供了许多

【IPV6从入门到起飞】5-1 IPV6+Home Assistant(搭建基本环境)

【IPV6从入门到起飞】5-1 IPV6+Home Assistant #搭建基本环境 1 背景2 docker下载 hass3 创建容器4 浏览器访问 hass5 手机APP远程访问hass6 更多玩法 1 背景 既然电脑可以IPV6入站,手机流量可以访问IPV6网络的服务,为什么不在电脑搭建Home Assistant(hass),来控制你的设备呢?@智能家居 @万物互联

系统架构师考试学习笔记第三篇——架构设计高级知识(20)通信系统架构设计理论与实践

本章知识考点:         第20课时主要学习通信系统架构设计的理论和工作中的实践。根据新版考试大纲,本课时知识点会涉及案例分析题(25分),而在历年考试中,案例题对该部分内容的考查并不多,虽在综合知识选择题目中经常考查,但分值也不高。本课时内容侧重于对知识点的记忆和理解,按照以往的出题规律,通信系统架构设计基础知识点多来源于教材内的基础网络设备、网络架构和教材外最新时事热点技术。本课时知识