训练表(中考前)

记号

sb 题 / 模板题前加:\(\circ\)

有一定难度的题前加:\(\Re\)

不可做的,积累经验的题前加:\(\Im\)

完全是自己做的题前加:\(\bigstar\)

看了题解或讨论的题前加:\(\Game\)

总结前加:\(\color{red} \bigstar\)

AT/CF 前加:\(\Delta\)

时间是 2025.3.7 ~ 2025.11.29。

正文

3.15

\(\Re\) \(\Game\) CF388C Fox and Card Game

考虑一个基础的贪心。维护前缀未选过的最小值,往后碰到大的选即可。此时策略是不正确的,在决策时往堆里压入不在堆中的决策元素即可。

代码:

for(int i = 1 ; i <= n ; ++ i) {
	if(! q.empty() && a[i] > q.top()) {
		ans += a[i] - q.top();
		q.pop();
		q.push(a[i]);
	}

	q.push(a[i]);
}

\(\Re\) \(\Game\) CF388C Fox and Card Game

下文 Ciel 简称 C,Jiro 简称 R。

第一个难点是猜出对于偶数个数的一堆牌最终态一定是 C 选每一堆的前半,R 选每一堆的后半。

具体理解的话就是 C 和 R 每次会对于一个最大的数竞争,而这个数的归属一定是确定的。

对于奇数则在中间元素上 C / R 依次选即可。

3.21

\(\Re\) \(\Game\) CF3D Least Cost Bracket Sequence

不妨将 ( 权值设为 \(1\)) 设为 \(-1\)。不难发现这个权值的前缀和序列在一个合法的括号序列中是非负的且 \(pre_n = 0\)

将原字符串中的 ? 钦定为 ),维护 \(cnt\) 表示当前的权值。如果当前权值小于 \(0\),则先前某个 ? 所对的 ) 要改为 (。设改为 ( 的代价为 \(x\),改为 ) 的代价为 \(y\),那么由 ) 改为 ( 的代价为 \(x - y\)

此时维护以 \(x - y\) 为关键字的优先队列,每次需要反悔时选队首即可。

for(int i = 1 ; i <= n ; ++ i) {
	if(s[i] == '?') {
		cin >> x >> y;

		q.push({i, x - y});
		-- cnt;
		ans += y;
	}
	else if(s[i] == '(') ++ cnt;
	else -- cnt;

	if(cnt < 0) {
		if(q.empty()) return cout << -1, 0;

		cnt += 2;
		ans += q.top().val;
		Ans[q.top().pos] = '(';
		q.pop();
	}
}

3.23

\(\Delta\) ABC 398

智障场。

3.29

感觉最近怎么状态愈发下降了。

\(\Re\) \(\Game\) CF1251D Salary Changing

二分答案后贪心,对于原序列分三种情况讨论即可。

inline bool check(int x) {
    int res = 0, cnt1 = 0, cnt2 = 0;

    for(int i = 1 ; i <= n ; ++ i) {
        if(r[i] < x) ++ cnt1, res += l[i];
        else if(l[i] > x) ++ cnt2, res += l[i];
    }

    cnt1 = (n >> 1) - cnt1;
    cnt2 = (n >> 1) - cnt2;

    if(cnt1 < 0) return /*cerr << "cnt:" << cnt1 << ' ' << cnt2 << '\n', */false;

    for(int i = 1 ; i <= n ; ++ i) {
        if(l[id[i]] <= x && r[id[i]] >= x) {
            if(cnt1) {
                -- cnt1;
                
                res += l[id[i]];
            }
            else res += x;
        }
    }
    
    // cerr << "sub:" << n << ' ' << res << '\n';
    
    // if(cnt1 > 1 || cnt2 > 1 || (cnt1 == 1 && cnt2 == 1) || res > s) return false;

    if(res > s) return false;
    
    return true;
}

\(\Re\) \(\bigstar\) P4396 [AHOI2013] 作业

方法一:

莫队时用 BIT 维护,时间复杂度 \(O(n \sqrt n \log n)\)

需卡常。

record

方法二:

莫队时增 / 删一个点是 \(O(1)\) 的,考虑值域分块,查询是 \(O(\sqrt n)\) 的。总时间复杂度 \(O((Q + n) \sqrt n)\)

record

4.5

\(\Re\) \(\Game\) CF1369E DeadLee

题解

\(\Re\) \(\Game\) CF1367F2 Flying Sort (Hard Version)

题解

感觉这种 dp 还是得多练。

4.6

\(\Re\) \(\Game\) CF797E Array Queries

首先要读题。

其次,根号分治,小于阈值的预处理,否则暴力查询。

for(int i = n ; i ; -- i)
	for(int j = 1 ; j * j <= n ; ++ j) {
		if(i + a[i] + j <= n) ans[i][j] = ans[i + a[i] + j][j] + 1;
		else ans[i][j] = 1;
	}

cin >> Q;

while(Q --) {
	cin >> p >> k;

	if(k * k > n) {
		for(int i = 1 ; ; ++ i) {
			p += a[p] + k;

			if(p > n) {
				cout << i << '\n';

				break;
			}
		}
	}
	else cout << ans[p][k] << '\n';
}

4.7

\(\Re\) \(\bigstar\) CF1891C Smilo and Monsters

让我破防的 *1500,但是的确很简单,怎么写了那么久呢?

大力贪心,对于 \(l = r\) 的边界讨论即可。

注释可以极致地展现我的破防程度:

while(l <= r) {
	// cerr << "\npos:" << l << ' ' << r << '\n';
	// cerr << "add:" << min(a[l], a[r] - sum) << '\n';

	if(l == r) {
		// cerr << "result:" << sum << ' ' << a[r] << '\n';

		if(sum >= a[r]) ++ ans;
		else if(! sum) ans += min(a[r], (a[r] + 1) / 2 + 1);
		else ans += (a[r] + sum + 1) / 2 - sum + 1;

		break;
	}

	int x = a[l], y = sum;

	a[l] -= min(x, a[r] - y);
	ans += min(x, a[r] - y);
	sum += min(x, a[r] - y);
	// cerr << "a[l]:" << a[l] << '\n';
	// cerr << "sum:" << sum << '\n';
	// cerr << "a[l]\':" << a[l] << '\n';

	if(! a[l]) ++ l;

	if(sum == a[r]) {
		// cerr << "\nR:" << r << '\n';
		// cerr << "sum:" << sum << "\n\n";

		a[r] = 0;
		sum = 0;
		-- r;
		++ ans;
	}
	// cerr << "\nsum:" << sum << '\n';
	// cerr << "pos:" << l << ' ' << r << '\n';
	// cout << "ans:" << ans << '\n';
	// cout << "arr:\n";
	// for(int i = 1 ; i <= n ; ++ i)
	//     cout << a[i] << ' ';
	// cout << "\n";
}

// cerr << "arr:\n";
// for(int i = 1 ; i <= n ; ++ i)
//     cerr << a[i] << ' ';
// cerr << '\n';

cout << ans << '\n';
}

4.21

做 Div.2 C D。

\(\Re\) \(\Game\) CF1917C Watering an Array

破防了。

对于序列全 \(0\) 时答案只能为 \(1\)。先大力猜循环节后预处理合并即可。

\(\Re\) \(\Game\) CF1383B GameGame

不会简单博弈,怎么回事呢?

题解

5.6

\(\Re\) \(\Game\) P2340 [USACO03FALL] Cow Exhibition G

我应该深刻理解背包本质。

滚动数组需要深刻理解转移本质。正数倒着转,负数正着转。

#include <bits/stdc++.h>
#define int long long
using namespace std;

const int N = 405;
const int V = 1e3 + 5;
const int S = 8e5;
int n, m, s[N], f[N], dp[N * V * 2];

signed main() {
    ios_base :: sync_with_stdio(NULL);
    cin.tie(nullptr);
    cout.tie(nullptr);

    cin >> n;
    for(int i = 1 ; i <= n ; ++ i)
        cin >> s[i] >> f[i], m += s[i] * (s[i] > 0);

    memset(dp, ~ 0x3f, sizeof dp);

    dp[S >> 1] = 0;
    
    for(int i = 1 ; i <= n ; ++ i) {
        if(s[i] > 0) {
            for(int j = S ; j >= s[i] ; -- j)
                dp[j] = max(dp[j], dp[j - s[i]] + f[i]);
        }
        else {
            for(int j = 0 ; j <= S + s[i] ; ++ j)
                dp[j] = max(dp[j], dp[j - s[i]] + f[i]);
        }
    }

    int ans = 0;

    for(int i = S >> 1 ; i <= S ; ++ i)
        if(dp[i] > 0) ans = max(ans, i + dp[i] - (S >> 1));
    
    cout << ans;

    return 0;
}
posted @ 2025-03-07 16:24  endswitch  阅读(7)  评论(0)    收藏  举报