本文发布于博客园,会跟随补题进度实时更新,若您在其他平台阅读到此文,请前往博客园获取更好的阅读体验。
跳转链接:https://www.cnblogs.com/TianTianChaoFangDe/p/18869039
开题 + 补题情况
最近一直在准备期中考试,好久没写代码了,这场有点糖,前一个小时一题没开,实属红温,尤其是 1007,想了好久好久。
下周打南昌,自己第一次参加全国性的比赛,希望自己加油发挥吧!
1010 - 绳子切割
此题考查并查集(实际暴力也可以)。
我们删点是从大到小删,那么倒过来就是加点,加点就是从小到大加。
对于每一个点,连向比它小的结点的连通块,看是否能和 \(0\) 在同一块,如果不在,那么直接输出 \(0\),因为这个时候,它不和横梁同属一个连通块,说明它到 \(0\) 之间有点被删除了,进而导致该点被提前删除了。
点击查看代码
cpp
#include <bits/stdc++.h>
#define inf32 1e9
#define inf64 2e18
#define ls o << 1
#define rs o << 1 | 1
using i64 = long long;
using u64 = unsigned long long;
using u32 = unsigned int;
const int N = 2e5 + 9;
struct DSU {
std::vector<int> fa;
DSU(int n) {
fa.resize(n + 1);
std::iota(fa.begin(), fa.end(), 0);
}
int root(int x) {
return (fa[x] == x) ? x : (fa[x] = root(fa[x]));
}
void merge(int u, int v) {
if(root(u) == 0) {
fa[root(v)] = root(fa[u]);
} else {
fa[root(u)] = root(fa[v]);
}
}
};
void solve()
{
int n, m;std::cin >> n >> m;
DSU dsu(n);
std::vector<std::vector<int>> g(n + 1);
for(int i = 1;i <= m;i ++) {
int u, v;std::cin >> u >> v;
g[u].push_back(v);
g[v].push_back(u);
}
for(int i = 1;i <= n;i ++) {
for(auto &x : g[i]) {
if(i < x)continue;
dsu.merge(i, x);
}
if(dsu.root(i) != 0) {
std::cout << 0 << '\n';
return;
}
}
std::cout << 1 << '\n';
}
int main()
{
std::ios::sync_with_stdio(0), std::cin.tie(0), std::cout.tie(0);
int t = 1;std::cin >> t;
while(t --)solve();
return 0;
}
1004 - 储值购物
这个题,先上结论,每次取 \(W / 2 + 1\),一定最优。
简单证明如下:
若某次取了 \(\leq W / 2\),那么这次剩下的,一定 \(\geq W / 2\),那么,当下次取 \(W / 2\) 的时候,会继续使用当前这个卡,若取更多,则可以均摊给上次取的,这样的话最后二者都会取到 \(W / 2 + 1\)。
因此,每次取 \(W / 2 + 1\),当取不够了的时候,判断是新加一张卡还是使用现有的。
点击查看代码
cpp
#include <bits/stdc++.h>
#define inf32 1e9
#define inf64 2e18
#define ls o << 1
#define rs o << 1 | 1
using i64 = long long;
using u64 = unsigned long long;
using u32 = unsigned int;
const int N = 2e5 + 9;
template<typename T>
T up(T x, T y) {
return (x + y - 1) / y;
}
void solve()
{
int v, w;std::cin >> v >> w;
int h = w / 2 + 1;
int l = w - h;
int sum = v / h;
v -= sum * h;
int ans = 0;
if(sum) {
if(v > l)ans = sum + 1;
else ans = sum;
} else {
ans = 1;
}
std::cout << ans << '\n';
}
int main()
{
std::ios::sync_with_stdio(0), std::cin.tie(0), std::cout.tie(0);
int t = 1;std::cin >> t;
while(t --)solve();
return 0;
}
1005 - 真爱口上
这个题看似是一个非常恶心的模拟题,但是题目有一个很重要的信息:所有的字符串都是符合规则的音节序列。也就意味着我们不需要判断串的合法性,只需要根据特征计数就行了。
- 基本音节结构:不难发现,一定是以元音结尾,因此只需要计算元音有多少个即可。
- 鼻音:单算
nn
相连的情况,特别注意比如nnna
的情况,此时不能算为两个鼻音。 - 促音:计算
pp
tt
kk
ss
的数量即可。
点击查看代码
cpp
#include <bits/stdc++.h>
#define inf32 1e9
#define inf64 2e18
#define ls o << 1
#define rs o << 1 | 1
using i64 = long long;
using u64 = unsigned long long;
using u32 = unsigned int;
const int N = 2e5 + 9;
bool isyun(char c) {
return c == 'a' || c == 'i' || c == 'u' || c == 'e' || c == 'o';
}
bool isptks(char c) {
return c == 'p' || c == 't' || c == 'k' || c == 's';
}
int getans(std::string &s) {
int n = s.size();
int res = 0;
int pre = -1;
for(int i = 0;i < n;i ++) {
if(isyun(s[i])) {
res ++;
}
if(isptks(s[i])) {
if(i - 1 >= 0 && s[i - 1] == s[i]) {
res ++;
}
}
if(s[i] == 'n') {
if(i - 1 >= 0 && s[i - 1] == s[i] && pre != i - 1) {
res ++;
pre = i;
}
}
}
return res;
}
void solve()
{
std::string s, t;std::cin >> s >> t;
std::cout << getans(s) << ' ' << getans(t) << '\n';
}
int main()
{
std::ios::sync_with_stdio(0), std::cin.tie(0), std::cout.tie(0);
int t = 1;std::cin >> t;
while(t --)solve();
return 0;
}
1007 - 扑克洗牌
你没有看错,这是我过的......第五个题......
真的红温了,这题一直没想到思路,但是过的人又那么多,后面猛地想到了。
首先要明晰一件事,对于移动了的牌,它一定是移动且仅移动一次!
为什么?
因为我们可以把它插入任何位置,那么我们就可以直接把它插入到应该处在的位置,并且不要去动它。
然后呢,我们就要考虑,对于目标序列,我们要如何移动得到。
对于原串,所有数字都是连续的,相邻的。
对于移动,是只能从两头进行移动的。
那么就可以得出以下结论:我们最终的目标串,一定是原串某个连续子串,插入两边的扑克牌得到的。
对于原串中的这个子串,它是不需要移动的,因为它是被插入的对象。
所以要让移动次数最小,那么就是要让选择的这个原串子串长度,尽可能的大。
这个长度,我们用简单的 dp 可以很快计算出来。
记 \(dp_i\) 为以数字 \(i\) 结尾的最长连续子序列的长度。
那么有以下转移:
- 当 \(i - 1\) 没有出现过时,\(dp_i = 1\)。
- 当 \(i - 1\) 出现过时,\(dp_i = dp_{i - 1} + 1\)。
对每个 \(dp_i\) 执行 \(n - dp_i\),对所有答案取最小值即可。
点击查看代码
cpp
#include <bits/stdc++.h>
#define inf32 1e9
#define inf64 2e18
#define ls o << 1
#define rs o << 1 | 1
using i64 = long long;
using u64 = unsigned long long;
using u32 = unsigned int;
const int N = 2e5 + 9;
void solve()
{
int n;std::cin >> n;
std::vector<int> a(n + 1);
std::vector<int> vis(n + 1);
std::vector<int> dp(n + 1);
for(int i = 1;i <= n;i ++) {
std::cin >> a[i];
}
vis[0] = true;
for(int i = 1;i <= n;i ++) {
if(vis[a[i] - 1]) {
dp[a[i]] = dp[a[i] - 1] + 1;
} else {
dp[a[i]] = 1;
}
vis[a[i]] = true;
}
int ans = inf32;
for(int i = 1;i <= n;i ++) {
ans = std::min(ans, n - dp[i]);
}
std::cout << ans << '\n';
}
int main()
{
std::ios::sync_with_stdio(0), std::cin.tie(0), std::cout.tie(0);
int t = 1;std::cin >> t;
while(t --)solve();
return 0;
}
1002 - 折线绘制
马拉车算法板子题,只不过从回文关系变成了中心对称关系。
为了方便计数,可以插入无关数字 \(-1\) 来把奇数长度区间和偶数长度区间的情况统一化。
点击查看代码
cpp
#include <bits/stdc++.h>
#define inf32 1e9
#define inf64 2e18
#define int long long
#define ls o << 1
#define rs o << 1 | 1
using i64 = long long;
using u64 = unsigned long long;
using u32 = unsigned int;
const int N = 2e5 + 9;
void solve()
{
int n;std::cin >> n;
std::vector<int> a(n);
for(auto &x : a) {
std::cin >> x;
}
std::vector<int> b(2 * n + 1);
int idx = 0;
b[idx ++] = -1;
for(auto &x : a) {
b[idx ++] = x;
b[idx ++] = -1;
}
n = (int)b.size();
std::vector<int> d(n);
auto check = [&](int x, int y, int sum) -> bool {
if(x < 0 || y >= n) {
return false;
}
if(b[x] == -1 && b[y] == -1) {
return true;
}
if(b[x] + b[y] == sum) {
return true;
}
return false;
};
int ans = 0;
for(int i = 0, l = 0, r = -1;i < n;i ++) {
int k = ((i > r) ? 1 : std::min(r - i + 1, d[l + r - i]));
int now = -1;
if(b[i] == -1 && i - 1 >= 0 && i + 1 < n) {
now = b[i - 1] + b[i + 1];
}
if(b[i] != -1) {
now = b[i] * 2;
}
while(check(i - k, i + k, now)) {
k ++;
}
d[i] = k --;
if(i + k > r)l = i - k, r = i + k;
if(b[i] == -1) {
ans += k / 2;
} else {
ans += d[i] / 2;
}
}
std::cout << ans << '\n';
}
signed main()
{
std::ios::sync_with_stdio(0), std::cin.tie(0), std::cout.tie(0);
int t = 1;std::cin >> t;
while(t --)solve();
return 0;
}