ABC217 复盘

ABC217 复盘

[ABC217A] Lexicographic Order

思路解析

直接使用 string 库中自带的大小判断即可。

时间复杂度:使用系统库的比较,复杂度应是 \(O(\left\vert s \right\vert)\)

code

#include<bits/stdc++.h>
using namespace std;
string s, t;
int main() {
	cin >> s >> t;
	if(s < t) cout << "Yes";	//直接判断
	else cout << "No";
	return 0;
}

[ABC217B] AtCoder Quiz

思路解析

可以用一个 set 存下 \(4\) 种字符串,每读入一个字符串就将其从 set 中删去,最后输出剩下的一个即可。

时间复杂度:由于字符串总数固定,长度固定,因此复杂度为常数级别。

code

#include<bits/stdc++.h>
using namespace std;
set<string> st;
void init() {	//预处理
	st.insert("ABC");
	st.insert("AGC");
	st.insert("ARC");
	st.insert("AHC");
}
int main() {
	string s;
	init();
	for(int i = 1; i <= 3; i++) {
		cin >> s;
		st.erase(s);
	}
	cout << *st.begin();
	return 0;
}

[ABC217C] Inverse of Permutation

思路解析

输入每一个 \(p_i\),直接 \(q_{p_i} \gets i\) 即可。

时间复杂度:遍历整个数组,复杂度为 \(O(n)\)

code

#include<bits/stdc++.h>
using namespace std;
const int N = 2e5 + 10;
int n, p[N], q[N];
int main() {
	cin >> n;
	for(int i = 1; i <= n; i++) {
		cin >> p[i];
		q[p[i]] = i;	//直接赋值
	}
	for(int i = 1; i <= n; i++) {
		cout << q[i] << ' ';
	}
	return 0;
}

[ABC217D] Cutting Woods

思路解析

我们发现 \(L \ge 10^9\),也就是说我们不能用一个数组存下每一个节点的信息,否则数组会爆。于是考虑 set,因为 set 可以做到 \(O(\log n)\) 查询+修改,同时存下每一个区间的右端点。然后如果要添加区间,就可以直接往 set 里插入 \(x\),若是查询长度,就可以用 set 自带的 lower_bound 找到右端点,而左端点就是 lower_bound - 1

时间复杂度:\(q\) 次询问,每次询问需要 \(O(\log L)\) 来插入或查询,因此复杂度为 \(O(q \log L)\)

code

#include<bits/stdc++.h>
using namespace std;
int n, q;
set<int> s;
int main() {
	cin >> n >> q;
	s.insert(0);	//提前处理好 [1,n] 区间
	s.insert(n);
	while(q--) {
		int c, x;
		cin >> c >> x;
		if(c == 1) {
			s.insert(x);	//插入右端点
		}
		else if(c == 2) {
			auto it = s.lower_bound(x);
			auto it1 = it, it2 = (--it);	//找到左右端点
			cout << (*it1) - (*it2) << '\n';
		}
	}
	return 0;
}

[ABC217E] Sorting Queries

思路解析

可以发现一个细节,如果原序列已经排好序,此时再往后添加新元素不会改变原序列的顺序,也即,可以把已经排好序的部分和未排序发部分分别单独处理。选择用 priority_queue 处理排好序的部分,一个普通的 queue 处理未排序部分,剩下就好做了。插入元素就直接往 queue 里插入;由于永远是排好序的在先,所以输出头部元素时先判断小根堆中是否为空,若为空输出小根堆堆顶并弹出,否则输出普通队列队首并弹出;排序则可以直接将普通队列中所有元素放进小根堆中即可。

时间复杂度:\(q\) 次询问,每次询问如果是查询和排序就会有 \(\log q\) 的复杂度,于是总体复杂度为 \(O(q \log q)\)

code

#include<bits/stdc++.h>
using namespace std;
int n;
queue<int> v;
priority_queue< int, vector<int>, greater<int> > q;	//小根堆
int main() {
	cin >> n;
	while(n--) {
		int op, x;
		cin >> op;
		if(op == 1) {
			cin >> x;
			v.push(x);
		}
		else if(op == 2) {
			if(!q.empty()) {	//线先判已排序的部分
				cout << q.top() << '\n';
				q.pop();
			}
			else if(!v.empty()) {
				cout << v.front() << '\n';
				v.pop();
			}
			else cout << "IAKIOI\n";
		}
		else if(op == 3) {
			while(!v.empty()) {	//将 queue 里所有元素放入小根堆
				q.push(v.front());
				v.pop();
			}
		}
	}
	return 0;
}

[ABC217F] Make Pair

思路解析

通过 \(n \le 200\) 和 “选出的两个学生离开队列,空出来的位置左右合拢” 这两个细节可以想到能用区间 dp 做,\(f_{i,j}\) 表示将 \(i \to j\) 这个区间全部选完的方案数,然后常规区间 dp,加一个判断如果当前区间 \([l,r]\)\(l,r\) 是朋友,就可以从 \([l+1,r-1]\) 推过来,于是加上 \(f_{l+1,r-1}\) 代表除去两个端点后的区间方案数。

接下来是重点,就是遍历每一个 \(k\),然后统计 \(f_{l,k}+f_{k+1,r}\),表示枚举每一个中间点,统计从该点切割开来的两半的贡献。如果对于这一点不太理解,建议去做 P1063 [NOIP2006 提高组] 能量项链 学习区间 dp。但是题目要求方案数,就很有可能出现判重,例如:

这体现了一个区间 \([l,r]\) 的学生示意图,圆圈代表学生,连线代表他们是朋友关系,线上的数字用于区分标记。接下来有几种可能的 \(k\) 的取值。

这种情况下可能出现的方案有:【1,2,3,4】,【2,1,3,4】,【1,3,4,2】……

这种情况下的方案则和上一种情况一模一样,原因则是因为我们没有给 \(k\) 的可能性做一个判断。可以想到我们的 \(k\) 的取值一定会与端点有联系,于是在转移前判断 \(k\) 是否与 \(l\) 连接,这样就可以有效排除掉重复的方案,因为我们需要统计的根本是和 \(l\) 联系的那一部分(例如我的例子中的 1 号朋友)加上和 \(l\) 没联系的部分(例如我的例子中的2,3,4),这样就可以排除掉 \(k\) 与当前 \(l\) 无关的重复方案。

最后就是由于是算方案数,就说明区间 \([l,k]\)\([k+1,r]\) 中每一种可能的方案在两段合并到一起后当前方案中的元素出现的相对顺序不会改变,例如:

左边的 1,2,3 代表区间 \([l,k]\) 中的一种可行方案,右边的 a,b 代表区间 \([k+1,r]\) 中的一种可行方案,那么当两者合并后的可行方案的顺序就有可能是:

这里不放出 a,b 是因为合并后原来一个方案中的相对顺序不会改变,画出横线是为了更好理解其实这就是一个在横线中填入相对顺序有序数字的一个问题,其实这个例子的方案数就是 \(\mathrm{C}_5^3\),扩展到所有方案就是 \(\mathrm{C}_{len/2}^{(k-l+1)/2}\)。于是便在状态转移时乘上该式即可。

时间复杂度:三层区间 dp,每层最大都是 \(n\),复杂度 \(O(n^3)\)

code

#include<bits/stdc++.h>
using namespace std;
#define ll long long
const int N = 1010;
const ll mod = 998244353;
ll n, m, f[N][N], c[N][N];
bool frd[N][N];
int main() {
	cin >> n >> m;
	n *= 2ll;
	for(int i = 1, a, b; i <= m; i++) {
		cin >> a >> b;
		frd[a][b] = frd[b][a] = true;	//标记为朋友
	}
	for(int i = 1; i <= n; i++) f[i + 1][i] = 1;
	c[0][0] = 1;
	for(int i = 1; i <= n; i++) {
		for(int j = 1; j <= i; j++) {
			c[i][j] = (c[i - 1][j - 1] + c[i - 1][j]) % mod;	//预处理组合数
		}
	}
	for(int i = 2; i <= n; i += 2) {
		for(int l = 1, r = l + i - 1; l + i - 1 <= n; l++, r++) {
			if(frd[l][r]) f[l][r] = f[l + 1][r - 1];	//小特判
			for(int k = l + 1; k <= r - 1; k += 2) {
				int l1 = (k - l + 1) / 2, l2 = (r - k) / 2;
				if(frd[l][k]) {	//去重的关键
					f[l][r] = (f[l][r] + ((f[l + 1][k - 1] * f[k + 1][r]) % mod * c[i / 2 + 1][l1 + 1]) % mod) % mod;	//注意要乘上组合数
				}
			}
		}
	}
	cout << f[1][n];
	return 0;
}
posted @ 2024-03-01 20:13  2020luke  阅读(107)  评论(0)    收藏  举报