W
e
l
c
o
m
e
: )

[考试记录] 2024.7.5

T1 酸碱度中和

题目描述

小明有 \(n\) 瓶生理盐水,由于浓度不太一样, 以及混进来了一些奇怪的东西,第𝑖i瓶生理盐水的酸碱度是 \(a_i\)

小明觉得 \(n\) 个瓶子太多了,于是他决定把这 \(n\) 瓶盐水重新灌装进 \(k\) 个瓶子中。

把若干瓶盐水混到一起的前提条件是:每一瓶盐水的酸碱度是一样的。

这显然太困难了,所以小明准备去哆啦A梦的杂货铺购买道具“酸碱度修改器”。

“酸碱度修改器”有一个属性值 \(m\) ,当你使用它在某一瓶盐水上的时候,可以把这瓶盐水的酸碱度增加/减少最多 \(m\)。比如你有一个属性为 \(3\) 的“酸碱度修改器”,那么你可以把原来酸碱度为 \(4\) 的生理盐水的酸碱度修改为 \(1\),\(2\),\(3\),\(4\),\(5\),\(6\),\(7\) 中的任何一个值。

“酸碱度修改器”可以重复使用。但是,对于每一瓶生理盐水来说只能使用一次。

属性值 \(m\) 越大的“酸碱度修改器”越贵,因此,小明决定购买 \(m\) 尽量小的,请帮助小明算一算,他最少要买属性为多少的“酸碱度修改器”。

输入格式

第一行输入 \(n\),\(k\)

接下来一行输入 \(n\) 个正整数表示 \(a_i\)

输出格式

一个数字表示答案。

输入数据 1

4 2
1 3 5 7

输出数据 1

1

输入数据 2

4 1
1 3 5 7

输出数据 2

3

数据范围

对于30%的数据:\(𝑛≤20\)

对于50%的数据:\(n≤500\)

对于另外20%的数据:\(k=2\)

对于100%的数据:\(𝑛≤10^5,1≤𝑎𝑖≤10^9\)。 全部数据 \(k<=50000\)

解析

本场考试最大的失误,看到 \(1e5\) 没往二分那里想,先考虑的 DP,然后发现不可做,于是打暴力拿部分分。

正解 二分答案。可以先把序列从小到大排序,可以发现修改器的最大值即为 $\left \lceil \frac{s}{2} \right \rceil $,其中 \(S=a[n]-a[1]\) 代表极差。由于随着修改器数值的减小,整个序列分成的段数就越多,满足单调性。那么可以二分这个修改器的属性,看能否把序列分成 \(k\)​ 段即可。

#include<bits/stdc++.h>
using namespace std;
constexpr int N = 1e5 + 1;
int n, k, a[N];
inline bool check(int t){
	int cnt = 1, lst = 1, i = 1;
	while(i <= n)
		if(a[i] - a[lst] > t) ++cnt, lst = i;
		else ++i;
	return cnt<=k;
}
int main(){
	ios::sync_with_stdio(0), cin.tie(0), cout.tie(0);
	cin>>n>>k; if(k >= n) return cout<<'0', 0; 
	for(int i=1; i<=n; ++i) cin>>a[i];
	sort(a+1, a+1+n);
	int l = 0, r = a[n]-a[1]+1;
	while(l < r){
		int mid = (l + r) >> 1;
		if(check(mid)) r = mid;
		else l = mid+1;
	} return cout<<(l+1>>1), 0;
}

T2 聪明的小明

题目描述

小明开了个酒厂,他的酒厂里面会出产 \(k\) 种酒。

有一天,市长要来他的酒厂视察,他可太高兴了。

为了应对这次视察,他决定在一个陈列长廊上摆放 \(n\) 瓶酒。市长走过这个长廊的时候就会看到每一瓶酒。

通过市长秘书处的打听,小明得到了一个重要消息:市长将会在考察完毕后,从长廊里面连续的取走 \(m\) 瓶酒作为纪念。

为了让市长带走的酒里,一定包含酒厂中产出的每一种酒,小明决定仔细研究这 \(n\) 瓶酒具体来摆放哪些酒。

请帮助小明算一算,他有多少种摆放酒的方案吧!

输入格式

第一行三个正整数 \(n\), \(k\) ,\(m\),如题所述。

输出格式

输出答案 \(\bmod 998244353\)

样例 #1

样例输入 #1

4 2 3

样例输出 #1

10

提示

【样例 1 解释】

一共1010种方案:

[1121],[1122],[1211],[1212],[1221],[2212],[2211],[2122],[2121],[2112]

样例输入 #2

10 4 6

样例输出 #2

81552

样例输入 #3

100000 7 10

样例输出 #3

77680521

数据范围

对于25%的数据:\(n≤20,k=2\)

对于另5%的数据:\(k=m\)

对于另20%的数据:\(k=2\)

对于另20%的数据:\(𝑚≤5\)

对于100%的数据:\(𝑚≤𝑛≤10^5,1≤𝑘≤𝑚≤10\)

解析

看到时一眼组合题,然后发现不可做。于是又打暴力大部分分。

部分分

其实当 \(k=m\) 时,总方案数为 \(k!\)

\(k=2\) 时,可用容斥原理求解,方案数为 \(2^n+\sum_\limits{i=1}^{n-m-2}2^{n-m+2-i}\times (n-m+2-i)\times (-1)^i\) (来自 dyk 大佬的推理,我不会)。

正解

对,没错,状压 DP。考虑其中一段 \(m\) 序列,假设 \(m=5\) , \(k=3\),于是有 \(11223\)\(12333\) 等等。可以发现 \(11223\)\(22113\)\(11332\) 的贡献是一样的,于是就可以只记录每种酒最后出现的位置 \(*1*23\),进一步 \(*1*23\)\(*2*13\) 是一样的,于是可简化为 01串 \(01011\)。看一眼范围 \(m\le 10\) 所以总共有 1<<10种状态,状态合法的条件是状态中 \(1\) 的数量等于 \(k\),并且最高位一定为 \(1\)。然后计算每种状态对应的 dp[m][s]。接下来就是递推操作。可以从初始状态 dp[m][s] 开始递推,对于每一次转移,考虑这个 01 串中哪一个 \(1\) 被替换到前面即可。例如 \(1100\) 可以从 \(1001\)\(1010\)\(1100\) 转移而来。复杂度 \(O(n2^m)\)

最后滚动数组优化。

#include<bits/stdc++.h>
using namespace std;
#define lb(x) (x&-x)
constexpr int M = 998244353, N = 1e5 + 1;
int n, k, m, dp[2][(1<<10)+1], ans, x=1, y;
vector<int> base, G[(1<<10)+1];
inline int get(int t){
	int ans = 1;
	for(int i=0, num=k; i<m; ++i){
		ans = (__int128)ans * num % M;
		if(t&(1<<i)) --num;
	} return ans;
}
int main(){
	ios::sync_with_stdio(0), cout.tie(0), cin.tie(0);
	cin>>n>>k>>m;
	for(int i=1<<(m-1); i<(1<<m); ++i){
		if(__builtin_popcount(i) == k){
			base.push_back(i);
			dp[x][i] = get(i);
		}
	}
	for(int a : base) for(int b : base){
		for(int i=1, t=b; i<=k; ++i, t-=lb(t)){
			if((b-lb(t)+(1<<m))>>1 == a) {
				G[a].push_back(b);
				break;
			}
		}
	}
	for(int i=m+1; i<=n; ++i){
		x ^= 1, y ^= 1;
		for(int a : base) { dp[x][a] = 0;
			for(int b : G[a]) dp[x][a] = (__int128)(dp[x][a] + dp[y][b]) % M;
			if(i == n) ans = (__int128)(ans + dp[x][a]) % M;
		}
	}
	return cout<<ans, 0;
}

T3 线段树

解析

#include<bits/stdc++.h>
using namespace std;
constexpr int N = 1e5 + 1;
int n, q, dp[501][501], m[510][510];
int main(){
	ios::sync_with_stdio(0), cin.tie(0), cout.tie(0);
	cin>>n>>q;
	for(int i=1, l, r; i<=q; ++i) cin>>l>>r, ++m[l][1], --m[l][r+1];
	for(int j=1; j<=n; ++j) for(int i=1; i<=n; ++i) m[i][j] += m[i-1][j] + m[i][j-1] - m[i-1][j-1]; 
	for(int i=1; i<=n; ++i) dp[i][i] = m[i][i];
	for(int i=2; i<=n; ++i) for(int j=i-1; j>=1; --j){
		dp[j][i] = INT_MAX;
		for(int k=i-1; k>=j; --k)
			dp[j][i] = min(dp[j][i], dp[j][k] + dp[k+1][i] - m[j][i]);
	} return cout<<dp[1][n], 0;
}

T4 公路 (CSP-J 2023)

题目描述

小苞准备开着车沿着公路自驾。

公路上一共有 \(n\) 个站点,编号为从 \(1\)\(n\)。其中站点 \(i\) 与站点 \(i + 1\) 的距离为 \(v_i\) 公里。

公路上每个站点都可以加油,编号为 \(i\) 的站点一升油的价格为 \(a_i\) 元,且每个站点只出售整数升的油。

小苞想从站点 \(1\) 开车到站点 \(n\),一开始小苞在站点 \(1\) 且车的油箱是空的。已知车的油箱足够大,可以装下任意多的油,且每升油可以让车前进 \(d\) 公里。问小苞从站点 \(1\) 开到站点 \(n\),至少要花多少钱加油?

输入格式

输入的第一行包含两个正整数 \(n\)\(d\),分别表示公路上站点的数量和车每升油可以前进的距离。

输入的第二行包含 \(n - 1\) 个正整数 \(v_1, v_2\dots v_{n-1}\),分别表示站点间的距离。

输入的第三行包含 \(n\) 个正整数 \(a_1, a_2 \dots a_n\),分别表示在不同站点加油的价格。

输出格式

输出一行,仅包含一个正整数,表示从站点 \(1\) 开到站点 \(n\),小苞至少要花多少钱加油。

样例 #1

样例输入 #1

5 4
10 10 10 10
9 8 9 6 5

样例输出 #1

79

提示

【样例 1 解释】

最优方案下:小苞在站点 \(1\) 买了 \(3\) 升油,在站点 \(2\) 购买了 \(5\) 升油,在站点 \(4\) 购买了 \(2\) 升油。

【样例 2】

见选手目录下的 road/road2.in 与 road/road2.ans。

【数据范围】

对于所有测试数据保证:\(1 \leq n \leq 10^5\)\(1 \leq d \leq 10^5\)\(1 \leq v_i \leq 10^5\)\(1 \leq a_i \leq 10^5\)

测试点 \(n \leq\) 特殊性质
\(1\sim 5\) \(8\)
\(6\sim 10\) \(10^3\)
\(11\sim 13\) \(10^5\) A
\(14\sim 16\) \(10^5\) B
\(17\sim 20\) \(10^5\)
  • 特殊性质 A:站点 \(1\) 的油价最低。
  • 特殊性质 B:对于所有 \(1 \leq i < n\)\(v_i\)\(d\) 的倍数。

解析

没想到我竟然推了个 DP,还是个看上去能斜率优化的,但是斜率递增,x 坐标不递增,而且没有考虑到加满后走了一段还剩下的情况。

正解

贪心。找到下一个比当前节点价格低的节点:

  1. 油箱剩下的油能走到,continue
  2. 加满油后一定能走到,只加走到那里需要的油。
  3. 加满油走不到,直接加满即可。
#include<bits/stdc++.h>
using namespace std;
#define int long long
constexpr int N = 1e5 + 1;
int n, c, sum[N], v[N], ans, cn;
signed main(){
	ios::sync_with_stdio(0), cin.tie(0), cout.tie(0);
	cin>>n>>c;
	for(int i=1, s; i<=n; ++i) cin>>s, sum[i] = sum[i-1] + s;
	for(int i=0; i<n; ++i) cin>>v[i];
	for(int i=0; i<=n; ++i){
		if(i) cn -= sum[i] - sum[i-1];
		if(i == n) break;
		int nxt = i+1; while(v[nxt] > v[i]) ++nxt;
		if(sum[nxt] - sum[i] <= cn) continue;
		else if(sum[nxt] - sum[i] <= c) ans += (sum[nxt]-sum[i]-cn)*v[i], cn = sum[nxt]-sum[i];
		else ans += (c-cn)*v[i], cn = c;
	} return cout<<ans, 0;
}
posted @ 2024-07-05 22:27  XiaoLe_MC  阅读(23)  评论(0编辑  收藏  举报