SDNU_Winter_Practice_2_20230104「思维」「构造」「二分」「递推数列」「判断存在同构树」

文章详细分析了多个Codeforces竞赛中的编程问题,涉及游戏策略如确保胜利的条件、数据结构的应用如路径查找,以及递推数列和二分查找等算法在解决问题中的应用。每个问题都提供了思路解析和C++代码实现。
摘要由CSDN通过智能技术生成

6/13 发现其实我也不是很擅长思维,这不是我笨拙的大脑能想出来的东西

C.Problem - 1496D - Codeforces

思路: 

Q若要赢需满足以下几个条件:

  • Q必须选山顶,否则D可以直接堵死。
  • 当Q/D相向而行时,必须保证Q -> D之间的点数必为奇数(但是在选x时无法保证该点于是必须强制D无法如此选择),否则Q将会在自己轮次中无路可走。
  • Q所在山顶通向两边的路必须最大且唯二,否则D会走那条/其他最大路且二人无法相遇,此时D必胜。
  • Q所在山顶通向两遍的路必须相等:
    • 证明:若不等:1.长路为偶数,Q走短路输,Q走长路与D相向也输。2.长路为奇数,Q走短路输,Q走长路时D将选择长路山脚倒数第二点,迫使该路为偶数,Q仍输。由于长路>短路,故长路-1>=短路,等号成立时Q先手Q也先面临无路可走局面,可保证D的永远可选。

总结:Q赢的唯一可能是处于唯二最长路间的山峰,且该最长路为奇数。

#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], l[N], r[N];

void work() {
	int n;
	cin >> n;
	for (int i = 1; i <= n; ++i) {
		cin >> arr[i];
	}
//刷新从左/从右的上升序列长度,类似oj上前两年的结训赛题_远足
	for (int i = 1; i <= n; i++) {
		if (arr[i] > arr[i - 1])
			l[i] = l[i - 1] + 1;
		else
			l[i] = 1;
	}
	for (int i = n; i >= 1; i--) {
		if (arr[i] > arr[i + 1])
			r[i] = r[i + 1] + 1;
		else
			r[i] = 1;
	}
	int maxx = -1, cnt = 0;
	bool flag = 0;
	for (int i = 1; i <= n; ++i) {
		if (maxx < max(l[i], r[i])) {
			maxx = max(l[i], r[i]);
			flag = 0, cnt = 0;
			if (l[i] == r[i] && l[i] % 2 != 0) {
				flag = 1;
				cnt++;
			}
		} else if (maxx == max(l[i], r[i])) {
			cnt++;
		}
	}
	if (flag && cnt == 1) {
		cout << "1\n";
		return;
	}
	cout << "0\n";
}

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

F.Problem - 1496E - Codeforces

思路:

构造。

  • 题面中一个非常重要的条件是:any two empty cells have no common points (neither edges nor corners)  ,无共边和共角,即在初始图中任一九宫格内最多只有一个X【*】

我们可以将1,4,7…列直接变为X,这样能保证对于每中间两列都可以使任一X可联通。接下来处理各列的联通,我开了一个bool数组,将遍历每中间两列时遇到的第一个X作为桥梁,记录下该两列已存在桥梁。接下来特判m%3是否为0,即最右侧两列是否都不是全X列,若是则使最后列每一个X都与主体联通。最后判断1~m/3+1中是否存在vis=0的情况,即是否存在两列全为 . ,若存在则将该两列的第一行作为桥梁。

注意:遇到需要取模的东西,数组还是从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 pb push_back
#define mem(a,b) memset(a,b,sizeof a)
#define inf 0x3f3f3f3f3f3f3f3f
const int N = 3e5 + 50;
char arr[505][505];

void work() {
	int n, m;
	cin >> n >> m;
	bool vis[m / 3 + 5];
	mem(vis, 0);
	for (int i = 1; i <= n; ++i) {
		for (int j = 1; j <= m; ++j) {
			cin >> arr[i][j];
		}
	}
	for (int i = 1; i <= n; ++i) {
		for (int j = 1; j <= m; ++j) {
			if (j % 3 == 1)
				arr[i][j] = 'X';
			else if (!vis[(j + 2) / 3] && (j % 3 == 0) && arr[i][j] == 'X') {
				arr[i][j - 1] = 'X';
				vis[(j + 2) / 3] = 1;
			} else if (!vis[(j + 2) / 3] && (j % 3 == 2) && arr[i][j] == 'X') {
				arr[i][j + 1] = 'X';
				vis[(j + 2) / 3] = 1;
			}
		}
	}
	if (m % 3 == 0) {
		for (int i = 1; i <= n; ++i) {
			if (arr[i][m] == 'X') {
				arr[i][m - 1] = 'X';
				//cout << i << m << " ";
			}
		}
	}
	for (int i = 1; i < m / 3 + 1; ++i) {
		if (!vis[i]) {
			arr[1][i * 3 - 1] = 'X';
			arr[1][i * 3] = 'X';
		}
	}
	for (int i = 1; i <= n; ++i) {
		for (int j = 1; j <= m; ++j) {
			cout << arr[i][j] ;
			if (j == m)
				cout << "\n";
		}
	}
}

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

 G.B - Light It Up (atcoder.jp)

思路:

暴力即可。

让每一个不发光的点找到离自己最近的发光点,记录距离,找出最大距离。 

#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;
struct node {
	int x, y;
	double dis = 1.0 * inf;
} arr[N];

double get_dis(node a, node b) {
	double ans = (a.x - b.x) * (a.x - b.x) + (a.y - b.y) * (a.y - b.y);
	ans = sqrt(ans);
	return ans;
}

void work() {
	int n, k;
	cin >> n >> k;
	vector<int> b(k + 1);
	bool vis[N];
	for (int i = 1; i <= k; ++i) {
		cin >> b[i];
		vis[b[i]] = 1;
	}
	for (int i = 1; i <= n; ++i) {
		cin >> arr[i].x >> arr[i].y;
	}
	double ans = 0;
	for (int i = 1; i <= n; ++i) {
		if (vis[i] == 1)
			continue;
		for (int j = 1; j <= k; ++j) {
			int t = b[j];
			arr[i].dis = min(arr[i].dis, get_dis(arr[i], arr[t]));
		}
		ans = max(ans, arr[i].dis);
	}
	printf("%.8lf\n", ans);
}

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

 H.Problem - 1496C - Codeforces

思路:

任何两条有交叉的连线一定比没有交叉的连线和长,最优时不让任意两线交叉,排序两个数组逐个计算距离即可。

#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;

double dis(int a, int b) {
	double ans = a * a + b * b;
	ans = sqrt(ans);
	return ans;
}

void work() {
	int n;
	cin >> n;
	vector<int>x, y;
	for (int i = 1; i <= 2 * n; ++i) {
		int a, b;
		cin >> a >> b;
		if (a == 0) {
			if (b < 0)
				b = -b;
			y.pb(b);
		} else {
			if (a < 0)
				a = -a;
			x.pb(a);
		}
	}
	sort(x.begin(), x.end());
	sort(y.begin(), y.end());
	double ans = 0.0;
	for (int i = 0; i < n; ++i) {
		ans += dis(y[i], x[i]);

	}
	printf("%.12lf\n", ans);
}

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

 K.E - Lucky Numbers (atcoder.jp)

思路:

思维(递推数列。 

a[i]=a[1]*(-1)^(i+1)+b[i]

b[i]=s[i-1]-b[i-1]

a[1]=(a[i]-b[i])*(-1)^(i+1)

令a[i]=x[j]时,可以求出a[1]=(x[j]-b[i])*(-1)^(i+1),用map记录下每个a[1]出现的次数,代表当前数作为a[1]时,a[i]=x[j]的不同个数。

#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 s[N], x[N], b[N];

void work() {
	int n, m;
	cin >> n >> m;
	for (int i = 1; i < n; ++i) {
		cin >> s[i];
	}
	for (int i = 1; i <= m; ++i) {
		cin >> x[i];
	}
	for (int i = 1; i <= n; ++i) {
		b[i] = s[i - 1] - b[i - 1];
	}
	map<int, int>mp;
	for (int i = 1; i <= n; ++i) {
		for (int j = 1; j <= m; ++j) {
			if (i % 2 == 1) {
				mp[x[j] - b[i]]++;
			} else {
				mp[b[i] - x[j]]++;
			}
		}
	}
	map<int, int>::iterator i = mp.begin();
	int ans = 0;
	for (i; i != mp.end(); ++i) {
		ans = max(ans, (*i).second);
	}
	cout << ans << '\n';
}

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

L.F - Pre-order and In-order (atcoder.jp)

思路:

巧妙的二分。

我个人认为这个做法最大的妙点在于:dfs(或者说二分)搜索的顺序和先序排列的顺序相同

在中序排列中,对于每一个根节点,他的左子孙都在自己左边,右子孙都在自己右边,于是我们在找当前节点时,可以用他在中序中的位置作为左右区间的分界。

在开始二分的时候,我们就可以把1~n的数列看作以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 pb push_back
#define mem(a,b) memset(a,b,sizeof a)
#define inf 0x3f3f3f3f3f3f3f3f
const int N = 3e5 + 50;
int a[N], id[N], b[N], L[N], R[N];
int tot, flag = 1;

void dfs(int cur, int l, int r) { //根节点(当前节点),根的左右叶节点(不一定是儿子)
	int mid = id[cur]; //当前节点在中序排列的位置
	if (l > mid || r < mid) {
		cout << "-1\n";
		flag = 0;
		exit(0);//return跳出函数,exit退出程序
	}
//当mid==l||mid==r时即表示mid没有这个儿子
	if (l < mid) {
		L[cur] = a[++tot]; //由于dfs深搜特性,搜索顺序与先序排列的顺序是相同的
		dfs(a[tot], l, mid - 1); //当前节点变为cur的左儿子
                                 //左儿子的最左子孙是l,最右子孙是mid-1
	}
	if (r > mid) {
		R[cur] = a[++tot];
		dfs(a[tot], mid + 1, r);//这里注意一下
    //不同于普通二分,由于在树种根节点在左右儿子中间,即l<mid<r,所以在找儿子时要空出根的位置
	}
}

void work() {
	int n;
	cin >> n;
	for (int i = 1; i <= n; ++i) {
		cin >> a[i];
	}
	for (int i = 1; i <= n; ++i) {
		cin >> b[i];
		id[b[i]] = i;//记录b[i]在中序排列中的位置
	}
	if (a[1] != 1) {
		cout << "-1\n";
		return;
	}
	dfs(a[++tot], 1, n);
	if (!flag)
		return;
	for (int i = 1; i <= n; ++i) {
		cout << L[i] << " " << R[i] << "\n";
	}
}

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

 M.Problem - 902C - Codeforces

思路:

 只要出现连续两层节点数大于1就会存在同构。

需要注意的几点:

  • 输出的层数是n+1
  • 可以输出上层倒数第二个节点(同构)的条件是本层和上层的节点数都不为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 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;
	bool flag = 0;
	for (int i = 0; i <= n; ++i) {
		cin >> arr[i];
		if (arr[i] > 1 && arr[i - 1] > 1)
			flag = 1;
	}
	if (!flag) {
		cout << "perfect\n";
		return;
	}

	cout << "ambiguous\n";
	int root = 0;
	for (int i = 0; i <= n; ++i) {
		for (int j = 1; j <= arr[i]; ++j) {
			cout << root << " ";
		}
		root += arr[i];
	}
	cout << '\n';
	root = 0;
	for (int i = 0; i <= n; ++i) {
		if (arr[i] != 1 && arr[i - 1] != 1) {
			for (int j = 1; j <= arr[i] - 1; ++j) {
				cout << root << " ";
			}
			cout << root - 1 << " ";
			root += arr[i];
		} else {
			for (int j = 1; j <= arr[i]; ++j) {
				cout << root << " ";
			}
			root += arr[i];
		}
	}
	cout << '\n';
}

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

下班!

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值