Codeforces Round 1022 (Div. 2) 补题

此次排名情况:

  • 共8k+,排名3037,做出ABC三题。
  • Rating+35,当前为 1361
  • 比赛链接

A. Permutation Warm-Up

题意:给定一个数字 \(n\) ,求 \(1\sim n\) 的所有排列与 \(1 \sim n\) 差值的和的不同数量。

赛时我通过部分样例总结规律,得出会 \(1,1,2,2,3,3,...\) 的差值往下增加。

于是先预处理,然后输出。

vector<int> vt(510);
void init(){
	vt[0]=0;
	vt[1]=1;
	for(int i=2,j=1;i<=500;i++){
		vt[i]=vt[i-1]+j;
		if(i%2) j++;
	}
}
void solve(){
	cin>>n;
	cout<<vt[n]<<"\n";
}

部分大佬题解:

对于一个 \(p_i=i\) 的排列,我们可以通过不断交换两个位置的数把它变成任意排列。

发现每次交换两个数增加的值是偶数,那么最后和的值只会是偶数。

然后猜测所有偶数都可以拿到。那么求出最大的值看有多少偶数。

最大的值为最后一个排列的差,即 \(n,n-1,....,1\) 与 \(1,2,...,n\) 的差。

void solve() {
    int n;cin >> n;
    int ans = 0;
    for (int i = 1; i <= n; ++ i) {
    	ans += abs(i - (n - i + 1));
    }
    cout << ans / 2 + 1 << "\n";
}

还有一种写法没看懂:

void solve(){
  int n;cin >> n;
  int res = 0;
  for (int i = 1; i < n; i++)
    res += min(i, n - i);
  cout << res + 1 << '\n';
}

B. SUMdamental Decomposition

题意:构造一个长度为 \(n\) 的正整数数组,数组元素全部异或结果为 \(x\) ,求这个数组的和最小是多少。

两个数异或为 \(0\) 不会对结果造成任何影响,当都取 \(1\) 时结果最小。

我们可以看 \(x\) 有多少个 \(1\),如果 \(n\) 大于这个数,就需要将多余的变成 \(0\)。小于或等于的直接就是 \(x\) 。可以采用新学的 bitset 来判断有多少个 \(1\) 。

一直错test2的测试点,特判 \(m=0\) 和 \(m=1\) 过了。

void solve(){
	cin>>n>>m;
	if(n==1&&m==0){
		cout<<"-1\n";
		return;
	}
	if(n==2&&m==1){
		cout<<"5\n";
		return ;
	}
	if(m==0){
		if(n%2==0) cout<<n<<"\n";
		else cout<<5+n-2<<"\n";
		return ;
	}
	if(m==1){
		if(n%2==1) cout<<n<<"\n";
		else cout<<5+n-2<<"\n";
		return ;
	}
	bitset<33> b(m);
	int z=b.count();
	ll sum=0;
	if(n>=z){
		if((n-z)%2==1){
			if(m%2==1){
				sum=m+n-z+1;
			}
			else sum=m+n-z+1;
		}
		else sum=m+n-z;
	}else{
		sum=m;
	}
	cout<<sum<<"\n";
}

部分大佬题解:

不需要 bitset ,直接使用内置函数 __builtin_popcount(x) 返回 \(1\) 的个数。

for(int t = rd();t--;){
    int n = rd(),x = rd(),v = __builtin_popcount(x);
    if(n <= v)printf("%d\n",x);
    else if((n-v)%2==0)printf("%d\n",x+n-v);
    else if(x > 1)printf("%d\n",x+n-v+1);
    else if(x == 1)printf("%d\n",n+3);
    else printf("%d\n",n>1?n+3:-1);
}

C. Neo’s Escape

题意:给定一个数组 \(a\) ,需要按大小顺序遍历所有元素,可以新建一个克隆人在一个元素前面,也可以让现有的克隆人左右移动,但是移动到某个元素,若该元素为遍历则直接遍历。最小新建克隆人数量是多少?

不要被题目带偏,说是要按大小顺序遍历所有元素,并且克隆人可以左右移动。

本质上这题目就是在统计数组 \(a\) 的峰的数量。

int n;cin >> n;
vector<int> a(n);
for (int i=0;i<n;i++) cin >> a[i];
ll ans=1;
bool d=0;
for(int i=1;i<n;i++){
    if(d==0&&a[i-1]>a[i]) d=1;
    else if(d==1&&a[i-1]<a[i]){
        ans++;
        d=0;
    }
}
cout<<ans<<"\n";

D. Needle in a Numstack

题意:交互题。给定 \(n\) 和 \(k\) ,有两个数组 \(A\) 和 \(B\) ,有几个条件

  • 两个数组长度之和是 \(n\)。
  • 两个数组的长度都至少为 \(k\)。
  • 数组中只包含从 \(1 \sim k\) 的数字。
  • 从 \(A\) 中任意取出 \(k\) 个连续元素,每个元素都是不同的,B也同样。

现在将 \(C\) 数组等于数组 \(A\) 加上数组 \(B\),即 \(C=A_1,...,A_x,B_1,...,B_y\)。

你可以提出问题询问 \(C\) 数组中第 \(i\) 个元素的值,请你找出数组 \(A\) 和数组 \(B\) 的长度,或者无法唯一确定长度。

CF大佬代码:

#include<bits/stdc++.h>
using namespace std;
const int N=1e6+5;
int T,n,k,a[N],b[N];
vector<int> p;
void solve(){
	if(n==2*k){
		cout<<'!'<<' '<<k<<' '<<k<<"\n";
		return;
	}
	if(p.empty()){
		cout<<'!'<<' '<<-1<<"\n";
		return;
	}
	int l=0,r=p.size()-1,res;
	cout<<'?'<<' '<<p[0]<<"\n";
	cin>>res;
	if(res==b[p[0]]){
		if(p[0]==k+1) cout<<'!'<<' '<<k<<' '<<n-k<<"\n";
		else cout<<'!'<<' '<<-1<<"\n";
		return;
	}
	while(l<r){
		int mid=l+r+1>>1;
		cout<<'?'<<' '<<p[mid]<<"\n";
		cin>>res;
		if(res==a[p[mid]]) l=mid;
		else r=mid-1;
	}
	if(r==p.size()-1){
		if(p[r]==n-k) cout<<'!'<<' '<<n-k<<' '<<k<<"\n";
		else cout<<'!'<<' '<<-1<<"\n";
		return;
	}
	if(p[r]+1==p[r+1]) cout<<'!'<<' '<<p[r]<<' '<<n-p[r]<<"\n";
	else cout<<'!'<<' '<<-1<<"\n";
}
int main(){
	ios::sync_with_stdio(false);
	cin.tie(0),cout.tie(0);
	cin>>T;
	while(T--){
		cin>>n>>k;
		for(int i=1;i<=k;++i) cout<<'?'<<' '<<i<<"\n",cin>>a[i];
		for(int i=n-k+1;i<=n;++i) cout<<'?'<<' '<<i<<"\n",cin>>b[i];
		for(int i=k+1;i<=n;++i) a[i]=a[i-k];
		for(int i=n-k;i;--i) b[i]=b[i+k];
		p.clear(),p.shrink_to_fit();
		for(int i=k+1;i<=n-k;++i) if(a[i]!=b[i]) p.emplace_back(i);
		solve();
	}
	return 0;
}

知乎大佬题解:

特别注意前后的长度都要大于等于 \(k\)。

询问前 \(k\) 个和后 \(k\) 个,观察循环节是否相同。

不相同,直接二分找到什么时候不相同的一段

然后直接暴力询问 \(k+1\) 长度,判断中间是否有一段即在 \(A\) ,也在 \(B\).

#include <bits/stdc++.h>
using namespace std;
using i64 = long long;
void solve(){
	int n, k;
	cin >> n >> k;
	auto query = [&](int x) -> int {
		cout << "? " << x <<"\n";
		int res;
		cin >> res;
		return res;
	};
	vector<int> pre(k), sf(k);
	for (int i = 1; i <= k; i++) 
		pre[i % k] = query(i);
	for (int i = n - k + 1; i <= n; i++) 
		sf[i % k] = query(i);
	if (pre == sf) {
		if (n == 2 * k) {
			cout << "! " << k << " " << k << "\n";
		} else {
			cout << "! -1" << "\n";
		}
		return;
	}
	int id = -1;
	for (int i = 0; i < k; i++) 
		if (pre[i] != sf[i]) 
			id = i;
	if (id == 0) id = k;
	int c = n / k + ((n % k) >= id ? 1 : 0);
	int l = 1, r = c;
	while (l < r) {
		int mid = l + r >> 1;
		if (query((mid - 1) * k + id) == pre[id % k]) {
			l = mid + 1;
		} else {
			r = mid;
		}
	}
	int lena = 0, lenb = n;
	for (int i = (l - 2) * k + id; i <= (l - 1) * k + id; i++) {
		int res = query(i);
		if (res == pre[i % k] && res != sf[i % k]) {
			lena = i;
		} else if (res != pre[i % k] && res == sf[i % k]) {
			lenb = min(lenb, i);
		}
	}
	lena = max(lena, k);
	lenb = min(lenb, n - k + 1);
	if (lena == lenb - 1)
		cout << "! " << lena << " " << n - lena << "\n";
	else 
		cout << "! -1" << "\n";
}
int main(){
	std::ios::sync_with_stdio(0);
	int t = 1;
	std::cin >> t;
	while (t--) {
		solve();
	}
	return 0;
}

E. Spruce Dispute

题意:有一棵有 \(n\) 个顶点的树,其中的 \(n-1\) 个顶点上挂着饰品,有 \(\frac{n-1}{2}\) 种不同的颜色,每种颜色恰好有两个饰品。

移走树的哪一条边,重新排列饰品,使相同颜色的饰品之间路径长度最大呢?

移走一条边:选择一对相邻顶点 \(a\) 和 \(b( a<b )\),然后从装饰树中移除顶点 \(b\) 并将 \(b\) 的所有相邻顶点( \(a\) 除外)直接连接到 \(a\) 。

知乎大佬题解:

首先观察到,所有的边都被最大的路径数经过时最优。

所有我们希望找到一个根,每棵子树的的大小都小于等于 \(n/2\),一定能找到。

然后所有点的贡献都是到他的路径长度加上点子树大小。我们发现这个点一定是叶子节点。

对于把节点分成两部分,且编号相同的不在同一个子树中,使用DFS序。

#include <bits/stdc++.h>
using namespace std;
using i64 = long long;
void solve(){
	int n;cin >> n;
	vector<vector<int>> adj(n + 1);
	for (int i = 1; i < n; i++) {
		int u, v;cin >> u >> v;
		adj[u].push_back(v);
		adj[v].push_back(u);
	}
	vector<int> sz(n + 1, 1), dep(n + 1), p(n + 1), in(n + 1), ord(n + 1);
	int cur = 0;
	auto dfs = [&](auto self, int u, int fa) -> void {
		sz[u] = 1;
		p[u] = fa;
		in[u] = ++cur;
		ord[in[u]] = u;
		for (auto v : adj[u]) {
			if (v == fa) {
				continue;
			}
			dep[v] = dep[u] + 1;
			self(self, v, u);
			sz[u] += sz[v];
		}
	};
	dfs(dfs, 1, 0);
	auto find = [&](auto self, int u) -> int {
		for (auto v : adj[u]) {
			if (v == p[u] || 2 * sz[v] <= n) {
				continue;
			}
			return self(self, v);
		}
		return u;
	};
	int rt = find(find, 1);
	p[rt] =dep[rt] =cur = 0;
	dfs(dfs, rt, 0);
	int x = -1;
	for (int i = 1; i <= n; i++) {
		if (i != rt && adj[i].size() == 1 && (x == -1 || dep[x] > dep[i])) {
			x = i;
		}
	}
	cout << x << " " << p[x] << "\n";
	if (x < p[x]) x = p[x];
	vector<int> ans(n + 1);
	ord.erase(ord.begin() + in[x]);
	for (int i = 1; i <= n / 2; i++) {
		ans[ord[i]] = ans[ord[i + n / 2]] = i;
	}
	for (int i = 1; i <= n; i++) {
		cout << ans[i] << " \n"[i == n];
	}
}
int main(){
	ios::sync_with_stdio(0),cin.tie(0);
	int t = 1;cin >> t;
	while (t--) {
		solve();
	}
	return 0;
}

F. Fallen Towers

题意:有一个长度为 \(n\) 的 \(a\) 数组。有一种操作是使 \(a_i=0\),\(a_i\) 后面的元素都 \(+1\),如果\(a_i\)本来就等于0,就不会都 \(+1\)。

现在按照任意顺序对所有的元素进行操作,即每个 \(a_i\) 都会操作一次。

最终得到的 \(a\) 数组必须是不递减的。求最终得到的 \(a\) 数组中不存在的最小非负整数。

CF大佬代码:

#include <bits/stdc++.h>
using namespace std;
using i64 = long long;
const int N=5e5+10;
int n;
i64 a[N],b[N],c[N],d[N];
bool check(int x){
	for(int j=n;j>=1;j--){
		c[j]=x-1-(n-j);
		c[j]=max(0ll,c[j]);
	}
	for(int i=1;i<=n;i++) b[i]=a[i];
	for(int j=1;j<=n;j++) d[j]=0;
	for(int i=1;i<=n;i++){
		d[i]+=d[i-1];
		if(d[i]<c[i]) return 0;
		if(i==n) return 1;
		i64 val=d[i]-c[i]+b[i];
		if(val==0) continue;
		i64 l=i+1;
		i64 r=min((i64)n,i+val);
		d[l]+=1;
		d[r+1]-=1;
	}
	return 1;
}
void solve(){
	cin>>n;
	for(int i=1;i<=n;i++) b[i]=0;
	for(int i=1;i<=n;i++) cin>>a[i];
	a[n]=0;
	int l=1,r=n+1;
	while(l<r){
		int mid=l+r+1>>1;
		if(check(mid)) l=mid;
		else r=mid-1;
	}
	cout<<r<<"\n";
}
int main() {
	ios::sync_with_stdio(0),cin.tie(0),cout.tie(0);
	int t=1;cin>>t;
	while(t--) solve();
	return 0;
}