【题解】 Codeforces Round #661 (Div. 3)

当你心情不好的时候 \(\textrm{vp}\) 一场 \(\textrm{div3}\) 就好了。

Link \(\textrm{to Codeforces}\)

Contest

A. Remove Smallest

Legend

给定长 \(n\ (1 \le n \le 50)\) 的数组 \(a\ (1 \le a_i \le 100)\),你每次可以选两个差的绝对值不超过 \(1\) 的两个数出来,并丢掉一个,放回去一个。问是否能最后让数组只剩一个元素?

数据组数 \(1 \le t\le 1000\)

Editorial

排序后判断相邻的数字是否相差不超过 \(1\)

复杂度 \(O(tn\log n)\)

Code

void solve(){
	int n; cin >> n;
	for(int i = 1 ; i <= n ; ++i) cin >> a[i];
	std::sort(a + 1 ,a + 1 + n);
	for(int i = 2 ; i <= n ; ++i){
		if(a[i] - a[i - 1] > 1){
			puts("NO");
			return;
		}
	}
	puts("YES");
}

B. Gifts Fixing

Legend

给定长度为 \(n\ (1 \le n \le 50)\) 的数组 \(a,b\ (1 \le a_i,b_i \le 10^9)\),你每次可以做以下操作之一:

  • \(a_i \gets a_i - 1\)
  • \(b_i \gets b_i - 1\)
  • \(a_i \gets a_i - 1\)\(b_i \gets b_i - 1\)

\(a_i,b_i\) 不能变为负数。请问使得 \(a\) 中每个数都相等且 \(b\) 中每个数都相等的最少操作次数。

数据组数 \(1 \le t\le 1000\)

Editorial

显然要把 \(a\) 里的数全变成 \(\min a_i\),把 \(b\) 里的数全变成 \(\min b_i\)。然后对于每一个下标都先贪心地两个一起减,然后再单独减。

复杂度 \(O(tn)\)

Code

void solve(){
	int n; cin >> n;
	int mna = 1e9 ,mnb = 1e9;
	for(int i = 1 ; i <= n ; ++i){
		cin >> a[i];
		mna = min(mna ,a[i]);
	}
	for(int i = 1 ; i <= n ; ++i){
		cin >> b[i];
		mnb = min(mnb ,b[i]);
	}
	long long Ans = 0;
	for(int i = 1 ; i <= n ; ++i){
		int common = min(a[i] - mna ,b[i] - mnb);
		Ans += a[i] + b[i] - mna - mnb - common;
	}
	printf("%lld\n" ,Ans);
}

C. Boats Competition

Legend

给定长度为 \(n\ (1 \le n \le 50)\) 的数组 \(a,b\ (1 \le a_i,b_i \le n)\)。你希望决定一个 \(s\) 使得将 \(a,b\) 两两配对得出的 \(a_i+b_j=s\) 的对数尽可能多。请输出这个最大的对数。

数据组数 \(1 \le t\le 1000\)

Editorial

不知道这题目存在的意义,直接暴力枚举 \(s\)。贪心匹配即可。

复杂度 \(O(tn^2)\)

void solve(){
	int n; cin >> n;
	for(int i = 1 ; i <= n ; ++i){
		cin >> a[i];
	}
	sort(a + 1 ,a + 1 + n);
	int Ans = 0;
	for(int i = 2 * n ; i >= 2 ; --i){
		int l = 1 ,r = n;
		int cnt = 0;
		while(l < r){
			if(a[l] + a[r] > i)  --r;
			else if(a[l] + a[r] == i) ++cnt ,++l ,--r;
			else ++l;
		}
		Ans = max(Ans ,cnt);
	}
	cout << Ans << endl;
}

D. Binary String To Subsequences

Legend

给定长度为 \(n\ (1 \le \sum n \le 2\cdot 10^5)\)\(\texttt{01}\) 串,你需要把它分成尽可能少的不相交子序列使得每一个子序列都不存在两个相邻的数字相同,输出一种方案。

数据组数 \(1 \le t\le 2 \cdot 10^4\)

Editorial

还是贪心,能和之前的子序列接着就接;否则新开一个子序列。

复杂度 \(O(\sum n)\)

Code

char str[MX];
int what[MX] ,ans[MX];
void solve(){
	int n; cin >> n;
	cin >> str + 1;
	int one = 0 ,zero = 0 ,cnt = 0;
	vector<int> q[2];
	for(int i = 1 ; i <= n ; ++i){
		if(str[i] == '1'){
			if(zero){
				--zero;
				q[1].push_back(q[0].back());
				ans[i] = q[0].back();
				q[0].pop_back();
				++one;
			}
			else{
				q[1].push_back(++cnt);
				ans[i] = cnt;
				++one;
			}
		}
		else{
			if(one){
				--one;
				q[0].push_back(q[1].back());
				ans[i] = q[1].back();
				q[1].pop_back();
				++zero;
			}
			else{
				q[0].push_back(++cnt);
				ans[i] = cnt;
				++zero;
			}
		}
	}
	
	cout << one + zero << endl;
	for(int i = 1 ; i <= n ; ++i)
		printf("%d%c" ,ans[i] ," \n"[i == n]);
}

E1. Weights Division (easy version)

Legend

给定一棵树,\(n\ (2 \le n \le 10^5)\) 个节点,\(1\) 为根,每条边有边权 \(w_i\ (1 \le w_i \le 10^6)\)。我们认为一棵树的权重为:

\[\sum\limits_{leaf \in leaves}dist(leaf,1) \]

其中 \(dist(x,y)\) 表示树上 \(x,y\) 的距离,\(leaves\) 表示所有叶子节点构成的集合。

我们定义一次操作为:将一条边的边权 \(w_i \gets \left\lfloor \dfrac{w_i}{2} \right\rfloor\)

给定一个限制 \(S\),请你计算使得树的权重 \(\le S\) 的最少操作次数。

Editorial

题外话:本场比赛 \(\textrm{AC}\) 率从此题开始骤降。

显然边与边之间没有影响,直接按每条边考虑把树的权重转化为:

\[\sum\limits_{e \in \rm E} size_{e}\cdot w_e \]

其中 \(\rm E\) 表示树的边集,\(size_e\) 表示 \(e\) 这条边被多少条叶子到根路径经过,\(w_e\) 表示 \(e\) 的权值。那么我们每选条边 \(e\) 则会让树的权重减少:

\[\Delta _e=size_e(w_e-\left\lfloor \dfrac{w_i}{2} \right\rfloor) \]

则我们直接用一个优先队列维护这个所有边 \(\Delta\) 的最大值,每次都贪心选 \(\Delta\) 最大的直到满足要求。

复杂度 \(O(n \log n \log w_i)\)

Code

#include <bits/stdc++.h>

using namespace std;

#define LL long long

const int MX = 2e5 + 233;;

int head[MX] ,tot;
struct edge{
	int node ,next;
	int w;
}h[MX << 1];
void addedge(int u ,int v ,int w ,int flg = 1){
	h[++tot] = (edge){v ,head[u] ,w} ,head[u] = tot;
	if(flg) addedge(v ,u ,w ,false);
}

struct Edge{
	LL size ,w;
	LL cutoff()const{
		return size * (w - w / 2);
	}
	bool operator <(Edge B)const{
		return this->cutoff() < B.cutoff();
	}
};

priority_queue<Edge> q;
LL sum ,sz[MX];
void dfs(int x ,int f){
	sz[x] = 0;
	for(int i = head[x] ,d ; i ; i = h[i].next){
		if((d = h[i].node) == f) continue;
		dfs(d ,x);
		sz[x] += sz[d];
		q.push((Edge){sz[d] ,h[i].w});
		sum += sz[d] * h[i].w;
	}
	if(!sz[x]) sz[x] = 1;
}

void solve(){
	
	int n; LL S; cin >> n >> S;

	tot = 1;
	for(int i = 1 ; i <= n ; ++i) head[i] = 0;
	
	for(int i = 1 ,u ,v ,w ; i < n ; ++i){
		cin >> u >> v >> w;
		addedge(u ,v ,w);
	}
	
	sum = 0;
	while(!q.empty()) q.pop();
	dfs(1 ,0);
	
	int opt = 0;
	while(sum > S){
		++opt;
		Edge chos = q.top();
		sum -= chos.cutoff();
		chos.w /= 2;
		q.pop(); q.push(chos);
	}
	cout << opt << endl;
}

int main(){
	int T; cin >> T;
	while(T--) solve();
	return 0;
}

E2. Weights Division (hard version)

Legend

同上题,唯一的区别在于每条边的操作有了代价 \(c\),要么是 \(1\),要么是 \(2\)。请你使得代价尽可能小。

Editorial

改变贪心策略。对于 \(c=1\) 的,丢一个优先队列,\(c=2\) 的丢另一个。

每次判断:

  • \(c=1\) 的里取两个最优的(注意到两次可以取同一条边);
  • \(c=2\) 的里取一个最优的;

哪个更好选哪个,但注意只取一个(对于 \(c=1\) 的也只去最优的一个)。

以及注意如果用一次 \(c=1\) 的就行了就直接用。

Code

细节较多。

#include <bits/stdc++.h>

using namespace std;

#define LL long long

const int MX = 2e5 + 233;;

int head[MX] ,tot;
struct edge{
	int node ,next;
	int w ,c;
}h[MX << 1];
void addedge(int u ,int v ,int w ,int c ,int flg = 1){
	h[++tot] = (edge){v ,head[u] ,w ,c} ,head[u] = tot;
	if(flg) addedge(v ,u ,w ,c ,false);
}

struct Edge{
	LL size ,w;
	LL cutoff()const{return size * (w - w / 2);}
	bool operator <(Edge B)const{
		return this->cutoff() < B.cutoff();
	}
};

priority_queue<Edge> q[3];
LL sum ,sz[MX];
void dfs(int x ,int f){
	sz[x] = 0;
	for(int i = head[x] ,d ; i ; i = h[i].next){
		if((d = h[i].node) == f) continue;
		dfs(d ,x);
		sz[x] += sz[d];

		q[h[i].c].push((Edge){sz[d] ,h[i].w});

		sum += sz[d] * h[i].w;
	}
	if(!sz[x]) sz[x] = 1;
}

void solve(){

	int n; LL S; cin >> n >> S;

	tot = sum = 0;
	for(int i = 1 ; i <= n ; ++i) head[i] = 0;
	while(!q[1].empty()) q[1].pop();
	while(!q[2].empty()) q[2].pop();

	for(int i = 1 ,u ,v ,w ,c ; i < n ; ++i){
		cin >> u >> v >> w >> c;
		addedge(u ,v ,w ,c);
	}
	
	dfs(1 ,0);

	int opt = 0;
	while(sum > S){
		++opt;
		if(!q[1].empty() && sum - q[1].top().cutoff() <= S) break;

		if(!q[1].empty()){
			Edge t1 = q[1].top(); q[1].pop();
			Edge midt1 = t1;
			midt1.w /= 2;
			if(q[2].empty() || 
			t1.cutoff() + max(q[1].empty() ? 0 : q[1].top().cutoff() ,midt1.cutoff()) >= q[2].top().cutoff()){
				sum -= t1.cutoff();
				t1.w /= 2;
				if(t1.w) q[1].push(t1);
				continue;
			}
			else{
				q[1].push(t1);
			}
		}

		Edge C = q[2].top();
		sum -= C.cutoff();
		C.w /= 2;
		q[2].pop(); q[2].push(C);
		++opt;
	}
	cout << opt << endl;
}

int main(){
	int T; cin >> T;
	while(T--) solve();
	return 0;
}

F. Yet Another Segments Subset

Legend

给定 \(n\ (1 \le n \le 3000)\) 个不同的区间 \([l_i,r_i]\ (1 \le l_i \le r_i \le 2 \cdot 10^5)\)。从中选出尽可能多的区间使得他们两两要么包含、要么没有交集。

Editorial

\(dp_i\) 为选中区间 \(i\) 与它包含的区间,最优能选多少个。

我懒得离散化,这个直接 \(O(n^2 \log r)\) 搞。

\(f_i\) 表示考虑了前 \(i\) 个区间,最优能选多少个。

我懒得离散化,这个直接 \(O(n \log r)\) 搞。

所以复杂度是 \(O(n^2 \log r)\) 的,离散化之后 \(O(n^2 \log n)\)

Code

#include <bits/stdc++.h>

using namespace std;

#define LL long long

const int MX = 3000 + 233;
const int MXV = 2e5 + 2;

struct interval{
	int l ,r;
	bool operator <(const interval &B)const{
		return r == B.r ? l > B.l : r < B.r;
	}
}V[MX];

int dp[MX] ,used[MX];

struct BIT{
	int data[MXV] ,size ,hsh[MXV];
	void add(int x ,int v ,int HHH){
		for(; x <= size ; x += x & -x){
			if(hsh[x] != HHH) data[x] = v, hsh[x] = HHH;
			data[x] = max(data[x] ,v);
		}
	}
	int qmax(int x ,int HHH){
		int Ans = 0;
		for( ; x > 0 ; x -= x & -x){
			if(hsh[x] != HHH) data[x] = 0;
			Ans = max(Ans ,data[x]);
		}
		return Ans;
	}
}C;

int ddp[MX] ,cnt;
void solve(){
	int n; cin >> n;
	C.size = MXV - 1;
	for(int i = 1 ,l ,r ; i <= n ; ++i){
		cin >> l >> r;
		V[i] = (interval){l ,r};
	}
	std::sort(V + 1 ,V + 1 + n);
	
	int Ans = 0;
	for(int i = 1 ; i <= n ; ++i){
		++cnt;
		int mx = 0;
		for(int j = 1 ; j < i ; ++j){
			if(V[j].l >= V[i].l){
				int tmp = dp[j] + C.qmax(V[j].l - 1 ,cnt);
				C.add(V[j].r ,tmp ,cnt);
			}
		}
		mx += C.qmax(MXV - 1 ,cnt);
		dp[i] = mx + 1;
	}
	++cnt;
	for(int i = 1 ; i <= n ; ++i){	
		ddp[i] = C.qmax(V[i].l - 1 ,cnt) + dp[i];
		Ans = max(Ans ,ddp[i]);
		C.add(V[i].r ,ddp[i] ,cnt);
	}
	cout << Ans << endl;
}

int main(){
	int T; cin >> T;
	while(T--) solve();
	return 0;
}

Summary

总的来说这场比赛还是很简单的,但是 \(\rm E2\) 这个题卡了我很久时间,这是因为我对于这类贪心 \(\textrm{priority_queue}\) 使用的不敏感,出现了很多次访问空队列的情况。一定要记得判断 \(\textrm{!q.empty()}\)。以及此题我细节方面有很多没有注意的,比如一开始我没有意识到在 \(c=1\) 的情况下是可以连续选择两次相同的边导致 \(\textrm{WA28}\)

但是这套题目作为 \(\textrm{div3}\) 还是很有趣的,只是作为 \(\textrm{div2}\) 选手来说有点套路。

posted @ 2020-09-12 15:58  Imakf  阅读(279)  评论(0编辑  收藏  举报