题解:CF1889C1-Doremy‘s Drying Plan (Easy Version)

题解:CF1889C1-Doremy’s Drying Plan (Easy Version)

一、 题意描述

1. 题目链接

(1) CF链接

CodeForces

(2) 洛谷链接

洛谷

2. 题目翻译

有一个长度为 n n n 的序列,上面有 n n n 个点,编号为 1 1 1 n n n。现在给定 m m m 个区间,第 i i i 个区间覆盖了序列中的 l i l_i li r i r_i ri 之间(包含两端)的所有点。你可以删除其中的 2 2 2 个线段,使得序列中未被任何线段覆盖的点数最大。
1 ≤ n ≤ 2 ⋅ 1 0 5 1\le n\le 2\cdot 10^5 1n2105, 2 ≤ m ≤ 2 ⋅ 1 0 5 2 \le m \le 2\cdot 10^5 2m2105, k = 2 k = 2 k=2
∑ n + m ≤ 2 ⋅ 1 0 5 \sum n+m\le2\cdot 10^5 n+m2105

二、 思路分析

观察数据范围, n n n m m m 都是 1 0 5 10^5 105 级别,所以考虑 O ( n + m ) O(n+m) O(n+m) 左右的算法,当然带上几个小 l o g log log 也没问题。
对于序列中所有的点,我们可以对它们进行分类:
①被 0 0 0 个区间覆盖;
②被 1 1 1 个区间覆盖;
③被 2 2 2 个区间覆盖;
④被 3 3 3 个或者更多个区间覆盖。
想要求出某个点是哪一类,我们可以在读入线段时记录差分。
显然,对于①类点,无论选择哪两条,它们都是“未被任何线段覆盖的点”,所以直接计入总数。
对于④类点,无论怎么选都无法让它们变成“未被任何线段覆盖的点”,所以它们被我们抛弃了。
如果只能删除一个线段,那么对最终答案所能产生的贡献自然是这个线段内②类点的数量,为了迅速求出它,我们需要维护一个前缀和,存储在某个点之前有多少个②类点。
显然,我们可以把每个线段按照这种贡献从大到小排序,选取最大的两个,把它们的贡献相加计入总数。
问题:这样求出的贡献一定是最大的吗?
显然不是!我们只能把贡献计入一个存储最终贡献的最大值的变量。如果两个线段有重叠,那么最终的贡献还要加上中间重叠部分的③类点总数(这个我们也用前缀和维护),但是如果继续用刚才的方法进行排序取出的最大两个有可能不是最优的。
于是我们考虑,被两个线段覆盖的点有哪些?我们可以遍历一遍每个节点,通过前缀和求出它被多少区间覆盖。但问题又来了,到底是哪两个区间呢?我们可以把线段的左端点和右端点加一分别扔进两个序列(记录一下差分维护的过程),按照端点下标排序,并记录到底是哪个线段,在遍历节点之前,用一个集合来维护那些线段覆盖到遍历到的点,最后遍历到每个点 i i i,左端点的序列里端点下标为 i i i 的元素先把对应的线段编号扔进这个集合,再把它从序列中删除,右端点加一的序列同理,只不过是把里面的线段编号从集合里删除。最后如果集合里是两条线段,就计算这两条线段的贡献,和之前的贡献取最大值。最后把贡献最大值累加到①类点数量才是答案。
具体实现还要看代码。

三、 时间代价

算法主体遍历线段、节点是 O ( n ⋅ m ) O(n\cdot m) O(nm) 的,但是维护一大堆集合和序列用到 s e t set set,需要乘上一个 O ( l o g ( m ) ) O(log(m)) O(log(m)),所以总共时间复杂度可以估计成 O ( ( n + m ) ⋅ l o g ( m ) ) O((n+m)\cdot log(m)) O((n+m)log(m))

四、代码实现

#include<bits/stdc++.h>
#define N 220000
using namespace std;
multiset<pair<int,int>>in={},out={};
multiset<int>inc1={};
set<int>xd={};
int c[N]={},c1[N]={},c2[N]={},l[N]={},r[N]={},s[N]={},s1[N]={},s2[N]={},ans=0,inc=0,k=0,m=0,n=0,t=0;
int main(){
	scanf("%d",&t);
	while(t--){
		scanf("%d%d%d",&n,&m,&k);
		for(int i=1;i<=n+1;i++){
			c[i]=0;
			c1[i]=0;
			c2[i]=0;
		}
		in.clear();
		out.clear();
		for(int i=1;i<=m;i++){
			scanf("%d%d",&l[i],&r[i]);
			c[l[i]]++;
			c[r[i]+1]--;
			in.insert({l[i],i});
			out.insert({r[i]+1,i});
		}
		ans=0;
		for(int i=1;i<=n;i++){
			s[i]=s[i-1]+c[i];
			if(s[i]==0){
				ans++;
			}else{
				if(s[i]==1){
					c1[i]++;
				}else{
					if(s[i]==2){
						c2[i]++;
					}
				}
			}
			s1[i]=s1[i-1]+c1[i];
			s2[i]=s2[i-1]+c2[i];
		}
		inc1.clear();
		for(int i=1;i<=m;i++){
			inc1.insert(-(s1[r[i]]-s1[l[i]-1]));
		}
		inc=0;
		int x=*inc1.begin();
		inc1.erase(inc1.begin());
		int y=*inc1.begin();
		inc=max(inc,-(x+y));
		xd.clear();
		for(int i=1;i<=n;i++){
			while(out.empty()==false&&out.begin()->first==i){
				xd.erase(out.begin()->second);
				out.erase(out.begin());
			}
			while(in.empty()==false&&in.begin()->first==i){
				xd.insert(in.begin()->second);
				in.erase(in.begin());
			}
			if(xd.size()==2){
				auto now=xd.begin();
				int a=*now;
				now++;
				int b=*now;
				int ret=(s1[r[a]]-s1[l[a]-1])+(s1[r[b]]-s1[l[b]-1])+(s2[min(r[a],r[b])]-s2[max(l[a],l[b])-1]);
				inc=max(inc,ret);
			}
		}
		printf("%d\n",ans+inc);
	}
	return 0;
}
  • 20
    点赞
  • 19
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
你好!对于扫雷游戏的题解,我可以给你一些思路和代码示例。首先,你需要了解扫雷游戏的规则和要求。接下来,你可以使用C++语言来实现游戏逻辑和界面。 下面是一个简单的扫雷游戏的C++代码示例: ```cpp #include <iostream> #include <vector> #include <random> using namespace std; class MinesweeperGame { private: int rows; int cols; vector<vector<char>> board; vector<vector<bool>> revealed; vector<pair<int, int>> directions = {{-1, -1}, {-1, 0}, {-1, 1}, {0, -1}, {0, 1}, {1, -1}, {1, 0}, {1, 1}}; public: MinesweeperGame(int m, int n, int mineCount) { rows = m; cols = n; board.resize(rows, vector<char>(cols, ' ')); revealed.resize(rows, vector<bool>(cols, false)); placeMines(mineCount); calculateNumbers(); } void printBoard() { cout << " "; for (int j = 0; j < cols; j++) { cout << j << " "; } cout << endl; for (int i = 0; i < rows; i++) { cout << i << " |"; for (int j = 0; j < cols; j++) { cout << board[i][j] << "|"; } cout << endl; } } bool isGameOver() { for (int i = 0; i < rows; i++) { for (int j = 0; j < cols; j++) { if (board[i][j] == 'M' && revealed[i][j]) { return true; } } } return false; } void reveal(int row, int col) { if (row < 0 || row >= rows || col < 0 || col >= cols || revealed[row][col]) { return; } revealed[row][col] = true; if (board[row][col] == 'M') { return; } if (board[row][col] == '0') { for (auto dir : directions) { reveal(row + dir.first, col + dir.second); } } } private: void placeMines(int mineCount) { random_device rd; mt1

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值