CSP2022

T1 假期计划

题目大意

  规定 d i s ( x , y ) dis(x, y) dis(x,y) 表示一张图上点 x x x y y y 的最短距离。

  给定一张无向图(图上所有边的边权都是 1 1 1,并且给定图上每一个点一个点权记为 v a l u e x value_x valuex),让你选定 4 4 4 个图上的点 a , b , c , d a, b, c, d a,b,c,d,使得 d i s ( 1 , a ) , d i s ( a , b ) , d i s ( b , c ) , d i s ( c , d ) dis(1, a), dis(a, b), dis(b, c), dis(c, d) dis(1,a),dis(a,b),dis(b,c),dis(c,d) d i s ( d , 1 ) dis(d, 1) dis(d,1) 都小于等于 k + 1 k + 1 k+1。并且要求 v a l u e a + v a l u e b + v a l u e c + v a l u e d value_a + value_b + value_c + value_d valuea+valueb+valuec+valued 最小。

分析

  首先发现点数比较小,甚至可以 O ( n 2 ) O(n^2) O(n2),所以我们可以直接对于每一个点 x x x 暴力 b f s ( x ) bfs(x) bfs(x) 求出 x x x 到其他节点的最短路径长度。

  然后如果考虑暴力,那么就可以直接枚举 4 4 4 个点然后判断能否符合条件(就是 d i s ( x , y ) dis(x, y) dis(x,y) 是否小于等于 k + 1 k + 1 k+1),就可以暴力统计答案了,但是这样复杂度 O ( n 4 ) O(n^4) O(n4) 显然爆炸,所以我们要考虑优化一下这个做法。

  我们考虑只枚举 b , c b, c b,c 这两个点,因为我们发现 a a a 点对于 b b b 点来说和 d d d 点对于 c c c 点来说约束条件是一样的,具体来说就是:

  1. 对于 a , b a, b a,b 来说, a a a 的约束条件是 d i s ( 1 , a ) ≤ k + 1 ∧ d i s ( a , b ) ≤ k + 1 dis(1, a) \le k + 1 \land dis(a, b) \le k + 1 dis(1,a)k+1dis(a,b)k+1
  2. 对于 d , c d, c d,c 来说, d d d 的约束条件是 d i s ( 1 , d ) ≤ k + 1 ∧ d i s ( d , c ) ≤ k + 1 dis(1, d) \le k + 1 \land dis(d, c) \le k + 1 dis(1,d)k+1dis(d,c)k+1

  这俩玩意儿是不是长得一模一样,所以我们考虑对于每一个点 x x x 预处理出另一个点 y y y 使得 y y y 满足 d i s ( 1 , y ) ≤ k + 1 ∧ d i s ( x , y ) ≤ k + 1 dis(1, y) \le k + 1 \land dis(x, y) \le k + 1 dis(1,y)k+1dis(x,y)k+1,且 v a l u e y value_y valuey 取到最大值,这里记录这样的 y y y f x f_x fx。这个预处理显然是 O ( n 2 ) O(n^2) O(n2) 的,然后再枚举点 b b b c c c,然后直接然后直接用 v a l u e b + v a l u e c + v a l u e f b + v a l u e f c value_b + value_c + value_{f_b} + value_{f_c} valueb+valuec+valuefb+valuefc 作为本次的答案,每次取 max ⁡ \max max 就好了。

  但是这样做会有一个问题,可能发现我们求到的 f b f_{b} fb f c f_c fc 是同一个点,这样就与题目意思不符了,所以我们还需要高出次大的点,记为 s b s_b sb s c s_c sc

  但是这样还是有一个问题,我们发现你有可能 f b = c f_b = c fb=c f c = b f_c = b fc=b 并且 s b = s c s_b = s_c sb=sc 这样就又不行了,所以我们需要再弄出第三大值 t b , t c t_b, t_c tb,tc,这样就一定是正确的了,具体的操作可以看代码。

代码

#include<bits/stdc++.h>
using namespace std;
#define in read()
#define MAXN 3030
#define MAXM 100100 
#define int long long
const int INFI = 1e18;

inline int read(){
	int x = 0; char c = getchar();
	while(c < '0' or c > '9') c = getchar();
	while('0' <= c and c <= '9')
		x = x * 10 + c - '0', c = getchar();
	return x;
}

int n = 0, m = 0, k = 0;
int tot = 0;
int first[MAXN] = { 0 };
int   nxt[MAXM] = { 0 };
int    to[MAXM] = { 0 };
int value[MAXN] = { 0 };
inline void add(int x, int y){
	nxt[++tot] = first[x];
	first[x] = tot, to[tot] = y;
}

int dis[MAXN][MAXN] = { 0 };
queue<int> q;
void bfs(int st){
	while(!q.empty()) q.pop();
	for(int i = 1; i <= n; i++) dis[st][i] = INFI;
	q.push(st); dis[st][st] = 0;
	while(!q.empty()){
		int x = q.front(); q.pop();
		for(int e = first[x]; e; e = nxt[e]){
			int y = to[e];
			if(dis[st][y] == INFI) dis[st][y] = dis[st][x] + 1, q.push(y);
		}
	}
}

int f[MAXN] = { 0 };                                 // 最大值 
int s[MAXN] = { 0 };                                 // 次大值
int t[MAXN] = { 0 };                                 // 第三大值 
void prework(){                                      // 对于每一个 c 选出最大的 d
	for(int c = 2; c <= n; c++)
		for(int d = 2; d <= n; d++){
			if(c == d) continue;
			if(dis[1][d] <= k and dis[d][c] <= k){
				if(value[d] > value[f[c]]) t[c] = s[c], s[c] = f[c], f[c] = d;
				else{
					if(value[d] > value[s[c]]) t[c] = s[c], s[c] = d;
					else if(value[d] > value[t[c]]) t[c] = d;
				}
			}
		}
}

inline bool can(int a, int b, int c, int d) { return a != d and a != c and d != b and a and d; }
void solve(){
	int ans = 0;
	for(int b = 2; b <= n; b++)
		for(int c = b + 1, a, d; c <= n; c++){
			if(dis[b][c] > k) continue;
			/* 这里懒得分类讨论,所以直接每都看可不可行然后取 max 作为答案 */
			a = f[b], d = f[c];
			if(can(a, b, c, d)) ans = max(ans, value[a] + value[b] + value[c] + value[d]);
			a = f[b], d = s[c];
			if(can(a, b, c, d)) ans = max(ans, value[a] + value[b] + value[c] + value[d]);
			a = f[b], d = t[c];
			if(can(a, b, c, d)) ans = max(ans, value[a] + value[b] + value[c] + value[d]);
			a = s[b], d = f[c];
			if(can(a, b, c, d)) ans = max(ans, value[a] + value[b] + value[c] + value[d]);
		    a = s[b], d = s[c];
			if(can(a, b, c, d)) ans = max(ans, value[a] + value[b] + value[c] + value[d]);
			a = s[b], d = t[c];
			if(can(a, b, c, d)) ans = max(ans, value[a] + value[b] + value[c] + value[d]);
			a = t[b], d = f[c];
			if(can(a, b, c, d)) ans = max(ans, value[a] + value[b] + value[c] + value[d]);
			a = t[b], d = s[c];
			if(can(a, b, c, d)) ans = max(ans, value[a] + value[b] + value[c] + value[d]);
			a = t[b], d = t[c];
			if(can(a, b, c, d)) ans = max(ans, value[a] + value[b] + value[c] + value[d]);
		}
	cout << ans << '\n';
}

signed main(){
//	freopen("a.in", "r", stdin);
	n = in, m = in, k = in; k++;
	for(int i = 2; i <= n; i++) value[i] = in;
	for(int i = 1; i <= m; i++){
		int x = in, y = in; 
		add(x, y), add(y, x);
	} 
	for(int i = 1; i <= n; i++) bfs(i);
	prework(); solve();
	return 0;
} 

T2

题目大意

  给定两个数组 a a a b b b,长度分别为 n , m n, m n,m,现在有 q q q 个询问,每次询问给出 l 1 , r 1 , l 2 , r 2 l_1, r_1, l_2, r_2 l1,r1,l2,r2,每次询问假定有两个人在进行博弈,甲需要在 a [ l 1 ∼ r 1 ] a[l_1 \sim r_1] a[l1r1] 中选出一个数字 x x x,乙需要在 b [ l 2 ∼ r 2 ] b[l_2\sim r_2] b[l2r2] 中选出一个数字 y y y ,最终得分就是 x y xy xy,甲的目的是让最终得分尽量大,乙则希望他尽量小,并且甲乙两个人都足够聪明,每次询问输出最后应该得出的答案。

分析

  粗略想一下应该是区间最值(线段树就好了),但是又有正负值之分,如果甲选了区间最大,并且最大是正值,乙选了区间最小,并且最小是负值,这样显然对甲不利,所以甲应该要选一个负数?再想想应该也不太对,因为如果甲选择负数的话乙就可以选一个最大的正数让答案最小。

  思考到这里发现好像分正负值分类讨论有一点困难,所以我们考虑分 16 16 16 种情况把所有的可能性都列举出来,具体来说也就是:

  1. a a a 中非负数的最小值 b b b 中非负数的最小值 的乘积
  2. a a a 中非负数的最小值 b b b 中非负数的最大值 的乘积
  3. a a a 中非负数的最小值 b b b 中非正数的最小值 的乘积
  4. a a a 中非负数的最小值 b b b 中非正数的最大值 的乘积
  5. a a a 中非负数的最大值 b b b 中非负数的最小值 的乘积
  6. a a a 中非负数的最大值 b b b 中非负数的最大值 的乘积
  7. a a a 中非负数的最大值 b b b 中非正数的最小值 的乘积
  8. a a a 中非负数的最大值 b b b 中非正数的最大值 的乘积
  9. a a a 中非正数的最小值 b b b 中非负数的最小值 的乘积
  10. a a a 中非正数的最小值 b b b 中非负数的最大值 的乘积
  11. a a a 中非正数的最小值 b b b 中非正数的最小值 的乘积
  12. a a a 中非正数的最小值 b b b 中非正数的最大值 的乘积
  13. a a a 中非正数的最大值 b b b 中非负数的最小值 的乘积
  14. a a a 中非正数的最大值 b b b 中非负数的最大值 的乘积
  15. a a a 中非正数的最大值 b b b 中非正数的最小值 的乘积
  16. a a a 中非正数的最大值 b b b 中非正数的最大值 的乘积

  这样就可以把所有的情况都考虑进去了。然后只需要在最后选出满足条件的答案就好了。

  现在问题又来了,怎么选出满足条件的答案呢?现在我们把这些情况先列出来一下看看(下面的 a p m i n apmin apmin 就表示 a a a 中非负数的最小值, b n m a x bnmax bnmax 就表示 b b b 中非正数的最大值):

apmin = x 1 x_1 x1apmax = x 2 x_2 x2anmin = x 3 x_3 x3anmax = x 4 x_4 x4
bpmin = y 1 y_1 y1 x 1 y 1 x_1y_1 x1y1 x 2 y 1 x_2y_1 x2y1 x 3 y 1 x_3y_1 x3y1 x 4 y 1 x_4y_1 x4y1
bpmax = y 2 y_2 y2 x 1 y 2 x_1y_2 x1y2 x 2 y 2 x_2y_2 x2y2 x 3 y 2 x_3y_2 x3y2 x 4 y 2 x_4y_2 x4y2
bnmin = y 3 y_3 y3 x 1 y 3 x_1y_3 x1y3 x 2 y 3 x_2y_3 x2y3 x 3 y 3 x_3y_3 x3y3 x 4 y 3 x_4y_3 x4y3
bnmax = y 4 y_4 y4 x 1 y 4 x_1y_4 x1y4 x 2 y 4 x_2y_4 x2y4 x 3 y 4 x_3y_4 x3y4 x 4 y 4 x_4y_4 x4y4

  我们先观察一列,发现这里的 x x x 都是一样的,所以我们只需要取出这一列中 x y i xy_i xyi 的最小值作为这一列的答案,再把这四列的答案取最大值得到最后的答案即可。

代码

#include<bits/stdc++.h>
using namespace std;
#define int long long
#define ls(p) (p << 1)
#define rs(p) (p << 1 | 1)
#define in read()
#define MAXN 100100
const int INFI = 1e18;

inline int read() {
	int x = 0, f = 0; char c = getchar();
	while(c < '0' or c > '9') {
		if(c == '-') f = 1; c = getchar();}
	while('0' <= c and c <= '9')
		x = x * 10 + c - '0', c = getchar();
	return f ? -x : x;
}

int a[MAXN] = { 0 };
int b[MAXN] = { 0 };
int n = 0, m = 0, q = 0;

struct SegmentTreemin{
	struct Tnode { int l, r, dat; };
	int arr[MAXN];
	Tnode t[MAXN << 2];
	inline void up(int p) { t[p].dat = min(t[ls(p)].dat, t[rs(p)].dat); }
	void build(int p, int l, int r){
		t[p].l = l, t[p].r = r;
		if(l == r) { t[p].dat = arr[l]; return; }
		int mid = l + r >> 1;
		build(ls(p), l, mid); build(rs(p), mid + 1, r);
		up(p);
	}
	int query(int p, int l, int r){
		if(l <= t[p].l and t[p].r <= r) return t[p].dat;
		int mid = t[p].l + t[p].r >> 1, ans = INFI;
		if(l <= mid) ans = min(ans, query(ls(p), l, r));
		if(r >  mid) ans = min(ans, query(rs(p), l, r));
		return ans;
	}
}Tapmin, Tanmin, Tbpmin, Tbnmin;

struct SegmentTreemax{
	struct Tnode { int l, r, dat; };
	int arr[MAXN];
	Tnode t[MAXN << 2];
	inline void up(int p) { t[p].dat = max(t[ls(p)].dat, t[rs(p)].dat); }
	void build(int p, int l, int r){
		t[p].l = l, t[p].r = r;
		if(l == r) { t[p].dat = arr[l]; return; }
		int mid = l + r >> 1;
		build(ls(p), l, mid); build(rs(p), mid + 1, r);
		up(p);
	}
	int query(int p, int l, int r){
		if(l <= t[p].l and t[p].r <= r) return t[p].dat;
		int mid = t[p].l + t[p].r >> 1, ans = -INFI;
		if(l <= mid) ans = max(ans, query(ls(p), l, r));
		if(r >  mid) ans = max(ans, query(rs(p), l, r));
		return ans;
	}
}Tapmax, Tanmax, Tbpmax, Tbnmax;

void init(){
	for(int i = 1; i <= max(n, m); i++) 
		Tapmin.arr[i] = Tanmin.arr[i] = Tbpmin.arr[i] = Tbnmin.arr[i] = INFI;
	for(int i = 1; i <= max(n, m); i++) 
		Tapmax.arr[i] = Tanmax.arr[i] = Tbpmax.arr[i] = Tbnmax.arr[i] = -INFI;
	for(int i = 1; i <= n; i++){
		if(a[i] >= 0) Tapmin.arr[i] = Tapmax.arr[i] = a[i];             // 非负数
		if(a[i] <= 0) Tanmin.arr[i] = Tanmax.arr[i] = a[i];             // 非正数
	}
	for(int i = 1; i <= m; i++){
		if(b[i] >= 0) Tbpmin.arr[i] = Tbpmax.arr[i] = b[i];             // 非负数
		if(b[i] <= 0) Tbnmin.arr[i] = Tbnmax.arr[i] = b[i];             // 非正数
	}
	Tapmin.build(1, 1, n), Tapmax.build(1, 1, n), Tanmin.build(1, 1, n), Tanmax.build(1, 1, n);
	Tbpmin.build(1, 1, m), Tbpmax.build(1, 1, m), Tbnmin.build(1, 1, m), Tbnmax.build(1, 1, m);
}

int solve(int x, int l2, int r2){
	int y = INFI;
	int t1 = Tbpmin.query(1, l2, r2);                                   // b+min
	int t2 = Tbpmax.query(1, l2, r2);                                   // b+max
	int t3 = Tbnmin.query(1, l2, r2);                                   // b-min
	int t4 = Tbnmax.query(1, l2, r2);                                   // b-max
	if(abs(t1) != INFI) y = min(y, x * t1); 
	if(abs(t2) != INFI) y = min(y, x * t2);
	if(abs(t3) != INFI) y = min(y, x * t3);
	if(abs(t4) != INFI) y = min(y, x * t4);
	return y;
}

int solve(int l1, int r1, int l2, int r2){
	int x = 0, y = INFI, ans = -INFI;
	x = Tapmin.query(1, l1, r1);
	if(abs(x) != INFI) ans = max(ans, solve(x, l2, r2));
	x = Tapmax.query(1, l1, r1);
	if(abs(x) != INFI) ans = max(ans, solve(x, l2, r2));
	x = Tanmin.query(1, l1, r1);
	if(abs(x) != INFI) ans = max(ans, solve(x, l2, r2));
	x = Tanmax.query(1, l1, r1);
	if(abs(x) != INFI) ans = max(ans, solve(x, l2, r2));
	return ans;
}

signed main(){
	n = in, m = in, q = in;
	for(int i = 1; i <= n; i++) a[i] = in;
	for(int i = 1; i <= m; i++) b[i] = in;
	init();
	 while(q--){
	 	int l1 = in, r1 = in, l2 = in, r2 = in;
	 	cout << solve(l1, r1, l2, r2) << '\n';
	 }
	return 0;
} 

T3

T4

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值