SDNU_Winter_Practise_1_20230102「博弈」「dfs/bfs树」

7/14 栓q,理解题意真的很难。

E.SPOJ.com - Problem CLFLARR

思路:

1.线段树维护

2.小技巧,输出的时候倒叙遍历卡出当前数所在的最后一个区间,若不属于任何区间则输出0。

#include <bits/stdc++.h>
using namespace std;
#define io ios::sync_with_stdio(false),cin.tie(0),cout.tie(0)
typedef long long ll;
#define int ll
#define inf 0x3f3f3f3f3f3f3f3f
const int N = 3e5 + 50;
struct node {
	int r, l, c;
} arr[N];

void work() {
	int n, q;
	cin >> n >> q;
	for (int i = 1; i <= q; ++i) {
		cin >> arr[i].l >> arr[i].r >> arr[i].c;
	}
	for (int i = 1; i <= n; ++i) {
		bool flag = 0;
		for (int j = q; j > 0; --j) {
			if (i <= arr[j].r && i >= arr[j].l) {
				flag = 1;
				cout << arr[j].c << "\n";
				break;
			}
		}
		if (!flag)
			cout << "0" << "\n";
	}
}

signed main() {
	io;
	work();
	return 0;
}

G.C - Distinct Numbers (atcoder.jp)

题目注意点:

1. 每次操作的是最大的数。2. arr 数列不允许重复元素(pairwise distinct,两两不同)

思路:

前置知识:

公平组合游戏(ICG):
(1)游戏规则对两个玩家公平
(2)游戏状态有限,能走的步数也有限
(3)轮流走,当一个玩家不能走时判负
(4)游戏的局势不能区分玩家的身份,例如黑白棋就不行
小结论:
给定初始局势,指定先手玩家,如果双方都采取最优策略,那么获胜者已经确定了,即ICG问题存在必胜策略

引理1:存在一个情况使得0~n-1中有k个空位,其余k个数从n开始连续。在该情况下,k为奇数时我必胜,否则我必败。

  • 证明:递推,每一个状态都由前一个状态推来,我可以进行一步操作使对手面对k-1局面,此时我的状态为 我面临k局面 的反状态。由此推到1,易知我必胜,于是后面状态都已知。

引理2:存在一个情况使得0~n-1中有2个空位,末尾两个数连续。在该情况下,arr[n]-(n-1)为奇数时我必胜,否则我必败。

  • 证明:1. 注意到当末尾两数为非连续的时候,当前的人可以控制将arr[ n ]放在arr[ n-1 ]前或后,当前人只需贴住对手即可制造必赢局面,所以当两人绝对聪明的时候必定是每次操作都让arr[ n ]=arr[ n-1 ] -1。
  • 证明:2. 在1成立的条件下,注意到操作次数必为(arr[n]-n)/2+(arr[n-1]-n-1)/2+2或(arr[n]-n-1)/2+(arr[n-1]-n)/2+2,此时arr[n]=arr[n-1]+1,即得arr[n]-(n-1),于是操作数为奇数时我必胜。

加强引理2:存在一个情况使得0~n-1中有k个空位,末尾两个数连续。在该情况下,arr[n]-(n-1)为奇数时我必胜,否则我必败。

  • 证明:在引理2成立的条件下,我们可以换一个角度思考arr[n]-(n-1)得数的算法,该得数说明了一轮游戏将遍历arr[ n ] ~ 0的所有数,且【题目注意点】保证了不会存在一个点被多次经过(*),于是拓展为当前情况,可以发现距离只与arr[n]和(n-1)有关,与其他点所在的位置无关。
    • 证明(*):反证法思考,如果一个点被多次遍历,那说明在当前最大值移动之前,该点已被移走,此时与【题目注意点1】矛盾。
  • 一些多余的思考(手推理解):当连续的末尾两数遇到倒数第三个数时,最大值可以移动到之前的任意地方,维持两数相连的对峙局面或保持三数相连(当前状态)。

证明:

由以上引理可知,必胜的方法为当我面临末尾两数连续时,arr[n]-(n-1)为奇数。

对于每局游戏都有以下几种可能:

1.末尾两数不连续,即arr[ n ] > arr[ n-1 ] + 1,此时我们需要保证创造自己的必胜局面,即判断arr[ n-1 ]的位置制造连续末尾,由于胜负由距离的奇偶性决定,而我们此时可以控制距离的奇偶性(即将arr[ n ]放于arr[ n-1 ]+1位或arr[ n-1 ]左边*),由此我们必胜。

        *:若arr[ n-2 ]与arr[ n-1 ]连续则可放于左边任意位,否则放于arr[ n-1 ]-1位。

2.末尾两数连续:由【加强引理2】易得胜负情况。

 这个博弈我写的真的很认真!!!我想了很久很久,偌大的CSDN竟无一篇博客能解决我思路上的问题,看了两篇都是先假设再假设(我觉得有点伪证,也或许是我笨思路不通顺)。

其实做题者是不需要想明白必胜策略的,我们只需要知道面对某种情况我是否存在必胜策略即可,这也是其他题解假设可以成立的依据,这个思想还是比较重要的,但我还是习惯于把逻辑捋通顺,不然忘得很快,这题懂了下一题也不会做。

博弈是极好极好的,只是我不会搞!

#include <bits/stdc++.h>
using namespace std;
#define io ios::sync_with_stdio(false),cin.tie(0),cout.tie(0)
typedef long long ll;
#define int ll
#define pb push_back
#define mem(a,b) memset(a,b,sizeof a)
#define inf 0x3f3f3f3f3f3f3f3f
const int N = 3e5 + 50;
int arr[N];

void work() {
	int n;
	cin >> n;
	for (int i = 0; i < n; ++i) {
		cin >> arr[i];
	}
	sort(arr, arr + n);
	if (arr[n - 1] - arr[n - 2] > 1 || (arr[n - 1] - n + 1) % 2 != 0) {
		cout << "Alice\n";
		return;
	}
	cout << "Bob\n";
}

signed main() {
	io;
	work();
	return 0;
}

I.Problem - 1474A - Codeforces 

思路:

贪心,判断局部最优即可达到整体最优。注意首位必为1。

#include <bits/stdc++.h>
using namespace std;
#define io ios::sync_with_stdio(false),cin.tie(0),cout.tie(0)
typedef long long ll;
#define int ll
#define inf 0x3f3f3f3f3f3f3f3f
const int N = 3e5 + 50;

void work() {
	int n;
	cin >> n;
	string b;
	cin >> b;
	int cur = b[0] - '0' + 1;
	cout << "1";
	for (int i = 1; i < n; ++i) {
		if (b[i] == '0') {
			if (cur == 1) {
				cout << "0";
				cur = 0;
			} else {
				cout << "1";
				cur = 1;
			}
		} else {
			if (cur == 2) {
				cout << "0";
				cur = 1;
			} else {
				cout << "1";
				cur = 2;
			}
		}
	}
	cout << '\n';
}

signed main() {
	io;
	int t;
	cin >> t;
	while (t--) {
		work();
	}
	return 0;
}

 J.F - Two Spanning Trees (atcoder.jp)

题意:

给一个无向无环图,要求以1为根节点建两棵树:

T1:对于每一个不属于T1的边,该边的两个端点必存在子孙关系。

T2:对于所有不属于T2的边,该边两个端点都不存在子孙关系。

思路:

T1是dfs树:深搜不是很好理解,但我们可以用反证法的思想想一下,当存在一个不属于dfs树的边,有几种情况:

  • 回边(返祖边),此时存在子孙关系。
  • 边的两个端点分别在树的两支上,此时说明出现了环,不成立。

这里推荐一个讲dfs树的博客(原博在CF,这是翻译),可以看看那个图DFS 树_malanlllll的博客-CSDN博客_dfs树

T2是bfs树:广搜很好理解,对于每一个点在都会遍历到他所有的边并建立子孙关系,故所有边都会建立子孙关系。

#include <bits/stdc++.h>
using namespace std;
#define io ios::sync_with_stdio(false),cin.tie(0),cout.tie(0)
typedef long long ll;
//#define int ll
#define pb push_back
#define inf 0x3f3f3f3f
const int N = 3e5 + 50;
vector<int>aa[N];

void add(int a, int b) {
	aa[a].pb(b);
	aa[b].pb(a);
}

int vis[N];
void dfs(int a) {
	vis[a] = 1;
	for (int i = 0; i < aa[a].size(); ++i) {
		if (!vis[aa[a][i]]) {
			cout << a << " " << aa[a][i] << '\n';
			dfs(aa[a][i]);
		}
	}
}

queue<int>q;
void bfs(int a) {
	memset(vis, 0, sizeof vis);
	q.push(a);
	vis[a] = 1;
	while (!q.empty()) {
		int cur = q.front();
		q.pop();
		for (int i = 0; i < aa[cur].size(); ++i) {
			if (!vis[aa[cur][i]]) {
				q.push(aa[cur][i]);
				cout << cur << " " << aa[cur][i] << '\n';
				vis[aa[cur][i]] = 1;
			}
		}
	}
}

void work() {
	int n, m;
	cin >> n >> m;
	for (int i = 0; i < m; ++i) {
		int a, b;
		cin >> a >> b;
		add(a, b);
	}
	dfs(1);
	bfs(1);
}

int main() {
	io;
	work();
	return 0;
}

有时间再看看dp吧,倦了。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值