2021-2022 ICPC Southwestern Europe Regional Contest

C

LIS典

一个人在数轴上移动,最大速度为vvv,也就是每秒最多移动vvv个刻度。ttt时刻需要赶到xxx位置才能观察到一个画面,问初始000时刻从000位置出发,最多能观察到多少个画面?

把一个画面看成一个点(t,x)(t,x)(t,x),要能看到这个画面,假设当前看到了第iii个时刻,要观测到画面jjj,则需要满足∣xj−xi∣<=(tj−ti)∗v|x_j-x_i|<=(t_j-t_i)*v∣xj−xi∣<=(tj−ti)∗v

可以把绝对值打开,完全等价于:
xj−tj∗v≤xi−ti∗vx_j-t_j*v \le x_i-t_i*vxj−tj∗v≤xi−ti∗v且
xi+ti∗v≤xj+tj∗vx_i+t_i*v \le x_j+t_j*vxi+ti∗v≤xj+tj∗v

记pi=xi−ti∗v,qi=xi+ti∗vp_i=x_i-t_i*v,q_i=x_i+t_i*vpi=xi−ti∗v,qi=xi+ti∗v

则问题转化为统计满足pi≥pj,qi≤qjp_i \ge p_j,q_i \le q_jpi≥pj,qi≤qj的点对个数,ppp的顺序要求可以排序解决,qqq则在排序后的序列上跑LISLISLIS。也就是对ppp为第一关键字降序,qqq为第二关键字升序排序,然后只看qqq,跑LISLISLIS。

这里qqq范围很大,考虑用二分LISLISLIS,不用离散化或者动态开点。并且需要注意的是,初始我们位于(0,0)(0,0)(0,0),所以LISLISLIS方案中的所有点都要是(0,0)(0,0)(0,0)能到的,也就是排序前,筛掉跟原点不满足前述两个式子的所有点。

二分贪心LISLISLIS的思路是,维护一个序列,aia_iai表示长度iii的LISLISLIS的结尾元素的最小值。维护最小值是因为,同样长的LISLISLIS,结尾越小,后面越容易接上一个元素,形成更长的LISLISLIS。新来一个元素xxx,在序列aaa上二分,(显然a是单调的),找到第一个大于xxx位置,这个位置对应的LISLISLIS的结尾可以替换成xxx,会更优。如果找不到,说明这个元素比目前所有LISLISLIS的结尾的最小值都要大,也就是可以接到所有长度的LISLISLIS的后面,那么未来使得LISLISLIS长度最大化,接到目前最长的LISLISLIS后面,也就是直接接到aaa数组结尾。

c 复制代码
void solve() {
	int n, v;
	cin >> n >> v;

	vi t(n + 1), a(n + 1);
	rep(i, 1, n) {
		cin >> t[i];
	}

	rep(i, 1, n) {
		cin >> a[i];
	}

	vector<pii>b;
	rep(i, 1, n) {
		int p = a[i] - t[i] * v;
		int q = a[i] + t[i] * v;
		if (p <= 0 && q >= 0)b.push_back({p, q});
	}

	sort(b.begin(), b.end(), [](auto & x, auto & y) {
		if (x.fi != y.fi)return x.fi > y.fi;
		return  x.se < y.se;
	});

	vi c;
	for (auto [p, q] : b) {
		auto it = upper_bound(c.begin(), c.end(), q);
		if (it == c.end()) {
			c.push_back(q);
		} else {
			*it = q;
		}
	}

	cout << c.size() << '\n';




}

D

转化 滑窗

数轴上从0开始,每隔100米有一个房子,房子里有人,同时数轴上还散落一些商店,每个人都会去离他严格最近的商店。现在要新建一个商店,商店位置可以是浮点,问优化位置后,能吸引到的最大人数?

如果建在第一个商店左侧,可以把左侧所有人都吸引到,但也只能吸引左侧的所有人。如果建在最后一个商店右侧,同理。

如果在某两个商店中间,把两个商店中间的区间分成两段,这两段里,能吸引到的范围都是这两段的一半,这里有一个绝妙的观察,由于能吸引到两个区间的各一半,能吸引到的总区间长度就是两个商店距离的一半!

于是问题转化为,在两个商店中间跑一个长度为两个商店距离一半的滑窗,使得窗口内的人数最大。枚举所有两个窗口形成的区间,做上述滑窗。

剩下的就是一点实现难度,因为对于两个商店,需要先定位商店中间的房子对应的区间,再在这个区间上滑窗。

c 复制代码
#include <cstdio>
#include <algorithm>
using namespace std;

int n, m;
int a[200005]; 
int p[200005];
long long s[200005], ans;


int lst(long long x) {
    if (x < 0) return 0;
    return min(x / 100 + 1, (long long)n);
}


int nxt(long long x) {
    if (x > (n - 1) * 100) return n + 1;
    return max((x - 1) / 100 + 2, 1LL);
}

int main() {
    if (scanf("%d%d", &n, &m) != 2) return 0;
    for (int i = 1; i <= n; i++)
        scanf("%d", &a[i]);
    for (int i = 1; i <= m; i++)
        scanf("%d", &p[i]);
        
    sort(p + 1, p + m + 1);
    

    for (int i = 1; i <= n; i++)
        s[i] = s[i - 1] + a[i];
        

    ans = max(s[lst(p[1] - 1)], s[n] - s[nxt(p[m] + 1) - 1]);
    

    for (int i = 1; i <= m - 1; i++) {
        int l = nxt(p[i] + 1); 
        int r = lst(p[i + 1] - 1); 
        if (l > r) continue; 
        
        long long len= p[i + 1] - p[i]; 
        
        int right = l;
        for (int left = l; left <= r; left++) {

            while (right <= r && (long long)(right - left) * 200 < len) {
                right++;
            }
            ans = max(ans, s[right - 1] - s[left - 1]);
        }
    }
    
    printf("%lld\n", ans);
    return 0;
}

E

思维 最小表示法

一个ABC组成的字符串s,可以随意增加或删除:AA,BB,CC,ABAB,BCBC,问能否变成另一个串t

一个比较自然的想法是,这种能否变成另一个串,不一定要从当前串出发变成另一个,操作一般是可逆的,于是可以检查两个串能否都变成某个串,也就是规定一个最小表示,看两个串转化成最小表示后是否相同。

前面比较自然,后面就需要一些观察了:

  • 首先,能删除AA,BB,CC意味着相同元素可以消掉;
  • 其次,能增加ABAB,意味着AB可以变成BC,具体操作是,AB中间加上一个ABAB,变成AABABB,然后把两侧的AA,BB消掉,就只剩BA了,BCBC同理,意味着BC可以变CB。
  • 第二点也意味着,B可以随意在AC序列上移动,但是AC的相对位置不动,于是可以考虑把所有B都移动到开头(结尾也行,这是只是规定一种规则),然后把所有能消掉的都消掉,这样可以视为得到了一个串的最小表示,接下来对比两个串的最小表示是否相同即可

实现上,先统计B有多少个,接下来忽略B,认为已经把B提到最开头了,然后把剩下的AC,用栈实现消除,检查消除后的AC串是否相同,开头的B是否相同,只要比较B个数的奇偶。这里可以用string模拟栈,反正可以实现压入队尾,弹出队尾就行,还可以直接比较是否相等。

c 复制代码
void solve() {
	string a, b;
	cin >> a >> b;

	int c1 = 0, c2 = 0;

	string p, q;
	for (char c : a) {
		if (c == 'B') {
			c1++;
		} else {
			if (p.size() && p.back() == c) {
				p.pop_back();
			} else {
				p.push_back(c);
			}
		}
	}

	for (char c : b) {
		if (c == 'B') {
			c2++;
		} else {
			if (q.size() && q.back() == c) {
				q.pop_back();
			} else {
				q.push_back(c);
			}
		}
	}
	

	if (c1 % 2 == c2 % 2 && p == q) {
		cout << "YES\n";
	} else {
		cout << "NO\n";
	}

}

J

数论 搜索

一个h∗wh*wh∗w的地面,现在要铺最外面一圈的瓷砖,也就是内部(h−2)∗(w−2)(h-2)*(w-2)(h−2)∗(w−2)都铺好了,剩下外面一圈。只能使用(1,a)(1,a)(1,a)的瓷砖,可以旋转九十度,问有多少种可行的aaa?

铺外面一圈唯一的问题是,四个顶点应该分别属于哪一条边。由于情况不多,只有24=162^4=1624=16种,考虑枚举每一种情况,对于每一种情况下,四条边的长度都是确定的,要使用(1,a)(1,a)(1,a)拼出这四条边,aaa就是四条边长度的gcdgcdgcd的任意因子。

枚举考虑使用dfsdfsdfs,四层forforfor不好写,搜索的流程就是初始假设四个变分别是都不含顶点的,也就是w−2,w−2,h−2,h−2w-2,w-2,h-2,h-2w−2,w−2,h−2,h−2,然后对于四个顶点分别枚举归属于相邻的两条边中的哪个。对于一个方案,计算出gcdgcdgcd,计算出所有因子,这些因子要去重,放到setsetset里统计

c 复制代码
int a[4];
set<int>ans;
void dfs(int i) {
	if (i == 4) {
		int g = 0;
		rep(i, 0, 3) {
			g = gcd(a[i], g);
		}
		for (int i = 1; i * i <= g; i++) {
			if (g % i == 0) {
				ans.insert(i);
				if (i != g / i) {
					ans.insert(g / i);
				}
			}
		}
		return;
	}

	a[i]++;
	dfs(i + 1);
	a[i]--;

	a[(i + 1) % 4]++;
	dfs(i + 1);
	a[(i + 1) % 4]--;
}
void solve() {
	int w, l;
	cin >> w >> l;

	a[0] = a[2] = w - 2;
	a[1] = a[3] = l - 2;
	ans.clear();
	dfs(0);

	cout << ans.size() << ' ';
	for (int x : ans) {
		cout << x << ' ';
	}
	cout << '\n';
}

L

BFS 建图

墙要么是一个圆弧,要么是一个直线,直线给出所在的角度,和跨越的半径长度,圆弧给出半径和跨越的角度。形成一个迷宫,问能否从最内层走到最外层?

如图,第一个能出去,第二个不能

题意很抽象,感觉可以BFS,只要建出图就结束了,关键在于建图。规定半径r,r+1r,r+1r,r+1的圆中间的角度为ccc的扇区,把它当成图上的点,编号为r∗360+cr*360+cr∗360+c

那么一个点初始和周围四个扇区联通,也就是(r−1)∗360+c,(r+1)∗360+c,r∗360+c+1,r∗360+c−1(r-1)*360+c,(r+1)*360+c,r*360+c+1,r*360+c-1(r−1)∗360+c,(r+1)∗360+c,r∗360+c+1,r∗360+c−1

但是这些墙阻断了一些边,记录哪些边被阻断了,最后从最内部的扇区[0,359][0,359][0,359]出发BFS即可,看能否到达最大rrr外侧的那些扇区。

c 复制代码
void solve() {
	int n;
	cin >> n;

	int mxr = 0;

	vvi vis(30 * 400, vi(30 * 400));
	rep(i, 1, n) {
		char c;
		cin >> c;
		if (c == 'C') {
			int r, c1, c2;
			cin >> r >> c1 >> c2;
			mxr = max(mxr, r);

			for (int j = c1; j != c2; j = (j + 1) % 360) {
				vis[(r - 1) * 360 + j][r * 360 + j] = vis[r * 360 + j][(r - 1) * 360 + j] = 1;
			}
		} else {
			int r1, r2, c;
			cin >> r1 >> r2 >> c;

			int c1 = (c - 1 + 360) % 360;
			rep(j, r1, r2 - 1) {
				vis[j * 360 + c1][j * 360 + c] = vis[j * 360 + c][j * 360 + c1] = 1;
			}
		}
	}
	vi a(30 * 400);
	queue<int>q;
	rep(i, 0, 359) {
		q.push(i);
		a[i] = 1;
	}


	while (q.size()) {
		int u = q.front();
		q.pop();

		int r = u / 360;
		int c = u % 360;
		vi nxt = {(r - 1) * 360 + c, (r + 1) * 360 + c, r * 360 + (c + 1) % 360, r * 360 + (c - 1 + 360) % 360};

		for (int v : nxt) {
			if (v < 0 || v >= (mxr + 1) * 360 || vis[u][v])continue;
			if (a[v])continue;
			a[v] = 1;
			q.push(v);
		}
	}


	rep(i, 0, 359) {
		if (a[mxr * 360 + i]) {
			cout << "YES\n";
			return;
		}
	}

	cout << "NO\n";


}
相关推荐
罗湖老棍子5 小时前
The xor-longest Path(信息学奥赛一本通- P1478)
算法·字符串·字典树··lca最近公共祖先
whuhewei5 小时前
React diff算法为什么是DFS,不是BFS
算法·react.js·深度优先
EdmundXjs6 小时前
大模型核心概念解读
人工智能·算法
lookaroundd6 小时前
llm-compressor 普通量化调用链分析
python·算法
小羊在睡觉6 小时前
力扣239. 滑动窗口最大值
数据结构·后端·算法·leetcode·go
兰令水6 小时前
topcode【随机算法题】【2026.5.20打卡-java版本】
java·开发语言·算法
此生决int7 小时前
算法从入门到精通——前缀和
c++·算法·蓝桥杯
大大杰哥7 小时前
leetcode hot100(4)矩阵
算法·leetcode·矩阵
小白|7 小时前
cmake:昇腾CANN构建系统完全指南
java·c++·算法