第 14 届陕西省国际大学生程序设计竞赛 B题思路分享(构造)

题意概述

\(n\) 堆石子,初始每一堆只有 \(1\) 颗石子。每次可以选一个位置 \(i\),从左右两边各拿一颗石子放到 \(i\)(如果在边界只拿单边的石子),如果某个位置的石子在操作后会变成负数,该操作不合法。

给定数组 \(a\)\(op\),求石子状态变成 \(a\) 的最少操作次数,如果 \(op=1\) 还要输出操作序列,此时保证 \(ans\le 2\times 10^5\)

\(3\le n \le 2\times 10^5\)

思路

考虑从终态开始,那么操作就变成拿 \(i\) 位置的石子分给两边。

由于最终状态为全 \(1\),容易想到一个性质:每次随便对 \(\gt 1\) 的位置操作,最终一定会到达终态,操作次数是固定的。

那么对于 \(op=1\) 的情况,由于保证了 \(ans\le 2\times 10^5\),用 \(set\) 维护可操作的位置,暴力即可。

对于 \(op=0\) 的情况,将操作转化为对前缀和数组的操作。

  • \(2 \le i \le n-1\) 操作,\(pre_i-1\)\(pre_{i-1}+1\),相当于把 \(i\) 位置一个石子左移到 \(i-1\)

  • \(i=n\) 操作,\(pre_{n-1}+1\),相当于在 \(n-1\) 位置造出来一个石子。

  • \(i=1\) 操作,\(pre_1-1\),相当于把 \(1\) 位置的一个石子丢掉。

先从右往左遍历,记录需要造多少个石子,然后再遍历一遍计算操作次数即可。

仔细观察会发现,上述的操作顺序没有保证每一步操作合法,所以 \(op=1\) 时,直接对着构造是错的。但是根据前面的结论,计算出的操作次数是正确的。

时间复杂度 \(\mathcal{O}(n\log n)\)

代码

//author:kzssCCC

#include <bits/stdc++.h>
using namespace std;
using ll = long long;


void solve(){
	int n,op;
	cin >> n >> op;

	vector<int> a(n+1);
	vector<ll> pre(n+1);
	for (int i=1;i<=n;i++){
		cin >> a[i];
		pre[i] = pre[i-1]+a[i];
	}

	auto b = pre;
	ll more = 0;

	for (int i=n-1;i>=1;i--){
		if (b[i]<i){
			more += i-b[i];
		}
		else{
			ll take = b[i]-i;
			b[i] -= take;
			if (i-1>=1) b[i-1] += take;
		}
	}

	pre[n-1] += more;
	ll res = 0;
	res += more;
	
	for (int i=n-1;i>=2;i--){
		ll take = pre[i]-i;
		pre[i] -= take;
		pre[i-1] += take;
		res += take;
	}

	ll temp = pre[1]-1;
	res += temp;
	pre[1] -= temp;

	cout << res << '\n';
	if (op){
		vector<int> wait;
		set<int> st;
		for (int i=1;i<=n;i++){
			if (a[i]>1){
				st.insert(i);
			}
		}		

		while (!st.empty()){
			int i = *st.begin();
			st.erase(st.begin());

			wait.push_back(i);
			a[i] -= i==1||i==n?1:2;
			if (i+1<=n){
				a[i+1]++;
			}
			if (i-1>=1){
				a[i-1]++;
			}

			if (a[i]>1){
				st.insert(i);
			}
			if (i+1<=n && a[i+1]>1 && !st.count(i+1)){
				st.insert(i+1);
			}
			if (i-1>=1 && a[i-1]>1 && !st.count(i-1)){
				st.insert(i-1);
			}
		}

		reverse(wait.begin(),wait.end());
		for (auto& v:wait){
			cout << v << ' ';
		}
		cout << '\n';
	}
}

int main(){
	ios::sync_with_stdio(false);
	cin.tie(0);
	
	int t = 1;
	// cin >> t;
	while (t--) solve();

	return 0;
}
posted @ 2026-05-18 21:32  kzssCCC  阅读(25)  评论(0)    收藏  举报