[每日随题3] 数学 - DP - 线段树、扫描线

整体概述

  • 难度:1000 -> 1600 -> 2200

1883C.Raspberries

  • 标签:数学

  • 前置知识:无

  • 难度:Div.3.C 1000

题目描述:

image

输入格式:

image

输出格式:

image

样例输入:

15
2 5
7 3
3 3
7 4 1
5 2
9 7 7 3 9
5 5
5 4 1 2 3
7 4
9 5 1 5 9 5 1
3 4
6 3 6
3 4
6 1 5
3 4
1 5 9
4 4
1 4 1 1
3 4
3 5 3
4 5
8 9 9 3
2 5
1 6
2 5
10 10
4 5
1 6 1 1
2 5
7 7

样例输出:

2
2
1
0
2
0
1
2
0
1
1
4
0
4
3

解题思路:

  • 要求所有数组内数字的乘积需要被 \(k\) 整除,且注意到 \(2\le k\le 5\) 很小。

  • \(k=2,3,5\) 时是质数,即我们使某个数字被 \(k\) 整除时答案最小。那么我们只需要找一个模 \(k\) 后最大的数字将其加到 \(k\) 即可。

    \(k=4\) 时,除了使某个数字能被 \(4\) 整除,我们还可以使某两个数字均能被 \(2\) 整除。那么我们统计有多少个模 \(4\)\(1\) 和余 \(2\) 的数字,计算需要操作几次。取两种方案中的最小操作数即可。

完整代码

#include<bits/stdc++.h>
#define Size(x) ((int)(x).size())
#define int long long
#define Min(x,y) (min((int)(x),(int)(y)))
using namespace std;
const int N = 1e5+5,INF = 0x3f3f3f3f;
int a[N];
inline int solve(){
	int n,k; cin >> n >> k;
	for(int i=1;i<=n;i++) cin >> a[i], a[i] %= k;
	int cnt_1 = 0, cnt_2 = 0, mn = INF;
	for(int i=1;i<=n;i++){ 
		if(a[i] == 0) return 0;
		if(a[i] == 1) cnt_1 += 1;
		if(a[i] == 2) cnt_2 += 1;
		mn = min(mn,k-a[i]);
	} 
	if(k == 4){ 
		if(cnt_2 >= 2) return 0;
		if(cnt_1 + cnt_2 >= 2) return Min(2-cnt_2,mn);
	} 
	return mn;
}
signed main(){
	ios::sync_with_stdio(0),cin.tie(0),cout.tie(0);
	int T; cin >> T;
	while(T--) cout << solve() << '\n';
	return 0;
}

1389B.Array Walk

  • 标签:线性DP

  • 前置知识:无

  • 难度:Div.2.B 1600

题目描述:

image

输入格式:

image

输出格式:

image

样例输入:

4
5 4 0
1 5 4 3 2
5 4 1
1 5 4 3 2
5 4 4
10 20 30 40 50
10 7 3
4 6 8 2 9 9 7 4 10 9

样例输出:

15
19
150
56

解题思路:

  • 我们发现能够向左走的次数 \(z\) 很小,仅仅 \(0\le z\le 5\),并且要求不能连续向左移动。那么我们的总状态数很小,只有 \(n*z*2\) 可以进行动态规划。

  • 我们定义 \(DP\) 数组 \(dp[i][j][k] = res\) 表示到达位置 \(i\),一共向左移动了 \(j\) 步,该位置是否由右侧移动而来,的状态下能获得的最高得分。其中 \(1\le i\le n\)\(0\le j\le 5\)\(k = 0,1\)

  • 那么 \(dp\) 的初始化为 dp_{i,0,0} = dp_{i-1,0,0} + a_i,即不使用向左移动。

    \(dp\) 的转移也比较简单:

    \[\left\{\begin{matrix} dp_{i,j,1} = dp_{i+1,j-1,0} + a_i \\ dp_{i,j,0} = max(dp_{i-1,j,0},\space dp_{i-1,j,1}) + a_i \end{matrix}\right. \]

  • 最后需要求步数恰好为 \(k\) 的答案中的最大值,那么我们只需要枚举可行的 \(z\),当向左 \(j\) 次,到达位置 \(i\) 时耗费了 \(i+2*j\) 步,即 \(0\le z\le \lfloor \frac{k}{2} \rfloor\)

  • 取所有可行的 \(z\) 中的最大值即可。

完整代码

#include<bits/stdc++.h>
#define Size(x) ((int)(x).size())
#define int long long
using namespace std;
const int N = 1e5+5;
int a[N],dp[N][6][2];	// 到i 向左了j次 上一步是否向左
inline void solve(){
	int n,k,z; cin >> n >> k >> z;
	for(int i=1;i<=n;i++) cin >> a[i];
	for(int i=1;i<=n;i++) dp[i][0][0] = dp[i-1][0][0] + a[i]; 
	for(int j=1;j<=z;j++){
		for(int i=1;i<=n;i++){
			if(i<n) dp[i][j][1] = dp[i+1][j-1][0] + a[i];
			if(i>1) dp[i][j][0] = max(dp[i-1][j][0],dp[i-1][j][1]) + a[i];
		}
	}
	int mx = 0;
	for(int j=0;j<=min(z,k/2);j++) mx = max(mx,max(dp[k+1-2*j][j][0],dp[k+1-2*j][j][1]));
	cout << mx << '\n';
	for(int i=0;i<=n;i++) for(int j=0;j<=z;j++) dp[i][j][0] = dp[i][j][1] = 0;
}
signed main(){
	ios::sync_with_stdio(0),cin.tie(0),cout.tie(0);
	int T; cin >> T;
	while(T--) solve();
	return 0;
}

1996G.Penacony

  • 标签:线段树,扫描线

  • 前置知识:STL-priority_queue

  • 难度:Div.3.G 2200

题目描述:

image

输入格式:

image

输出格式:

image

样例输入:

7
8 3
1 8
2 7
4 5
13 4
1 13
2 12
3 11
4 10
10 2
2 3
3 4
10 4
3 8
5 10
2 10
4 10
4 1
1 3
5 2
3 5
1 4
5 2
2 5
1 3

样例输出:

4
7
2
7
2
3
3

解题思路:

  • 由于在一个环上,我们发现每对 \(a_i\)\(b_i\) 之间存在两条路径很麻烦。同时我们发现至少可以删除一条多余的路,因为一个环删除一条路仍然是全联通的。

    同时由于删除了一条路,不存在环了,任意两点之间只有唯一的路径了,方便我们处理。

  • 那么我们暴力枚举必定会删除哪一条路,计算此时有哪些路必须要选。我们可以将两点需要连通看作从 \([a,b)\) 这些路径都被经过了一次,那么我们最后只需要统计有哪些路径没有被经过,即可以被删除。

  • 我们可以使用线段树来维护区间加法,区间查最小值的操作。同时我们发现每个点对 \(a_i\)\(b_i\) 只会带来固定几次区间操作,分别是 \(a_i\) 直接到 \(b_i\)\(a_i\) 反方向到 \(b_i\),而这个转化的时间在于直接到达的路径上是否被必定不选的那条路分隔开了。

  • 因此我们可以用扫描线技术,将点对按 \(a_i\) 从小到大排序,将 \(b_i\) 放入优先队列。在枚举切开位置的时候扫过一遍所有的点对,依次更新路径被切开的点对的贡献,随后再查询即可。

  • 时间复杂度为 \(O(n*log_2^n)\)

完整代码

#include<bits/stdc++.h>
#define Size(x) ((int)(x).size())
#define int long long
using namespace std;
const int N = 2e5+5;
int n,m;
struct INFO{
	int a,b;
	bool operator < (const INFO& y)const{
		return a < y.a;
	}
}rel[N];
class SegmentTree{
#define ls ((u)<<1)
#define rs ((u)<<1|1)
#define mid (((l)+(r))>>1)
public:
	int mn[N<<2], cnt[N<<2], tag[N<<2];
	inline void up(int u){
		if(mn[ls] == mn[rs]) mn[u] = mn[ls], cnt[u] = cnt[ls] + cnt[rs];
		else if(mn[ls] < mn[rs]) mn[u] = mn[ls], cnt[u] = cnt[ls];
		else mn[u] = mn[rs], cnt[u] = cnt[rs];
	}
	inline void lazy(int u,int val){
		mn[u] += val;
		tag[u] += val;
	}
	inline void down(int u){
		if(tag[u]){
			lazy(ls,tag[u]);
			lazy(rs,tag[u]);
			tag[u] = 0;
		}
	}
	inline void build(int l=1,int r=n,int u=1){
		tag[u] = 0;
		if(l == r){
			mn[u] = 0, cnt[u] = 1;
			return;
		}
		build(l,mid,ls),build(mid+1,r,rs);
		up(u);
	}
	inline void update(int x,int y,int val,int l=1,int r=n,int u=1){
		if(x <= l && r <= y){ lazy(u,val); return; }
		down(u);
		if(x <= mid) update(x,y,val,l,mid,ls);
		if(y > mid) update(x,y,val,mid+1,r,rs);
		up(u);
	}
}SG;
struct Node{
	int b,id;
	bool operator > (const Node&y) const{
		return b > y.b;
	}
};
priority_queue<Node,vector<Node>,greater<Node>> qu;
inline void solve(){
	while(!qu.empty()) qu.pop();
	cin >> n >> m;
	for(int i=1;i<=m;i++){
		cin >> rel[i].a >> rel[i].b;
		if(rel[i].a > rel[i].b) swap(rel[i].a,rel[i].b);
	}
	sort(rel+1,rel+1+m);
	SG.build(1,n,1);
	int l = 0;
	for(int i=1;i<=m;i++){
		if(rel[i].a == 1){
			l = i; qu.push({rel[i].b,i});	// 被切开的
			SG.update(rel[i].b,n,1);
		}else SG.update(rel[i].a,rel[i].b-1,1);
	}
	int res = n - SG.cnt[1];
	for(int i=2;i<=n;i++){
		while(l+1<=m && rel[l+1].a == i){ 
			l += 1, qu.push({rel[l].b,l});
			SG.update(rel[l].a,rel[l].b-1,-1);
			SG.update(rel[l].b,n,1);
			SG.update(1,rel[l].a-1,1);
		}
		while(!qu.empty() && qu.top().b == i){
			int id = qu.top().id; qu.pop();
			SG.update(rel[id].b,n,-1);
			if(rel[id].a>1) SG.update(1,rel[id].a-1,-1);
			SG.update(rel[id].a,rel[id].b-1,1);
		}
		int cur = n - SG.cnt[1];
		res = min(res,cur);
	}
	cout << res << '\n';
}
signed main(){
	ios::sync_with_stdio(0),cin.tie(0),cout.tie(0);
	int T; cin >> T;
	while(T--) solve();
	return 0;
}

posted @ 2025-07-11 01:22  浅叶梦缘  阅读(10)  评论(0)    收藏  举报