CF2165 VP 记录

A

贪心,注意到从小到大合并,每次选择代价少的最优,因为生成的新数等于代价. 在此基础上模拟即可,可以使用链表实现. 我用的链表 + 并查集,感觉怪怪的.

点击查看代码
#include<bits/stdc++.h>
using namespace std;
typedef long long ll;

const int maxn = 2e5 + 10;
int T, n, a[maxn];
int p[maxn], l[maxn], r[maxn];

int fa[maxn];
int fd(int u) {return u == fa[u] ? u : fa[u] = fd(fa[u]);}
void merge(int u, int v) {
	u = fd(u), v = fd(v);
	if(u != v) {
		if(a[u] < a[v]) swap(u, v);
		fa[v] = u;
	}
}
inline bool cmp(int x, int y) {return a[x] < a[y];}

void solve(){
	ll ans = 0;
	cin >> n; for(int i = 1; i <= n; i++) cin >> a[i], p[i] = i, l[i] = i - 1, r[i] = i + 1;
	l[1] = n, r[n] = 1; sort(p + 1, p + n + 1, cmp);
	for(int i = 1; i <= n; i++) fa[i] = i;
	for(int i = 1; i < n; i++) {
		int L = fd(l[p[i]]), R = fd(r[p[i]]), nw = fd(p[i]);
		if(max(a[L], a[nw]) < max(a[R], a[nw])) ans += max(a[L], a[nw]), merge(L, nw), r[L] = r[nw], l[nw] = l[L];
		else ans += max(a[R], a[nw]), merge(R, nw), l[R] = l[nw], r[nw] = r[R];
	} cout << ans << "\n";
}

int main() {
	ios :: sync_with_stdio(false); cin.tie(0); cout.tie(0);
	
	cin >> T; while(T--) solve();
	
	return 0;
} 

B

众数的限制难以刻画,正难则反.

考虑对于一个生成的多重集合 \(S\) 来判断是否合法,我们发现 \(S\) 中未出现的数 \(x\),即在任意一个划分出来的多重集合中为非众数的,满足限制:

\[\max \limits_{x\notin S}\{cnt_x\}\le\sum_{y\in S}cnt_y \]

这个条件是充要的. 考虑限制最严格的时候是 \(cnt_x\)\(x\) 在同一个划分出来的多重集合里面,那么至少有上述限制.

考虑根据限制设计 DP,发现是否在多重集合里面选这个数就是 \(0/1\) 背包. 设 \(f_j\) 表示选的 \(\sum cnt_y=j\) 时的方案数. 选一个数后,必然可以通过调整(比如每个数划分一个多重集合)凑出所有 \(1\sim cnt_y\) 个出现在生成的多重集合中的情形,所以有转移(倒序枚举 \(j\)):

\[f_j\leftarrow f_j+cnt_y\times f_{j-cnt_y} \]

最后的答案即:

\[ans=\sum_{i=\max \limits_{x\notin S}\{cnt_x\}}^nf_i \]

点击查看代码
#include<bits/stdc++.h>
using namespace std;

const int maxn = 5e3 + 10, mo = 998244353;
int T, n, a[maxn];
int cnt[maxn], f[maxn];

inline int add(const int &x, const int &y) {return x + y >= mo ? x + y - mo : x + y < 0 ? x + y + mo : x + y;}
inline void upd(int &x, const int &y) {x = add(x, y);}

void solve() {
	int mxc = 0, ans = 0; memset(cnt, 0, sizeof cnt);
	cin >> n; for(int i = 1; i <= n; i++) cin >> a[i], mxc = max(mxc, ++cnt[a[i]]);
	memset(f, 0, sizeof f); f[0] = 1;
	for(int i = 1; i <= n; i++) if(cnt[i]) {
		for(int j = n; j >= cnt[i]; j--) upd(f[j], 1ll * cnt[i] * f[j - cnt[i]] % mo);
	}
	for(int i = mxc; i <= n; i++) upd(ans, f[i]);
	cout << ans << "\n";
}

int main() {
	ios :: sync_with_stdio(false); cin.tie(0); cout.tie(0);
	
	cin >> T; while(T--) solve();
	
	return 0;
}

C

贪心,思路是明确的,考场上有一些细节没想清楚.

拆位,从高位到低位考虑,若当前存在 \(a_{i,t}\ge c_t\) 则不需要操作,否则找到最大的 \(a_i\) 改成 \(2^t\). 发现这个操作至多进行 \(O(\log V)\) 次,即我们只需要拿出最大的 \(O(\log V)\)\(a_i\) 来进行上述操作,每次做完一位把 \(a_i\) 这一位去掉即可.

点击查看代码
#include<bits/stdc++.h>
using namespace std;
typedef long long ll;

const int maxn = 5e5 + 10;
int T, n, q, a[maxn], tmp[40];

void solve() {
	cin >> n >> q; for(int i = 1; i <= n; i++) cin >> a[i];
	sort(a + 1, a + n + 1); reverse(a + 1, a + n + 1); 
	
	int lim = min(n, 31);
	for(int i = 1; i <= q; i++) {
		int c; ll ans = 0 ; cin >> c;
		for(int i = 1; i <= lim; i++) tmp[i] = a[i];
		for(int t = 30; t >= 0; t--) {
			int cnt = 0;
			for(int i = 1; i <= lim; i++) cnt += (tmp[i] >> t & 1);
			if(cnt > (c >> t & 1)) break;
			if(cnt < (c >> t & 1)) {
				int id = 1; for(int i = 2; i <= lim; i++) if(tmp[i] > tmp[id]) id = i;
				ans += (1 << t) - tmp[id], tmp[id] = 0;//补位之后对后面的位无贡献 
			}
			else for(int i = 1; i <= lim; i++) tmp[i] &= (1 << t) - 1;
		} cout << ans << "\n";
	}
	return;
}

int main() {
	ios :: sync_with_stdio(false); cin.tie(0); cout.tie(0);
	
	cin >> T; while(T--) solve();
	
	return 0;
}

D

有点神奇的贪心.

考虑把划分成最少的子序列转换为进行最多次形如序列中左右两个相差为 \(1\) 的数的匹配. 每个数仅能与左边和右边各一个数匹配. 一个贪心策略是,如果我们每次找当前位置右边的数来匹配,那么从右往左匹配最优. 即我们希望剩下没匹配的数尽量靠左,这样限制更加宽松. 可以用 set 维护,非常好写,时间复杂度 \(O(n\log n)\). 精细实现可以做到 \(O(n)\).

点击查看代码
#include<bits/stdc++.h>
using namespace std;

const int maxn = 1e6 + 10;
int T, n;

vector<int> pos[maxn << 1];
set<int> s[maxn << 1];

void reinit() {
	for(int i = 1; i <= 2 * n; i++) s[i].clear();
	for(int i = 1; i <= 2 * n; i++) pos[i].clear();
}

void solve() {
	cin >> n; int ans = n;
	for(int i = 1; i <= n; i++) {int x; cin >> x, pos[x].push_back(i), s[x].insert(i);}
	for(int i = 1; i <= 2 * n; i++) {
		reverse(pos[i].begin(), pos[i].end());
		for(int x : pos[i]) {
			auto p = s[i - 1].lower_bound(x);
			if(p != s[i - 1].end()) {
				ans--, s[i - 1].erase(p);
			}
			else {
				p = s[i + 1].lower_bound(x);
				if(p != s[i + 1].end()) {
					ans--, s[i + 1].erase(p);
				}
			}
		}
	} cout << ans << "\n";
	reinit();
}

int main() {
	ios :: sync_with_stdio(false); cin.tie(0); cout.tie(0);
	
	cin >> T; while(T--) solve();
	
	return 0;
} 

E

鬼题.

F

非常巧妙的思维题.

首先将数区间转换成对每个 \(p_2\) 找到最小的 \(p_5\). 限制无法直接做,不妨拆成两部分 \(21\)\(435\). 一旦我们找到了所有的这样的两种子序列,拼起来的限制是 \(v_3>v_2,p_4>p_1\),于是可以做二维偏序来求解.

第一部分是简单的,可以用单调栈维护下降子序列来求得左边第一个大于 \(v_1\) 的位置. 第二部分相对大小关系非常神秘,一个非常非常厉害的观察是,最小的 \(p_5\) 一定是右边第一个大于 \(v_3\) 的数. 考虑分讨:

0cf36d8c5e3c93c6c85a8ac91ef191898bf411ac

  • 若存在 \(p_5<p_5'\) 且有 \(v_5>v_5'>v_4\)\(p_5\) 更优.
  • 若存在 \(p_5<p_5'\) 且有 \(v_5'>v_5>v_4\),选择 \(p_5\) 不劣.
  • 若存在 \(p_5<p_5'\) 且有 \(v_5'>v_4>v_5\),可以把 \(p_3\) 换到 \(p_5\) 处,仍然合法.

所以同样用一个单调栈求出 \((p_3,p_5)\)\(p_4\) 可以用扫描线+线段树二分实现.

posted @ 2025-11-20 20:46  Ydoc770  阅读(14)  评论(0)    收藏  举报