2023牛客多校第三场 - A D I J

比赛地址:传送门
赛时过了3题AHJ,最后10分钟D出思路,但没时间了,赛后过
A 签到题
D 需要一点点思维
I LCA 问题,难题。。。但值得一做
J topo排序 + 思维

A World Fragments I

题意
给你一个二进制串 x 和 y,你可以取 x 某个数位上的数字 b,使得 \(x = x \pm b\),问你使得 x 变为 y 的最小操作次数

思路
简单签到题

  • \(x = y\) 时,\(ans = 0\)
  • \(x \ne y\) 时,当 \(x = 0\)\(ans = 0\),反之 \(ans = | y - x |\)

代码

void solve(){
	string a,b;
	cin >> a >> b;
	if(a == b) cout << 0;
	else{
		if(a == "0") cout << -1;
		else{
			ll x = 0, y = 0;
			for(auto c : a){
				x = (x << 1) + c - '0';
			}
			for(auto c : b){
				y = (y << 1) + c - '0';
			}
			cout << abs(x - y);
		}
	}
	return ;
}

D Ama no Jaku

题意
给你一个 \(n \times n\) 的矩阵,每个位置上的数字均为 0 或 1,你可以进行一种操作:选择某一行或者某一列使其 01 翻转。问你最少的操作次数,使得行表示的最小二进制数大于列表示 的最大二进制数,若无法实现,输出 -1。

思路
考虑后会发现,只有整个矩阵全 0 或者全 1 时,才有可能成立,那么就是判断给定的矩阵通过最少的操作次数使其变为全 0 矩阵或者全 1 矩阵。
正面想不容易,可以反面考虑,由全0或者全1矩阵变为给定矩阵所需的最小操作次数。那么我们可以发现,无论怎么行列变换,当仅进行列变换时,所有的行均相等,再进行行变换后,所有的行只存在两种情况,要么与第一行相等,要么与第一行01翻转后相等,所以存在性就利用这个判断即可。
最后是最小的操作次数,首先就是存在时,所有的字符串仅存在两种:s[0]和翻转s[0],那么,只需要统计各自的个数,再判断其列翻转为全1或者全0的次数加起来,取小即可。取小的话是先列翻转取小(判断由全1变来次数少还是全0变来次数少),再行翻转取小(判断行翻转应用于s[0]的次数少还是应用于翻转s[0]的次数少 )。

代码

//>>>Qiansui
#include<bits/stdc++.h>
#define ll long long
#define ull unsigned long long
#define mem(x,y) memset(x, y, sizeof(x))
#define debug(x) cout << #x << " = " << x << '\n'
#define debug2(x,y) cout << #x << " = " << x << " " << #y << " = "<< y << '\n'
//#define int long long

using namespace std;
typedef pair<int, int> pii;
typedef pair<ll, ll> pll;
typedef pair<ull, ull> pull;
typedef pair<double, double> pdd;
/*

*/
const int maxm = 2e5 + 5, inf = 0x3f3f3f3f, mod = 998244353;

void solve(){
	int n;
	cin >> n;
	string s[2],t;
	cin >> s[0];
	s[1] = s[0];
	ll ans = 0, c[2] ={0, 0};
	for(int i = 0; i < n; ++ i){
		if(s[0][i] == '1'){
			s[1][i] = '0';
			++ c[1];
		}
		else{
			s[1][i] = '1';
			++ c[0];
		}
	}
	bool f = true;
	int d[2] = {1, 0};
	for(int i = 0; i < n - 1; ++ i){
		cin >> t;
		if(f){
			if(t == s[0]){
				++ d[0];
			}else if(t == s[1]){
				++ d[1];
			}else{
				f = false;
			}
		}
	}
	if(f) ans = min(c[0], c[1]) + min(d[0], d[1]);
	else ans = -1;
	cout << ans << '\n';
	return ;
}

signed main(){
	ios::sync_with_stdio(false), cin.tie(nullptr), cout.tie(nullptr);
	int _ = 1;
	// cin >> _;
	while(_ --){
		solve();
	}
	return 0;
}


I To the Colors of the Dreams of Electric Sheep

题意
有一个 n 个节点的树,每一个节点所包含的颜色均有二进制数 \(c_i\) 定义。现在给你 q 次询问,每次从 u 出发到 v,你的初始颜色可以自由选择。你可以花费 1 秒的时间移动到相邻的具有和你同色的节点,或者说该自己的颜色为所在节点的某种颜色。问你抵达所需花费的最小时间?无法抵达输出 -1

思路
思路来自懵哥
详见代码,这里仅做一点点解释。
数组 dep 和 Fa 维护 lca 最近公共祖先
col 维护每个节点每种颜色不变色最高能跳到何处,jmp 维护每个节点改变 2 的 k 次方中颜色能抵达的最高位置
dfs 函数预处理,lca 函数求最近公共祖先,calc计算最后的答案

代码

//>>>Qiansui
#include<bits/stdc++.h>
#define ll long long
#define ull unsigned long long
#define mem(x,y) memset(x, y, sizeof(x))
#define debug(x) cout << #x << " = " << x << '\n'
#define debug2(x,y) cout << #x << " = " << x << " " << #y << " = "<< y << '\n'
//#define int long long

using namespace std;
typedef pair<int, int> pii;
typedef pair<ll, ll> pll;
typedef pair<ull, ull> pull;
typedef pair<double, double> pdd;
/*

*/
const int maxm = 5e5 + 5, inf = 0x3f3f3f3f, mod = 998244353;
int n, q;
int jmp[21][maxm], dep[maxm], f[21][maxm], Fa[maxm];
int col[60][maxm];  //每个颜色能跳的最高的节点
ll a[maxm];
vector<int> ve[maxm];

void dfs(int x,int fa){
	dep[x] = dep[fa] + 1;
	Fa[x] = fa;
	int up = -1;
	//处理当前节点向上最多不变色走到哪里
	for(int i = 0; i < 60; ++ i){
		col[i][x] = col[i][fa];
		if((a[x] >> i & 1) && col[i][x] == -1)
			col[i][x] = x;
		if(~a[x] >> i & 1)
			col[i][x] = -1;
		if(up == -1 || (col[i][x] != -1 && dep[up] > dep[col[i][x]]))
			up = col[i][x];
	}
	//倍增处理变2的k次方色会跳到哪里
	jmp[0][x] = up;//不变色到up
	for(int i = 0; i <= 19; ++ i){
		f[i + 1][x] = f[i][f[i][x]];//向上2的i + 1的父亲为自己向上2的i的父亲再向上i
		if(jmp[i][x] != -1)
			jmp[i + 1][x] = jmp[i][jmp[i][x]];//倍增向上跳
	}
	for(auto it : ve[x]){//dfs子树
		if(it == fa) continue;
		f[0][it] = x;
		dfs(it, x);
	}
	return ;
}

int lca(int x, int y){//朴素的lca求最近公共祖先
	if(dep[x] < dep[y]) swap(x, y);
	for(int i = 20; i >= 0; -- i){
		if(dep[f[i][x]] >= dep[y])
			x = f[i][x];
		if(x == y) return x;
	}
	for(int i = 20; i >= 0; -- i){
		if(f[i][x] != f[i][y]){
			x = f[i][x];
			y = f[i][y];
		}
	}
	return f[0][x];
}

int calc(int x, int y){//计算最终答案
	int L = lca(x, y);
	int ans = dep[x] + dep[y] - dep[L] * 2;//跑的路径一定是在u,v和L之间的最短路径上
	if(x == L) swap(x, y);
	if(x == L) return 0;//两点均位与L处
	if(y == L){//一点为祖先,另一点向上跑
		for(int i = 20; i >= 0; -- i){
			if(jmp[i][x] != -1 && dep[jmp[i][x]] > dep[L]){//x向上跑
				ans += 1 << i;//预处理可知需要2的i次方步
				x = jmp[i][x];
			}
		}
		if(a[x] & a[Fa[x]]) return ans;//最后一步x可达
		return -1;
	}
	//两点均向上跑
	for(int i = 20; i >= 0; -- i){
		if(jmp[i][x] != -1 && dep[jmp[i][x]] > dep[L]){
			ans += 1 << i;
			x = jmp[i][x];
		}
	}
	for(int i = 20; i >= 0; -- i){
		if(jmp[i][y] != -1 && dep[jmp[i][y]] > dep[L]){
			ans += 1 << i;
			y = jmp[i][y];
		}
	}
	if(jmp[0][x] == -1 || jmp[0][y] == -1) return -1;//有某个点不可达
	if(dep[jmp[0][x]] > dep[L] || dep[jmp[0][y]] > dep[L]) return -1;//某个点不可达
	bool flag = false;//判断在最近祖先处是否需要改变颜色
	for(int i = 0; i < 60; ++ i){
		if(col[i][x] != -1 && col[i][y] != -1){
			if(dep[col[i][x]] <= dep[L] && dep[col[i][y]] <= dep[L]) flag = true;
		}
	}
	if(flag) return ans;
	else return ans + 1;
}

void solve(){
	cin >> n >> q;
	mem(col, -1);
	mem(jmp, -1);
	for(int i = 1; i <= n; ++ i){
		cin >> a[i];
	}
	for(int i = 1; i < n; ++ i){
		int u, v;
		cin >> u >> v;
		ve[u].push_back(v);
		ve[v].push_back(u);
	}
	dfs(1,0);//dfs 预处理lca
	while(q--){
		int u, v;
		cin >> u >> v;
		cout << calc(u, v) << '\n';
	}
	return ;
}

signed main(){
	ios::sync_with_stdio(false), cin.tie(nullptr), cout.tie(nullptr);
	int _ = 1;
	// cin >> _;
	while(_ --){
		solve();
	}
	return 0;
}


J Fine Logic

题意
给你 n 个抽象物体,m 种两两胜利关系,让你找到最少的 1 ~ n 的排列的个数,使得所有的胜利关系均在至少一个排列中得到体现。

思路
不难想到最多 2 个排列,最少 1 个排列即可表示题意
n 个顶点,m 条有向边,若不成环 topo 排序即为答案,若成环,则输出 1 ~ n 和 n ~ 1 即可,因为这俩包含了所有的两两可能情况。如果说能有办法能找到别的处理环的办法,也不是不可以,但是这样的输出最优,但需要一点思维。

代码

//>>>Qiansui
#include<bits/stdc++.h>
#define ll long long
#define ull unsigned long long
#define mem(x,y) memset(x, y, sizeof(x))
#define debug(x) cout << #x << " = " << x << '\n'
#define debug2(x,y) cout << #x << " = " << x << " " << #y << " = "<< y << '\n'
//#define int long long

using namespace std;
typedef pair<int, int> pii;
typedef pair<ll, ll> pll;
typedef pair<ull, ull> pull;
typedef pair<double, double> pdd;
/*

*/
const int maxm = 1e6 + 5, inf = 0x3f3f3f3f, mod = 998244353;
vector<int> e[maxm];
vector<int> ans[2];
int n, m, k, in[maxm];

void solve(){
	cin >> n >> m;
	k = 1;
	for(int i = 0; i < m; ++ i){
		int u, v;
		cin >> u >> v;
		e[u].push_back(v);
		++ in[v];
	}
	deque<int> q;
	for(int i = 1; i <= n; ++ i){
		if(in[i] == 0) q.push_back(i);
	}
	while(!q.empty()){
		int t =q.front();
		q.pop_front();
		ans[0].push_back(t);
		for(auto c : e[t]){
			-- in[c];
			if(in[c] == 0) q.push_back(c);
		}
	}
	if(ans[0].size() != n){
		k = 2;
		ans[0].clear();
		for(int i = 1; i <= n; ++ i){
			ans[0].push_back(n - i + 1);
			ans[1].push_back(i);
		}
	}
	cout << k << '\n';
	for(int i = 0; i < k; ++ i){
		for(auto c : ans[i]){
			cout << c << ' ';
		}
		cout << '\n';
	}
	return ;
}

signed main(){
	ios::sync_with_stdio(false), cin.tie(nullptr), cout.tie(nullptr);
	int _ = 1;
	// cin >> _;
	while(_ --){
		solve();
	}
	return 0;
}

posted on 2023-07-24 19:06  Qiansui  阅读(21)  评论(0编辑  收藏  举报