2022.5.14模拟赛

  第一次 r a n k 1 rank1 rank1 有点激动 q w q qwq qwq

T1 query

  签到题,求区间等于 x x x 的数的个数。考虑树状数组,对于一个区间 [ L , R ] [L, R] [L,R] 考虑算出小于等于 L − 1 L - 1 L1 中等于 x x x 的数的个数记为 a a a,和小于等于 R R R 中的等于 x x x 的数的个数记为 b b b 然后答案就是 a n s = b − a ans = b - a ans=ba。然后我们又发现等于 x x x 的数的个数等于小于等于 x x x 的数的个数减去小于等于 x − 1 x - 1 x1 的数的个数。然后就跑两遍一维数点就过了。

#include<bits/stdc++.h>
using namespace std;
#define in read()
#define MAXN 200200	

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 a[MAXN] = { 0 };
int n = 0; int q = 0;
int c[MAXN * 4] = { 0 };

struct Tq{
	int l, r, x;
	int ans, idx;
}qs[MAXN];
bool comp1(Tq a, Tq b) { return a.l < b.l; }
bool comp2(Tq a, Tq b) { return a.r < b.r; }
bool comp3(Tq a, Tq b) { return a.idx < b.idx; }

inline int lowbit(int x) { return x & -x; }
void add(int x){
	for(; x <= n; x += lowbit(x)) c[x]++;
}
int query(int x){
	int ans = 0;
	for(; x; x -= lowbit(x)) ans += c[x];
	return ans; 
}

int main(){
	freopen("query.in", "r", stdin);
	freopen("query.out", "w", stdout);
	n = in;
	for(int i = 1; i <= n; i++) a[i] = in;
	q = in;
	for(int i = 1; i <= q; i++)
		qs[i].l = in, qs[i].r =in, qs[i].x = in, qs[i].idx = i;
	int j = 1; sort(qs + 1, qs + q + 1, comp1);
	for(int i = 1; i <= n; i++){
		add(a[i]);
		while(qs[j].l < i + 1 and j <= q) j++;
		while(qs[j].l == i + 1 and j <= q){
			int a = query(qs[j].x) - query(qs[j].x - 1);
			qs[j].ans -= a; j++;
		}
	} memset(c, 0, sizeof(c));
	j = 1; sort(qs + 1, qs + q + 1, comp2);
	for(int i = 1; i <= n; i++){
		add(a[i]);
		while(qs[j].r < i and j <= q) j++;
		while(qs[j].r == i and j <= q){
			int a = query(qs[j].x) - query(qs[j].x - 1);
			qs[j].ans += a; j++;
		}
	}
	sort(qs + 1, qs + q + 1, comp3);
	for(int i = 1; i <= q; i++) cout << qs[i].ans << '\n';
	return 0;
}

T2 paint

  考场上的玄学做法竟然没有 T L E TLE TLE,(虽然有一个点是卡着 1 s 1s 1s 过的)。显然可以 O ( n m ) O(nm) O(nm) 预处理出每一个色块的边界,然后枚举所有色块,统计这个色块被哪些颜色覆盖过了。这些覆盖了这个色块的颜色显然就不是最先放下去的颜色。所以我们开一个桶存储一下,然后最后统计一遍就是答案了。

  注意这个做法在正常地图只有一个颜色的时候是错的,所以要特判一下是否整张地图都只有一个颜色,如果是直接输出 k − 1 k - 1 k1 (就是因为这里少了 10 p t s 10 pts 10pts)。

#include<bits/stdc++.h>
using namespace std;
#define in read()
#define MAXN 100100
#define INFI 1 << 30

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; int m = 0; int k = 0;
struct Tnode{
	int sx, sy;
	int tx, ty;
	int ts, tt;
}tag[MAXN];
int fa[MAXN] = { 0 };

int main(){
	freopen("paint.in", "r", stdin);
	freopen("paint.out", "w", stdout);
	n = in; m = in; k = in; int cnt = 0;
	if(n == 1 and m == 1) { cout << k - 1 << '\n'; return 0; }     // 事后知道只有一个颜色的样例就只有一个点,所以直接这样写了 qwq
	for(int i = 1; i <= k; i++) fa[i] = i;
	int c[n + 10][m + 10];
	for(int i = 1; i <= n; i++)
		for(int j = 1; j <= m; j++)
			c[i][j] = in;
	for(int i = 1; i <= k; i++) tag[i].sx = tag[i].sy = INFI, tag[i].tx = tag[i].ty = -INFI;
	for(int i = 1; i <= n; i++)
		for(int j = 1; j <= m; j++){
			tag[c[i][j]].sx = min(tag[c[i][j]].sx, i);
			tag[c[i][j]].sy = min(tag[c[i][j]].sy, j);
			tag[c[i][j]].tx = max(tag[c[i][j]].tx, i);
			tag[c[i][j]].ty = max(tag[c[i][j]].ty, j);
		}
	for(int x = 1; x <= k; x++){
		if(tag[x].sx == INFI) continue;
		for(int i = tag[x].sx; i <= tag[x].tx; i++)
			for(int j = tag[x].sy; j <= tag[x].ty; j++)
				if(c[i][j] != x and fa[c[i][j]] == c[i][j]) cnt++, fa[c[i][j]] = 0;
	}
	cout << k - cnt << '\n';
	return 0;
}

  我们考虑优化这个做法,如果 n > m n > m n>m 我们就交换 n n n m m m 这样可以保证 n < m n < m n<m 这样就有 n ∈ [ 1 , 1 e 5 ] n \in [1, \sqrt{1e5}] n[1,1e5 ]。然后我们在找哪些颜色覆盖了其他颜色是时候可以弄一个链表来保证所有的点只被扫一次(上面的做法慢就是因为有的点要被扫多次),这样的时间复杂度就能达到 O ( 1 e 5 n m ) O(\sqrt{1e5}nm) O(1e5 nm) 就能过了。

// std 代码
#include<bits/stdc++.h> 
#define M 100010
#define p(a, b) (a * mm + b - mm) 
using namespace std;
int i, j, Li[M], Ri[M], Lj[M], Rj[M], a[M], use[M], ans, n, m, K, fl, nxt[M], pre[M], vis[M], mm, u;
int find(int x){
	if(!vis[x])return x;
	return nxt[x] = find(nxt[x]);
}
int main(){
	freopen("paint.in","r",stdin);
	freopen("paint.out","w",stdout);
	fl = 0;
	scanf("%d%d%d", &n, &m, &K);
	memset(Li, 63, sizeof(Li));
	memset(Lj, 63, sizeof(Lj));
	memset(Ri, 0, sizeof(Ri));
	memset(Rj, 0, sizeof(Rj));
	memset(use, 0, sizeof(use));
	mm = max(n, m);
	for(int i = 1; i <= n; i++)
		for(int j = 1; j <= m; j++){
			if(n > m) u = p(j, i);
			else u = p(i, j);
			scanf("%d", &a[u]);
		}
	if(n > m)swap(n, m);
	for(int i = 1; i <= n; i++)
		for(int j = 1; j <= m; j++){
			u = p(i, j);
			nxt[u] = p(i, j + 1);
			pre[u] = p(i, j - 1);
			if(Ri[a[u]] == 0 && a[u])fl++;
			if(i < Li[a[u]])Li[a[u]] = i;
			if(i > Ri[a[u]])Ri[a[u]] = i;
			if(j < Lj[a[u]])Lj[a[u]] = j;
			if(j > Rj[a[u]])Rj[a[u]] = j;
		}
	for(int i = 1; i <= K; i++)
		if(Li[i] <= Ri[i]){
			for(int j = Li[i]; j <= Ri[i]; j++)
				for(int k = Lj[i]; k <= Rj[i]; k = find(nxt[k]))
					if(a[p(j, k)] != i){
						use[a[p(j, k)]] = 1;
						vis[p(j, k)] = 1;
						nxt[pre[p(j, k)]] = nxt[p(j, k)];
						pre[nxt[p(j, k)]] = pre[p(j, k)];
					}
		}
	for(int i = 1; i <= K; i++)
		if(!use[i])ans++;
	if(ans == K && fl == 1 && ans > 1)ans--;
	if(fl)printf("%d\n", ans);
	else puts("0");
}

T3 seq

  一个不确定的数组,每次约束条件是区间模 p p p 和,让你判断是否与前面的矛盾。容易想到做一个前缀和,但是数组不确定所以我们假设已经做了一个前缀和记为 s s s。那么每次的约束条件就可以写成:

s r − s l − 1 ≡ k ( m o d p ) s_r - s_{l - 1} \equiv k \pmod p srsl1k(modp)

  然后移一下项就可以得到:

s r ≡ s l − 1 + k ( m o d p ) s_r \equiv s_{l - 1} +k \pmod p srsl1+k(modp)

  于是题目就转化成了有 m m m 个像这样:

s j ≡ s i + k ( m o d p ) s_j \equiv s_i + k \pmod p sjsi+k(modp)

  的约束条件,判断每一个条件是否与前面所有的条件矛盾。我们发现当给出的 ( s j , s i ) (s_j, s_i) (sj,si) 都两两不同的时候是绝对不会有矛盾的,所以只需要考虑有相同的时候是否矛盾。这就可以想到用带权并查集来维护信息。

  具体的,对于一个式子:

s i ≡ s j + k 1 ( m o d p ) s_i \equiv s_j + k_1 \pmod p sisj+k1(modp)

  我们连一条由 j j j 指向 i i i 的边,其边权为 k 1 k_1 k1。根据我们上面的表述,当且仅当这个并查集里成环的时候我们才与要判断是否矛盾。并查集如果现在长这样:

在这里插入图片描述

  也就是说有两个约束条件:

{ s i ≡ s j + k 1 ( m o d p ) s i ≡ s n + k 2 ( m o d p ) \begin{cases} s_i \equiv s_j + k_1 \pmod p \\ s_i \equiv s_n +k_2 \pmod p \end{cases} {sisj+k1(modp)sisn+k2(modp)

  经过一些变换我们能得到:

s j + k 1 ≡ s n + k 2 ( m o d p ) → s j ≡ s n + ( k 2 − k 1 ) ( m o d p ) s_j + k_1 \equiv s_n +k_2 \pmod p \rightarrow s_j \equiv s_n + (k_2 - k_1)\pmod p sj+k1sn+k2(modp)sjsn+(k2k1)(modp)

  这个时候如果有一个新的约束条件:

s j ≡ s n + k ′ s_j \equiv s_n + k' sjsn+k

  加进来,也就是把并查集变成这样:

在这里插入图片描述

  我们就只需要判断 k ′ k' k 是否等于 k 2 − k 1 k_2 - k_1 k2k1 就可以了,不用真的把 k ′ k' k 这条边加进去。

  然后就结束了。

#include<bits/stdc++.h>
using namespace std;
#define in read()
#define MAXN 1001000

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; int m = 0; int p = 0;
struct Tnode{
	int x;
	int l, r;
}a[MAXN];
int  fa[MAXN] = { 0 };
int siz[MAXN] = { 0 };

int find(int x){
	if(x != fa[x]){
		int f = find(fa[x]);
		siz[x] += siz[fa[x]];
		fa[x] = f;
	}
	return fa[x];
}

int main(){
	freopen("seq.in", "r", stdin);
	freopen("seq.out", "w", stdout);
	n = in; m = in; p = in; bool flag = 0;
	for(int i = 1; i <= m; i++) a[i].l = in, a[i].r = in, a[i].x = in;
	for(int i = 1; i <= n; i++) fa[i] = i;
	for(int i = 1; i <= m; i++){
		int fax = find(a[i].l - 1); int fay = find(a[i].r);
		if(fax != fay)
			fa[fax] = fay, siz[fax] = a[i].x + siz[a[i].l - 1] + siz[a[i].r];
		else
			if((siz[a[i].l - 1] - siz[a[i].r]) % p != a[i].x) { cout << i - 1 << '\n'; flag = 1; break; }
	}
	if(!flag) cout << m << '\n';
	return 0;
}

T4 bomb

  考虑线性 d p dp dp,设 f i f_i fi 表示引爆第 i i i 个炸弹且炸弹 i i i 左边的所有炸弹都炸的最小能量。 g i g_i gi 表示引爆第 i i i 个炸弹且炸弹 i i i 右边的所有炸弹都炸的最小能量。那么答案就是:

a n s = min ⁡ i = 1 n { max ⁡ ( f i , g i ) } ans = \min_{i = 1}^n\left\{\max( f_i, g_i ) \right\} ans=i=1minn{max(fi,gi)}

  现在我们考虑如何计算 f i f_i fi g i g_i gi 同理)。显然有:

f i = min ⁡ j = 1 i − 1 { max ⁡ ( 3 f j 2 , a i − a j ) } f_i = \min_{j = 1}^{i - 1} \{ \max( \frac{3f_j}{2}, a_i - a_j ) \} fi=j=1mini1{max(23fj,aiaj)}

  意思就是枚举 j j j 找到能引爆 j j j 的能量(也就是 a i − a j a_i - a_j aiaj) 和能引爆 j j j 左边的所有的最小值(也就是 3 f j 2 \frac{3f_j}{2} 23fj)的能量最大值,然后再找出其中的最小值就是 f i f_i fi。同理我们有:

g i = min ⁡ j = i + 1 n { max ⁡ ( 3 f j 2 , a j − a i ) } g_i = \min_{j = i + 1}^n \{ \max ( \frac{3f_j}{2}, a_j - a_i ) \} gi=j=i+1minn{max(23fj,ajai)}

  这两个式子都是 O ( n 2 ) O(n^2) O(n2) 的,所以暴力计算可以得到 60 p t s 60 pts 60pts

  考虑如何优化,我们发现对于每一次的 a i − a j a_i - a_j aiaj 都是单调递减的,画出来就是一条从左上画到右下的斜线,而 3 f j 2 \frac{3f_j}{2} 23fj 是单调递增的,就是左下到右上的线。所以我们发现这俩的 max ⁡ \max max 的最小值肯定在这两条线的交点的两边最近的地方取到,而且对于每一次的查询 f j f_j fj 这条线不会变,每次就是 a j − a i a_j - a_i ajai 这条线像下平移,所以我们只需要 O ( n ) O(n) O(n) 每次维护出这两条线每一次的交点然后就能 O ( n ) O(n) O(n) 求出这个式子了。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值