返回顶部

AtCoder Beginner Contest 247题解

A - Move Right

题目描述:给你一个长度为\(4\)01串,让你将它右移一位并将高位补0后输出。

思路:根据题意模拟即可

时间复杂度:\(O(1)\)

参考代码:

void solve() {
	string s;
	cin >> s;
	s = '0' + s.substr(0, 3);
	cout << s << '\n';
	return;
}

B - Unique Nicknames

题目描述:有\(n\)个人,每个人有两个候选的名字,问你是否存在一种分配方案,使得每个人的名字都不在其他人的候选名字中出现。

思路:考虑到数据量比较小,考虑读入之后暴力检验即可。

时间复杂度:\(O(n^2|S|)\)\(|S|\)为字符串的长度

参考代码:

void solve() {
	int n;
	cin >> n;
	vector<vector<string>>strs(n, vector<string>(2));
	for (int i = 0; i < n; ++i) cin >> strs[i][0] >> strs[i][1];
	for (int i = 0; i < n; ++i) {
		int cnt = 0;
		for (int k = 0; k <= 1; ++k) {
			for (int j = 0; j < n; ++j) {
				if (i == j) continue;
				if (strs[j][0] != strs[i][k] && strs[j][1] != strs[i][k]) continue;
				++cnt;
				break;
			}
		}
		if (cnt == 2) {
			cout << "No" << '\n';
			return;
		}
	}
	cout << "Yes" << '\n';
	return;
}

C - 1 2 1 3 1 2 1

题目描述:定义序列\(S_i\),其中\(S_1 = (1)\),有递推式\(S_i = S_{i - 1}\;i\;S_{i - 1}\),现在给定\(n\),输出\(S_n\)

思路:根据题意模拟即可。

时间复杂度:\(O(2^n)\)

参考代码:

void solve() {
	int n;
	cin >> n;
	vector<int> res = { 1 };
	for (int i = 2; i <= n; ++i) {
		int m = res.size();
		vector<int>ans(2 * m + 1, 0);
		for (int k = 0; k < m; ++k) ans[k] = ans[m + k + 1] = res[k];
		ans[m] = i;
		swap(res, ans);
	}
	for (auto&& re : res) cout << re << ' ';
	cout << '\n';
	return;
}

D - Cylinder

题目描述:有一个队列,有\(Q\)次操作,操作有以下两种类型:

  • 1 x c:表示向队列中插入\(c\)个值为\(x\)的元素
  • 2 c:表示从队列中取出\(c\)个元素并输出这c个元素的值的和

数据范围:\(1 \leq Q \leq 2 \times 10^5\)

思路:将每次插入当做一个整体存储在队列中,考虑到出队的数量可能小于当前队首的数量,所以使用双端队列维护即可。

时间复杂度:\(O(n)\)

参考代码:

void solve() {
	int q;
	cin >> q;
	using PII = pair<int, int>;
	deque<PII> deq;
	while (q--) {
		int op, x, c;
		cin >> op;
		if (op == 1) {
			cin >> x >> c;
			deq.push_back({ c , x });
		}
		else {
			cin >> c;
			long long res = 0;
			while (true) {
				auto [nums, val] = deq.front(); deq.pop_front();
				if (nums < c) {
					c -= nums;
					res += 1ll * nums * val;
				}
				else {
					nums -= c;
					res += 1ll * c * val;
					deq.push_front({ nums , val });
					break;
				}
			}
			cout << res << '\n';
		}
	}
	return;
}

E - Max Min

题目描述:给你一个长度为\(n\)的数组\(A\),和两个整数\(x , y (x \leq y)\),问你有多少个区间\([L , R]\),满足区间的最大值为\(y\),最小值为\(x\)

思路:首先考虑到值小于\(x\)或者大于\(y\)的位置一定不能对答案做出贡献,所以我们可以将数组分解成最少的不重叠子串,使得对于每一段中的元素都在区间\([x , y]\)内。现在讨论其中的一个子串,假设这个子串的长度为\(m\),我们使用双指针,统计出现的数字,若\(x , y\)在区间\([lr , rs]\)内恰好都出现, 那么此时对答案的贡献为\(m - rs + 1\),然后我们移动左指针直到某一个临界值不在区间内,再去移动右指针,重复即可。

时间复杂度:\(O(n)\)

双倍经验: 992. K 个不同整数的子数组

参考代码:

void solve() {
	int n, a, x, y;
	cin >> n >> y >> x;
	vector<int> nums;
	long long res = 0;
	auto cal = [&](){
		int m = nums.size();
		if (m == 1) return;
		int rs = 1, cntx = nums[1] == x, cnty = nums[1] == y;
		for (int lr = 1; lr < m; ++lr) {
			while (rs < m && (cntx == 0 || cnty == 0)) {
				if (++rs >= m) break;
				cntx += nums[rs] == x;
				cnty += nums[rs] == y;
			}
			
			res += m - rs;
			cntx -= nums[lr] == x;
			cnty -= nums[lr] == y;
		}
		return;
	};
	nums.push_back(0);
	for (int i = 1; i <= n; ++i) {
		cin >> a;
		if (a >= x && a <= y) nums.push_back(a);
		else {
			cal();
			nums.clear();
			nums.push_back(0);
		}
	}
	cal();
	cout << res << '\n';
	return;
}

双倍经验的参考代码:

class Solution {
public:
    int subarraysWithKDistinct(vector<int>& nums, int k) {
        int res = 0, n = nums.size();
        vector<int>cnt(n + 1 , 0);
        ++cnt[nums[0]];
        int lr = 0 , rs = 0, ct = 1;
        for(int i = 0 ; i < n ; ++i){
            while(lr < n && ct < k){
                if(++lr >= n) break;
                if(++cnt[nums[lr]] == 1) ++ct;
            }
            if(rs <= lr) rs = lr + 1;
            while(rs < n && cnt[nums[rs]] != 0) ++rs;
            if(lr < n) res += rs - lr;
            if(--cnt[nums[i]] == 0) --ct;
        }
        return res;
    }
};

F - Cards

题目描述:给你\(n\)张卡片,卡片的正面的数字集合是一个\(n\)的排列,卡片的背面的数字集合也是一个\(n\)的排列,问你有多少种选择方案可以使得选出来的卡片包含有\(1 \sim n\)的所有数字,答案对998244353取模。

思路:考虑到卡片正反面都是排列,若将数字\(i\)抽象成编号为\(i\)的顶点,对于一张卡片上的数字,假设正面为\(u\),背面为\(v\),那么我们假定顶点对\((u , v)\)之间存在一条有向边\(u\to v\),那么按照这样就可以构建成一个有向图,考虑到是排列,那么这个有向图就是由一个又一个不相交的环所组成。对于一个环上的数字,任意相邻两个就是一张卡片,那么要表示这个环所对应的数字集合,假设这个环上有\(m\)个顶点,即求:对于任意两个相邻的顶点必须选择其中一个的方案数。假设共有\(k\)个环,第\(i\)个环的顶点数为\(V_i\),设其对答案的贡献为\(g(V_i)\),根据乘法原理最终答案为:

\[\prod\limits_{i = 1}^{k}g(V_i) \]

时间复杂度:\(O(n)\)

参考代码:

const int mod = 998244353;

void solve() {
	int n;
	cin >> n;
	vector<vector<int>>f1(n + 1, vector<int>(2, 0));//选择第一个
	vector<vector<int>>f2(n + 1, vector<int>(2, 0));//不选第一个
	f1[1][1] = 1;
	f2[1][0] = 1;
	for (int i = 2; i <= n; ++i) {
		f1[i][0] = f1[i - 1][1];
		f1[i][1] = (f1[i - 1][0] + f1[i - 1][1]) % mod;
		f2[i][0] = f2[i - 1][1];
		f2[i][1] = (f2[i - 1][0] + f2[i - 1][1]) % mod;
	}
	auto calCircle = [&](int m)->int {
		if (m == 1) return 1;
		return (1ll * f1[m][1] + f1[m][0] + f2[m][1]) % mod;
	};
	vector<int>adj(n + 1), a(n + 1), b(n + 1);
	for (int i = 1; i <= n; ++i) cin >> a[i];
	for (int i = 1; i <= n; ++i) {
		cin >> b[i];
		adj[a[i]] = b[i];
	}
	vector<bool>vis(n + 1, false);
	auto dfs = [&](auto&& dfs, int rt, int u)->int {
		vis[u] = true;
		if (u == rt) return 1;
		return dfs(dfs, rt, adj[u]) + 1;
	};
	int res = 1;
	for (int i = 1; i <= n; ++i) {
		if (vis[a[i]]) continue;
		int j = dfs(dfs , a[i], b[i]);
		res = 1ll * res * calCircle(j) % mod;
	}
	cout << res << '\n';
	return;
}
posted @ 2022-04-12 20:53  cherish-lgb  阅读(131)  评论(0编辑  收藏  举报