2021.11.15模拟赛

本文作者分享了参加算法竞赛的经验,重点讨论了Trie树、Hash表和线段树的应用。在Trie树问题中,通过模板题优化了暴力解法;在Hash表问题中,利用unordered_map快速查找,降低复杂度;线段树问题中展示了如何在有限时间内解决动态更新和查询的问题。此外,还提及了一道构造题的思路和优化过程。
摘要由CSDN通过智能技术生成

  %%% FSYo Orz orz orz

F

  考完了之后才发现是 Trie 树或者 Hash 表的模板题(我太蒻了)。考试的时候打了一个 O ( n n ! ) O(nn!) O(nn!) 的 dfs 暴力:

#include<bits/stdc++.h>
using namespace std;
#define MAXN 2020
#define endl '\n'

int n = 0;
int a[MAXN] = { 0 };
int b[MAXN] = { 0 };
int c[MAXN] = { 0 };

int p[MAXN] = { 0 };
int x[MAXN] = { 0 };
int vis[MAXN] = { 0 };

bool comp(int x, int y){
	return x < y;
}
int cnt = 0;
int ans[MAXN] = { 0 };
void work(){
	for(int i = 1; i <= n; i++){
		x[i] = a[i] ^ b[p[i]];
		if(i > 1 and x[i] != x[i-1]) return;
	}
	ans[++cnt] = x[1];
}

void dfs(int now){
	if(now > n){
		work(); return;
	}
	for(int i = 1; i <= n; i++)
		if(!vis[c[i]]){
			vis[c[i]] = 1; p[now] = c[i];
			dfs(now + 1);
			vis[c[i]] = 0; p[now] = 0;
		}
}

int main(){
	scanf("%d", &n);
	for(int i = 1; i <= n; i++) scanf("%d", &a[i]);
	for(int i = 1; i <= n; i++) scanf("%d", &b[i]);
	for(int i = 1; i <= n; i++) c[i] = i;
	dfs(1);
	sort(ans+1, ans+cnt+1, comp); cout << cnt << endl;
	for(int i = 1; i <= cnt; i++) cout << ans[i] << ' ';
	puts("");
	return 0;
}

  考完之后发现 x x x 的取值就只有 n 2 n^2 n2 中可能,所以我们 O ( n 2 ) O(n^2) O(n2) 枚举所有 ( a i , b j ) (a_i,b_j) (ai,bj),并把它们和它们的异或值存进一个 Hash 表里面,然后就再遍历一遍 Hash 表,答案就是出现次数 ≥ n \geq n n 次的所有 x x x(这么简单我竟然没做出来…)。

#include<bits/stdc++.h>
using namespace std;
#define in read()
#define MAXN 2020
#define endl '\n'

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 a[MAXN] = { 0 };
int b[MAXN] = { 0 };
unordered_map<int , int> s;
int cnt = 0;
int ans[MAXN] = { 0 };
bool comp(int x, int y){
	return x < y;
}

void check(int x){
	unordered_map <int, int> c;
	for(int i = 1; i <= n; i++) c[b[i]]++;
	for(int i = 1; i <= n; i++){
		if(!c[a[i] ^ x]) return;
		c[a[i] ^ x]--;
	}
	ans[++cnt] = x;
}

int main(){
	n = in;
	for(int i = 1; i <= n; i++) a[i] = in;
	for(int i = 1; i <= n; i++) b[i] = in;
	for(int i = 1; i <= n; i++)
		for(int j = 1; j <= n; j++)
			s[a[i] ^ b[j]]++;
	for(auto x : s)
		if(x.second >= n) check(x.first);
	sort(ans+1, ans+cnt+1, comp);
	cout << cnt << endl;
	for(int i = 1; i <= cnt; i++)
		cout << ans[i] << ' ';
	puts("");
	return 0;
}

  这里的 unordered_map 不像 map 一样是用红黑树实现,这个可以实现 O ( 1 ) O(1) O(1) 下标查询,所以就能 O ( n 2 ) O(n^2) O(n2) 过掉。

S

  一道神奇的构造题,构造方式好想但是证明就不太好想了(考场上不证明打了正解 qwq)。

  我是这样构造的:
在这里插入图片描述
显然这样是最优的qwq. 我们就来证明一下这样构造是最优的。首先我们看上界是多少。这个图主要的限制就是一组周围的一圈都不能有点,所以我们考虑两种情况:
在这里插入图片描述
  周围一圈红色的就是不能填的,我们要让能填的最大,那么肯定选择竖着的或者横着的而不是斜着放的。然后可以证到上界就是 ⌊ ( n + 1 ) 2 6 ⌋ − 2 \lfloor\frac{(n+1)^2}{6}\rfloor-2 6(n+1)22,我们把 n n n 分为三种种情况计算发现这种排法和上界都是一样的。然后这种构造方法就是最优的了。

#include<bits/stdc++.h>
using namespace std;
#define MAXN 1010
#define endl '\n'
typedef long long ll; 

int n = 0;
int type = 0;

int f[MAXN] = { 0 };
int ans[MAXN][MAXN] = { 0 };

int main(){
	scanf("%d%d", &n, &type);
	if(!type){
		if(n <= 1000){
			f[2] = 2; f[4] = 8; f[6] = 16;
			for(int i = 8; i <= n; i += 2) f[i] = f[i-6] + 4 * (i - 2);
			cout << f[n] << endl;	
		}
		else{
			ll ans = 0;
			if((n - 2) % 6 == 0) ans = 2 + (1ll * n * n + 1ll * 2 * n - 8) / 3;
			else if((n - 4) % 6 == 0) ans = 8 + (1ll * n * n + 1ll * 2 * n - 24) / 3;
			else ans = 16 + (1ll * n * n + 1ll * 2 * n - 48) / 3;
			cout << ans << endl;
		}
		return 0;
	}
	else{
		f[2] = 2; f[4] = 8; f[6] = 16;
		for(int i = 8; i <= n; i += 2) f[i] = f[i-6] + 4 * (i - 2);
		cout << f[n] << endl;
		if(n == 2) ans[1][1] = ans[2][2] = 1;
		else if(n == 4){
			ans[1][1] = ans[1][2] = ans[1][4] = 1;
			ans[2][4] = ans[3][1] = 1;
			ans[4][1] = ans[4][3] = ans[4][4] = 1;
		}
		else if(n == 6){
			ans[1][1] = ans[1][2] = ans[1][4] = ans[1][6] = 1;
			ans[2][4] = ans[2][6] = ans[5][1] = ans[5][3] = 1;
			ans[3][1] = ans[3][2] = ans[4][5] = ans[4][6] = 1;
			ans[6][1] = ans[6][3] = ans[6][5] = ans[6][6] = 1;
		}
		else{
			for(int x = 1; x <= n / 2; x += 3){
				for(int i = x; i <= n - x - 1 ; i += 2){
					ans[i][x] = ans[i][x+1] = 1;
					ans[n-x+1][i] = ans[n-x][i] = 1;
				}
				for(int i = x + 3; i <= n - x + 1 ; i += 2){
					ans[x][i] = ans[x+1][i] = 1;
					ans[i][n-x+1] = ans[i][n-x] = 1;
				}
			}
			if((n - 8) % 6 == 0) ans[n/2][n/2] = ans[n/2+1][n/2+1] = 1;
		}
		for(int i = 1; i <= n; i++){
			for(int j = 1; j <= n; j++){
				cout << ans[i][j] << ' ';
			}
			puts("");
		}
	}
	return 0;
}

Y

  让 c i c_i ci 是第 i i i 个人向后传的球的个数。

  当 min ⁡ i = 1 n c i ≠ 0 \min\limits_{i=1}^nc_i \neq 0 i=1minnci=0 的时候,我们发现如果我们把所有的 c i c_i ci 都减 1,是不会影响交换之后产生的序列的。所以我们可以把每个 c i c_i ci 都减去一个 min ⁡ i = 1 n c i \min\limits_{i=1}^nc_i i=1minnci 使得 min ⁡ i = 1 n c i = 0 \min\limits_{i=1}^nc_i = 0 i=1minnci=0,然后再进行处理(下文中默认 min ⁡ i = 1 n c i = 0 \min\limits_{i=1}^nc_i = 0 i=1minnci=0)。

  我们知道,我们一共有 t o t = ∏ i = 1 n ( a i + 1 ) − ∏ i = 1 n a i tot = \prod\limits_{i=1}^n (a_i+1) - \prod\limits_{i=1}^n a_i tot=i=1n(ai+1)i=1nai 种这样的序列 c c c (解释一下:没有 min ⁡ i = 1 n c i ≠ 0 \min\limits_{i=1}^nc_i \neq 0 i=1minnci=0 这个条件的时候,第 i i i 个人能给别人的球的个数为 a i + 1 a_i + 1 ai+1,所以所有的可能就是 ∏ i = 1 n ( a i + 1 ) \prod\limits_{i=1}^n (a_i+1) i=1n(ai+1),如果要求 ∀ c i > 0 \forall c_i > 0 ci>0 那么所有的可能就是 ∏ i = 1 n a i \prod\limits_{i=1}^n a_i i=1nai。然后两式相减就得到了条件 min ⁡ i = 1 n c i ≠ 0 \min\limits_{i=1}^nc_i \neq 0 i=1minnci=0 成立时的总个数) 。所以如果我们能找到的 c i c_i ci 序列所形成的结果序列 x i x_i xi 那我们就找到了答案就是 ∑ i = 1 t o t ∏ j = 1 n x i , j \sum\limits_{i=1}^{tot}\prod\limits_{j=1}^n x_{i,j} i=1totj=1nxi,j。我们也知道 ∏ i = 1 n x i \prod\limits_{i=1}^n x_i i=1nxi 的值代表的是某一个 c c c 序列操作后的结果序列 x x x 的各个项的乘积,但是它也可以代表 i i i 个人有 x i x_i xi 个球,每次从一个人手里拿一个球的取法的方案数。

  然后后面的 DP 我就看不懂了(我太蒻了)…

o

  有生之年第一次在考场上打了 T4,太感动了。一看题目,wow,裸的线段树能拿 20 分!

#include<bits/stdc++.h>
using namespace std;
#define int long long
#define MAXN 50500 
#define ls(p) (p << 1)
#define rs(P) (p << 1 | 1)
#define in read()
#define endl '\n'

inline int read(){
	int x = 0; char c = getchar();
	while(c > '9' or c < '0') 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;

struct Tnode{
	int l, r;
	int dat;
}t[4 * MAXN];
inline void pushup(int p){
	t[p].dat = 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 = a[l]; return;
	}
	int mid = (l + r) >> 1;
	build(ls(p), l, mid);
	build(rs(p), mid + 1, r);
	pushup(p);
}

void update(int p, int index, int val){
	if(t[p].l == t[p].r){
		t[p].dat = val; return;
	}
	int mid = (t[p].l + t[p].r) >> 1;
	if(index <= mid) update(ls(p), index, val);
	else update(rs(p), index, val);
	pushup(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; int ans = 0;
	if(l <= mid) ans += query(ls(p), l, r);
	if(r > mid)  ans += query(rs(p), l, r);
	return ans;
}

struct Tqu{
	int t;
	int idx;
	int ans;
	int l, r;
}qu[MAXN];
bool comp1(Tqu a, Tqu b){
	return a.t < b.t;
}
bool comp2(Tqu a, Tqu b){
	return a.idx < b.idx;
}

signed main(){
	n = in; q = in;
	for(int i = 1; i <= n; i++) a[i] = in;
	build(1, 1, n);
	for(int i = 1; i <= q; i++){
		qu[i].t = in; qu[i].l = in; qu[i].r = in; qu[i].idx = i;
	} sort(qu+1, qu+q+1, comp1);
	int idx = 1;
	for(int i = 1; i <= qu[q].t; i++){
		for(int j = 1; j <= n; j++) update(1, j, max(a[j], a[j-1]));
		for(int j = 1; j <= n; j++) a[j] = query(1, j, j);
		while(qu[idx].t == i){
			qu[idx].ans = query(1, qu[idx].l, qu[idx].r);
			idx++;
		}
	} sort(qu+1, qu+q+1, comp2);
	for(int i = 1; i <= n; i++)  cout << qu[i].ans << endl;
	return 0;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值