IT数码 购物 网址 头条 软件 日历 阅读 图书馆
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
图片批量下载器
↓批量下载图片,美女图库↓
图片自动播放器
↓图片自动播放器↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁
 
   -> 数据结构与算法 -> Codeforces Round #798 (Div. 2) -> 正文阅读

[数据结构与算法]Codeforces Round #798 (Div. 2)

link
期末结束了,开始训练。不知道是太久没做题了还是太弱了,感觉连ABC都写了挺久的,所以就都写一份题解吧。

A

题意

给出两个字符串以及一个初始为空的答案串,每次可以从两个字符串中任选一个字符移动到答案串的末尾,直到某一串为空,问答案串的最小字典序。其中,同一个串不能连续取超过 k k k 次。(保证两个串没有相同字母)

思路

因为和顺序无关,所以首先将2个串内部分别按字典序从小到大排序,然后贪心地取小的即可,并维护取到哪个串以及连续取了该串的多少个,等于 k k k 时就强制换串。因为保证了没有相同字母,所以不需要考虑相同的情况。

代码

int n, m, k;
void solve() {
    cin >> n >> m >> k;
    string s1, s2, s3;
    cin >> s1 >> s2;
    sort(s1.begin(), s1.end());
    sort(s2.begin(), s2.end());
    s3.clear();
    int cnt = 0;
    int last =-1;
    while(s1.size() && s2.size()) {
    	if(cnt < k) {
    		cnt++;
    		if(*s1.begin() < *s2.begin()) {
    			s3.pb(*s1.begin());
    			s1.erase(s1.begin());
    			if(last != 1) {
    				last = 1;
    				cnt = 1;
    			}
    		}
    		else {
    			s3.pb(*s2.begin());
    			s2.erase(s2.begin());
    			if(last != 2) {
    				last = 2;
    				cnt = 1;
    			}
    		}
    	}
    	else {
    		if(last == 1) {
    			last = 2;
    			cnt = 1;
    			s3.pb(*s2.begin());
    			s2.erase(s2.begin());
    		}
    		else {
    			last = cnt = 1;
    			s3.pb(*s1.begin());
    			s1.erase(s1.begin());
    		}
    	}
    }
    cout << s3 << endl;
}

B

题意

给出一个全排列,求在满足错排条件下的最小字典序排列。

思路

可以直接贪心,用一个 s e t set set 维护当前还未被取的数,能取最小的就取最小的,不能就取集合中第二小的,若最后一个数不满足错排只需要交换最后两个数即可。

代码

int a[maxn], n;
void solve() {
    cin >> n;
    set<int> s;
    for(int i = 1; i <= n; i++) {
    	cin >> a[i];
    	s.insert(i);
    }
    if(n == 1) {
    	cout << -1 << endl;
    	return ;
    }
    vector<int> ans;
    ans.pb(0);
    for(int i = 1;i <= n; i++) {
    	if(a[i] != *s.begin() || i == n) {
    		ans.pb(*s.begin());
    		s.erase(s.begin());
    	}
    	else {
    		ans.pb(*s.begin()+1);
    		s.erase(*(s.begin())+1);
    	}
    }
    if(ans[n] == a[n]) {
    	swap(ans[n], ans[n-1]);
    }
    for(int i = 1; i <= n; i++) {
    	cout << ans[i] <<' ';
    }
    cout << endl;
}

C

题意

给出一棵根为1的二叉树,有一个病毒从根开始传播,每轮会传播到其中一个儿子,同时你每轮可以选择一个节点砍掉,如果这样做则以该结点为根的子树都不会被感染。问最多能有多少个节点既没有被感染也没有被砍掉?

思路

这道题用两个标记来标记有几个儿子还算挺妙的,学到了
树形dp,用 cnt[] 和 dp[] 数组分别维护以 i i i 为根的子数的节点数量以及最多有多少个节点满足条件。
因为是二叉树,所以如果一个节点只有一个儿子,那么我们可以直接把它的儿子砍掉,dp[i] = cnt[son] - 1 。两个儿子的话 dp[i] = max(dp[son1] + cnt[son2], dp[son2] + cnt[son1]) - 1

D

1900

题意

给你一个由黑白两种颜色构成的 n × m ( n , m ≤ 1000 ) n\times m(n,m\leq 1000) n×m(n,m1000) 的矩阵,请你找到某个坐标 ( i , j ) (i,j) (i,j) ,使得离这个坐标曼哈顿距离最大的黑格子距离最小。

思路

想了一会才发现,无论图上有多少个黑格子,与任意一个点曼哈顿距离最大的一定是“左上,左下,右上,右下”(最多)四个顶点之一。另一方面,如果我们知道了这四个点坐标,那么对任意点都可以 O ( 1 ) O(1) O(1) 得出答案。所以考虑如何求这四个点。

  1. 右下和左上:显然分别是 max ? ( i + j ) 和 min ? ( i + j ) \max(i+j) 和\min(i+j) max(i+j)min(i+j)
  2. 右上和左下:画个图发现右上是 max ? ( j ? i ) \max(j-i) max(j?i) ,左下是 max ? ( i ? j ) \max(i-j) max(i?j)

当然最后求出来的“右上”不一定对每个点都处在右上方,但并不影响答案,因为此时“右上”一定不会构成最大值。

代码

int n, m;
char a[1010][1010];
pii ru, rd, lu, ld;
void judge(int i, int j) {
	if(i + j < lu.x + lu.y) {
		lu.x = i, lu.y = j;
	}
	if(i + j > rd.x + rd.y) {
		rd.x = i, rd.y = j;
	}
	if(j - i > ru.y - ru.x) {
		ru.x = i, ru.y = j;
	}
	if(i - j > ld.x - ld.y) {
		ld.x = i, ld.y = j;
	}
}
int cal(int i, int j) {
	int ans = abs(rd.x-i)+abs(rd.y-j);
	chmax(ans, abs(lu.x-i)+abs(lu.y-j));
	chmax(ans, abs(ru.x-i)+abs(ru.y-j));
	chmax(ans, abs(ld.x-i)+abs(ld.y-j));
	return ans;
}
void solve() {
    cin >> n >> m;
    rd = mp(0, 0);
    lu = mp(INF, INF);
    ru = mp(INF, 0);
    ld = mp(0, INF);
    for(int i = 1; i <= n; i++) {
    	for(int j = 1; j <= m; j++) {
    		cin >> a[i][j];
    		if(a[i][j] == 'B') {
    			judge(i, j);
    		}
    	}
    }
    int ans = INF;
    int xx = 0, yy = 0;
    for(int i = 1; i <= n; i++) {
    	for(int j = 1; j <= m; j++) {
    		if(cal(i, j) < ans) {
    			ans = cal(i, j);
    			xx = i, yy = j;
    		}
    	}
    }
    cout << xx << ' ' << yy << endl;
}

E

2500

题意

给出一个序列 n ( n ≤ 2000 ) n(n\leq 2000) n(n2000),我们定义 i i i j j j 之间存在一条边当且仅当 a i & a j ≠ 0 a_i \&a_j \neq 0 ai?&aj??=0, 可以进行以下两种操作任意次:

  1. 选一个 i i i ,使 a i + 1 a_i+1 ai?+1
  2. 选一个 i i i ,使 a i ? 1 a_i-1 ai??1 ,其中 a i > 0 a_i > 0 ai?>0

问至少多少操作次使得序列构成的图是联通的?

思路

首先如果这个序列中有0,我们必须把它们先变成1。
此时的序列有如下结论:至多操作2次满足联通
方法:
l b ( x ) lb(x) lb(x) x x x 的 lowbit 值, m l b mlb mlb max ? ( l b ( x ) ) , x ∈ 序 列 \max(lb(x)),x\in 序列 max(lb(x)),x ,这里假设 x x x 的 lowbit 是最大的,那么我们只需要将 x x x 减1,则所有 lowbit 小于 l b ( x ) lb(x) lb(x) 的数都满足和 x x x 连通,剩下的可能不与 x x x 连通的数 lowbit 也一定等于 l b ( x ) lb(x) lb(x), 然后我们只需要找其中一个 +1 就可以了。

上面这两种都是先考虑减,但还有一种特殊情况,比如 1010 , 0110 , 0001 1010,0110,0001 1010,0110,0001,就需要选一个数加1,最少一次就可以得到。

总之具体做的时候枚举每个数++,- -,++,时间复杂度 O ( 30 n 2 ) O(30n^2) O(30n2)

代码

int n;
int a[maxn];
int fa[maxn], sz[maxn];
int find(int x) {
	if(fa[x] == x) return x;
	return fa[x] = find(fa[x]);
}
void merge(int x, int y) {
	int fx = find(x);
	int fy = find(y);
	int tmp = sz[fx] + sz[fy];
	if(fx != fy) {
		fa[fy] = fx;
		sz[fx] = tmp;
	}
}
int lb(int x) {
	return x & (-x);
}
void init() {
	for(int i = 0; i <= 30; i++) fa[i] = i, sz[i] = 1;
}
bool check() {
	init();
	int k = 0;
	for(int i = 1; i <= n; i++) {
		k |= a[i];
	}
	for(int i = 1; i <= n; i++) {
		int la = -1;
		for(int j = 0; j <= 30; j++) {
			if((a[i] >> j) & 1) {
				if(la != -1) merge(la, j);
				la = j;
			}
		}
	}
	set<int> s;
	for(int i = 0; i <= 30; i++) {
		if((k >> i) & 1) s.insert(find(i));
	}
	return s.size() == 1;
}
int ans = 0;
void print() {
	cout << ans << endl;
	for(int i = 1; i <= n; i++) {
		cout << a[i] << ' ';
	}
	cout << endl;
	return;
}
void solve() {
    cin >> n;
    ans = 0;
    for(int i = 1; i <= n;i++) {
    	cin >> a[i];
    	if(!a[i]) a[i]++, ans++;
    }
    if(check()) {
    	print();
    	return;
    }
    for(int i = 1; i <= n; i++) {
    	a[i]++;
    	ans++;
    	if(check()) {
    		print();
    		return;
    	}
    	ans--;
    	a[i]--;
    }
    int id = 0, mlb = 0;
    for(int i = 1; i <= n; i++) {
    	if(lb(a[i]) > mlb) {
    		id = i;
    		mlb = lb(a[i]);
    	}
    }
    vector<int> v;
    for(int i = 1; i <= n; i++) {
    	if(lb(a[i]) == mlb) {
    		a[i]--;
    		ans++;
    		if(check()) {
    			print();
    			return;
    		}
    		ans--;
    		a[i]++;
    	}
    }
    a[id]--;
    ans++;
    if(check()) {
    	print();
    	return;
    }
    for(int i = 1; i <= n; i++) {
    	if(i == id) continue;
    	a[i]++;
    	ans++;
    	if(check()) {
    		print();
    		return;
    	}
    	ans--;
    	a[i]--;
    }
}
  数据结构与算法 最新文章
【力扣106】 从中序与后续遍历序列构造二叉
leetcode 322 零钱兑换
哈希的应用:海量数据处理
动态规划|最短Hamilton路径
华为机试_HJ41 称砝码【中等】【menset】【
【C与数据结构】——寒假提高每日练习Day1
基础算法——堆排序
2023王道数据结构线性表--单链表课后习题部
LeetCode 之 反转链表的一部分
【题解】lintcode必刷50题<有效的括号序列
上一篇文章           查看所有文章
加:2022-06-23 00:59:44  更:2022-06-23 01:00:48 
 
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁

360图书馆 购物 三丰科技 阅读网 日历 万年历 2024年11日历 -2024/11/26 1:30:04-

图片自动播放器
↓图片自动播放器↓
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
图片批量下载器
↓批量下载图片,美女图库↓
  网站联系: qq:121756557 email:121756557@qq.com  IT数码