ICode9

精准搜索请尝试: 精确搜索
首页 > 其他分享> 文章详细

gdfzoj 比赛题解

2022-08-26 02:01:43  阅读:153  来源: 互联网

标签:return 比赛 int 题解 gdfzoj len ++ dfrac include


前言

本次比赛:初一训练5.21 / 编号531

题目难度中等偏上,有几题比较简单,有两三题较难。

T1

题目gdfzoj1441

思路

算是一道暴力题。

由于 \(h_{i, j}\) 范围很小,考虑二分答案。

二分答案的范围应该是 \([0, 110]\)。

对于 chk() 函数,可以暴力枚举所有差为 \(\texttt{mid}\) 的数对,并使用 bfs 强行搜索检验。

bfs 比较容易实现,重点在于 chk() 的枚举。

我们明显可以枚举所有数对的较小值,较大值自然就是 \(\text{较小值} + \texttt{mid}\)。

但是直接这样枚举太过鲁莽,可以剪枝。

注意到 \(h_{1, 1}\) 与 \(h_{n, n}\) 是必定会经过的。

数对较大值 的 最小值 应该是 \(\max(h_{1, 1}, h_{n, n})\)。

因此可得,数对较小值 的 最小值 应该是 \(\max(h_{1, 1}, h_{n, n}) - \texttt{mid}\)。

再来看 数对较小值 的 最大值 ,它是 \(\min(h_{1, 1}, h_{n, n})\)。这个稍微有点难想。

思路总结

  1. 读入数据。
  2. 写一份 bfs,参数有 \(\texttt{最高点}\) 与 \(\texttt{最低点}\),用于检验这种情况是否可行。
  3. 写二分答案必须用到的 chk() 函数,实现如上所述,思考上略有困难。
  4. 写二分,然后输出。

代码

#include <iostream>
#include <cstdio>
#include <queue>
#include <cstring>
#define N 105
using namespace std;
int n, a[N][N];
int Max, Min;
void Input()
{
	scanf("%d", &n);
	if (n == 1) //特判
	{
		printf("0");
		exit(0);
	}
	for (int i = 1; i <= n; i++)
		for (int j = 1; j <= n; j++)
			scanf("%d", &a[i][j]);
	Max = max(a[1][1], a[n][n]);
	Min = min(a[1][1], a[n][n]);
}
struct Node
{
	int x, y;
};
int dict[4][2] = {{0, 1}, {0, -1}, {1, 0}, {-1, 0}};
bool vis[N][N];
bool bfs(int minn, int maxn) //非常模板的 bfs 只是要判一下数的范围。
{
	memset(vis, false, sizeof(vis)); //切记清空数组
	queue <Node> Q;
	Q.push( (Node){1,1} );
	vis[1][1] = true;
	while (!Q.empty())
	{
		int x = Q.front().x, y = Q.front().y;
		Q.pop();
		if (x == n && y == n) return true;
		for (int i = 0; i < 4; i++)
		{
			int dx = x + dict[i][0], dy = y + dict[i][1];
			if (!(1 <= dx && dx <= n && 1 <= dy && dy <= n)) continue;
			if (vis[dx][dy]) continue;
			if (minn <= a[dx][dy] && a[dx][dy] <= maxn) //唯一和板子不一样的地方。
			{
				vis[dx][dy] = true;
				Q.push( (Node){dx, dy} );
			}
		}
	}
	return false;
}
bool chk(int ans) //较难理解。
{
	int l = Max - ans, r = Min;
	for (int i = l; i <= r; i++)
		if (bfs(i, i + ans))
			return true;
	return false;
}
int FIND()
{
	int pos = -1, L = 0, R = 110;
	while (L < R)
	{
		int mid = (L + R) >> 1;
		if (chk(mid)) R = mid, pos = R; //答案求最大值,pos 应该在 R 这一边更新。
		else L = mid + 1;
	}
	return pos;
}
int main()
{
	Input();
	printf("%d", FIND());
	return 0;
}

T2

题目gdfzoj1942

思路

个人觉得这题最难。

重要的前置知识:分数取模。

由于篇幅原因,不详细证明,使用到了 费马小定理 与 逆元。

这里只给出结论:\(\dfrac{x}{y}\equiv x \cdot y^{p-2} \pmod{p}\)

通过样例得知,先手赢一场比赛的概率为 \(\dfrac{2}{3}\)。

进一步地,我们设先手赢第 \(n\) 场比赛的概率为 \(p_n\),则先手输掉第 \(n\) 场比赛的概率为 \((1 - p_n)\)。

容易写出状态转移方程(假设『我』代表先手,『他』代表后手):

\[\text{我赢的概率} = \text{上一场我赢的概率} \times \text{这一场他输的概率} +\text{上一场我输的概率} \times \text{这一场我赢的概率} \]

转换成数学语言:

\[\begin{aligned}p_i & = p_{i-1} \times \dfrac{1}{3} + (1 - p_{i-1}) \times \dfrac{2}{3}\\ & = p_{i-1} \times \dfrac{1}{3} + \dfrac{2}{3} - p_{i-1} \times \dfrac{2}{3}\\ & = \dfrac{2}{3} - \dfrac{1}{3} \times p_{i-1}\end{aligned} \]

整合一下,标准的状态转移方程就是:

\[p_i = \begin{cases}\dfrac{2}{3} & i = 1\\\\\dfrac{2}{3} - \dfrac{1}{3} \times p_{i-1} & i \ge 2\end{cases} \]

但是这样的效率是 \(O(n)\) 的,还需要优化。

最快的办法是找规律,如下。

场数 先手获胜的概率 后手获胜的概率
\(1\) \(\dfrac{2}{3}\) \(\dfrac{1}{3}\)
\(2\) \(\dfrac{4}{9}\) \(\dfrac{5}{9}\)
\(3\) \(\dfrac{14}{27}\) \(\dfrac{13}{27}\)
\(4\) \(\dfrac{40}{81}\) \(\dfrac{41}{81}\)

规律如下,其实还是比较容易找的。

若当前是第 \(n\) 场比赛,则:

  • \(分母 = 3^{场数}\);
  • 先手获胜与后手获胜的概率的分子总是相差 \(1\)。

进一步地,可以得到下面这条式子:

\[p_n = \begin{cases} \dfrac{(3^n + 1)}{2 \times 3^n} & n\equiv1\pmod{2}\\ \\ \dfrac{(3^n - 1)}{2 \times 3^n} & n\equiv0\pmod{2}\end{cases} \]

然后对着它打代码就完事了,照着 $$\dfrac{x}{y}\equiv x \cdot y^{p-2} \pmod{p}$$ 这条结论模即可。

需要注意,此处需要使用快速幂来达到 \(O(\log n)\) 的速度求 \(3^n\)。

代码

#include <iostream>
#include <cstdio>
#define LL long long
using namespace std;
LL ksm(LL x, int y, int p) //x^y mod p
{
	if (y == 1) return x % p;
	LL t = ksm(x, y >> 1, p);
	t = (t * t) % p;
	if (y & 1) t = (t * x) % p;
	return t;
}
int main()
{
	int n, p = 998244353;
	scanf("%d", &n);
	if (n & 1)
	{
		LL POW = ksm(3, n, p);
		LL x = (POW + 1) % p, y = (2 * POW) % p;
		y = ksm(y, p-2, p);
		printf("%lld", (x * y) % p);
	}
	else
	{
		LL POW = ksm(3, n, p);
		LL x = (POW - 1) % p, y = (2 * POW) % p;
		y = ksm(y, p-2, p);
		printf("%lld", (x * y) % p);
	}
	return 0;
}

T3

题目gdfzoj2232

思路

T2 有点难啊,T3 轻松多了。

我们都知道,合法的括号序列有一个特征:

  • 假设 ( 代表 \(1\),) 代表 \(-1\);
  • 统计转换为数字的序列的前缀和。
  • 如果前缀和中出现了负数,表明括号序列不成立。

对于本题,我们可以先确认更改的字符是 ( 还是 )

假设需要更改左括号(如果是右括号,只需要倒着扫)。

统计前缀和,如果某个前缀和出现了负数,说明可以更改从开头至当前位置所有左括号

代码实现上,如果需要更改右括号,可以将左括号改成右括号,并将字符串翻转。

这样,代码就具有良好的扩展性了。

代码

#include <iostream>
#include <cstdio>
#include <string>
#include <algorithm>
using namespace std;
string s;
int len, lcnt, rcnt;
void Input()
{
	cin >> s;
	len = s.length(), lcnt = 0, rcnt = 0;
	for (int i = 0; i < len; i++)
	{
		if (s[i] == '(') lcnt++;
		else rcnt++;
	}	
}
void Reverse()
{
	//每个字符翻转+整串翻转 
	for (int i = 0; i < len; i++)
	{
		if (s[i] == '(') s[i] = ')';
		else s[i] = '(';
	}
	reverse(s.begin(), s.end());
}
void solve()
{
	int sum = 0, cnt = 0;
	for (int i = 0; i < len; i++)
	{
		if (s[i] == '(') sum++;
		else sum--, cnt++;
		if (sum < 0)
		{
			printf("%d", cnt); 
			return;
		}
	}
	printf("0");
}
int main()
{
	Input();
	if (lcnt > rcnt) Reverse();
	solve();
	return 0;
}

T4

题目gdfzoj2233

思路

没有思路,这是一道暴力题。

由于规模较小,采用 dfs 更优。

一遍深搜,一遍记录当前走过的字符串。

写一个 chk() 函数判断字符串是否为完美字符串即可。

代码

#include <iostream>
#include <cstdio>
using namespace std;
char a[7][7];
bool vis[7][7];
int maxn, dict[4][2] = {{0, 1}, {0, -1}, {1, 0}, {-1, 0}};
bool chk(string s)
{
	int len = s.length();
	if (len & 1) return false;
	int tlen = len >> 1;
	for (int i = 0; i < tlen; i++)
		if (s[i] == ')')
			return false;
	for (int i = tlen; i < len; i++)
		if (s[i] == '(')
			return false;
	return true;
}
int n;
void dfs(int x, int y, string s)
{
	if (chk(s))
	{
		int len = s.length();
		maxn = max(maxn, len);
		return;
	}
	for (int i = 0; i < 4; i++)
	{
		int dx = x + dict[i][0], dy = y + dict[i][1];
		if (!(1 <= dx && dx <= n && 1 <= dy && dy <= n)) continue;
		if (vis[dx][dy]) continue;
		vis[dx][dy] = true;
		dfs(dx, dy, s + a[dx][dy]);
		vis[dx][dy] = false;
	}
}
int main()
{
	scanf("%d", &n);
	for (int i = 1; i <= n; i++)
		for (int j = 1; j <= n; j++)
			cin >> a[i][j];
	vis[1][1] = true;
	string tql = ""; tql += a[1][1];
	dfs(1, 1, tql);
	printf("%d", maxn);
	return 0;
}

T5

题目gdfzoj2234

本题名声过大,了解过括号序列的人肯定做过这题。

故,不细述。

T6

题目gdfzoj2235

思路

此题明显是最短路。

我们需要求任意两点的最短路的最大值,即全源最短路。

本题共有 \(n^2\) 个点,如果跑 Floyd 时间复杂度是 \(O(n^6)\),非常极限,不是本题正解。

但是,我又不会写 Johnson 全源最短路,怎么办呢?

每个点朝四周建边,容易发现,边的数量不超过 \(4 \times n^2\)。

想到这里,明显可以跑 \(n^2\) 次 dijkstra 实现,因为当边数较小时,跑多次 dijkstra 会比 Floyd 快。

此处 dijkstra 是使用优先队列实现。

那么就很容易写出代码了。

思路总结

  1. 读入数据。
  2. 每个点朝四周建边。
  3. 写一个 dijkstra 的模版。
  4. 统计最大值,输出。

代码

#include <iostream>
#include <cstdio>
#include <queue>
#define N 905
#define M 7205 //905 * 4 * 2 = 7200
using namespace std;
int n, nn, A, B;
int dict[4][2] = {{0, 1}, {0, -1}, {1, 0}, {-1, 0}};
bool a[35][35];
struct Node
{
	int now, nxt, w;
}e[M];
int head[N], cur;
void add(int x, int y, int k)
{
	e[++cur].now = y;
	e[cur].nxt = head[x];
	e[cur].w = k;
	head[x] = cur;
}
struct Dis
{
	int pos, len;
	bool operator <(const Dis &t) const
	{
		return len > t.len; 
	}
}dis[N];
bool vis[N];
int dijkstra(int first) 
{
	priority_queue <Dis> Q;
	for (int i = 1; i <= nn; i++) dis[i].pos = i, dis[i].len = 2147483647, vis[i] = false;
	dis[first].len = 0;
	Q.push(dis[first]);
	while (!Q.empty())
	{
		int topi = Q.top().pos;
		Q.pop();
		if (vis[topi]) continue;
		vis[topi] = true;
		for (int i = head[topi]; i; i = e[i].nxt)
			if (dis[topi].len + e[i].w < dis[e[i].now].len)
			{
				dis[e[i].now].len = dis[topi].len + e[i].w;
				Q.push(dis[e[i].now]);
			}
	}
    //此处有是惟一与普通模版不同的地方,需要找出最大值。
	int maxn = 0;
	for (int i = 1; i <= nn; i++) maxn = max(maxn, dis[i].len);
	return maxn;
}
void Input()
{
	scanf("%d%d%d", &n, &A, &B);
	nn = n * n;
	for (int i = 1; i <= n; i++)
		for (int j = 1; j <= n; j++)
		{
			char x;
			cin >> x;
			if (x == '(') a[i][j] = true;
		}
}
void get_edge()
{
	for (int i = 1; i <= n; i++)
		for (int j = 1; j <= n; j++)
			for (int k = 0; k < 4; k++)
			{
				int x = i + dict[k][0], y = j + dict[k][1];
				if (!(1 <= x && x <= n && 1 <= y && y <= n)) continue;
				int t1 = n * (i-1) + j, t2 = n * (x-1) + y;
				if (a[i][j] == a[x][y]) add(t1, t2, A);
				else add(t1, t2, B);
			}
}
void solve()
{
	int maxn = 0;
	for (int i = 1; i <= nn; i++) maxn = max(maxn, dijkstra(i));
	printf("%d", maxn);
}
int main()
{
	Input();
	get_edge();
	solve();
	return 0;
}

T7

题目gdfzoj2958

思路

dp 题,较简单。

注意到数据范围 \(n \le 1000\),容易想到时间复杂度大约是 \(O(n^2)\) 左右的级别。

设 \(dp_i\) 表示:第 \(i\) 个蒟蒻留下的最多人数。

容易想到转移方程(表达式可能不太规范):

\[dp_i = \huge[\small\max\limits_{j=1}^{i-1} dp_j \space(\text{满足} |a_i-a_j| \ne 1) \huge] \small + 1 \]

貌似代码比这东西容易理解多了。

代码

#include <iostream>
#include <cstdio>
#include <cmath>
#define N 1005
using namespace std;
int a[N], dp[N];
int main()
{
	int n;
	scanf("%d", &n);
	for (int i = 1; i <= n; i++) scanf("%d", &a[i]), dp[i] = 1; //注意初始化。
	for (int i = 1; i <= n; i++)
		for (int j = 1; j < i; j++)
			if (abs(a[i] - a[j]) != 1)
				dp[i] = max(dp[i], dp[j] + 1);
	printf("%d", dp[n]);
	return 0;
}

结语

感觉这次比赛比较综合。

搜索、二分、dp、最短路、数学题、括号序列,这几种常考题型及算法都出了。

还要继续加油!

首发:2022-05-27 13:23:18

标签:return,比赛,int,题解,gdfzoj,len,++,dfrac,include
来源: https://www.cnblogs.com/liangbowen/p/16622879.html

本站声明: 1. iCode9 技术分享网(下文简称本站)提供的所有内容,仅供技术学习、探讨和分享;
2. 关于本站的所有留言、评论、转载及引用,纯属内容发起人的个人观点,与本站观点和立场无关;
3. 关于本站的所有言论和文字,纯属内容发起人的个人观点,与本站观点和立场无关;
4. 本站文章均是网友提供,不完全保证技术分享内容的完整性、准确性、时效性、风险性和版权归属;如您发现该文章侵犯了您的权益,可联系我们第一时间进行删除;
5. 本站为非盈利性的个人网站,所有内容不会用来进行牟利,也不会利用任何形式的广告来间接获益,纯粹是为了广大技术爱好者提供技术内容和技术思想的分享性交流网站。

专注分享技术,共同学习,共同进步。侵权联系[81616952@qq.com]

Copyright (C)ICode9.com, All Rights Reserved.

ICode9版权所有