2024 CCPC 全国邀请赛(郑州)暨河南省赛题解 更新至 10 题
Preface
这场单挑的,队友一个住院了一个睡过了。一个人打真的很容易红温。中间红温了好几场,A题更是没过,我的随机数写法(bushi卡在了23个点倒下了。
其他的题倒是不难,只能说中规中矩,就这个A题好恶心。
我会在代码一些有必要的地方加上注释,签到题可能一般就不会写了.
以下是代码火车头:
#include <iostream>
#include <algorithm>
#include <cstring>
#include <cmath>
#include <vector>
#include <set>
#include <queue>
#include <map>
#include <unordered_map>
#include <iomanip>
#include <functional>
#define endl '\n'
#define int long long
#define rep(i,a,b) for (int aa = a, bb = b, i = aa; i <= bb; i++)
#define rep2(i,a,b) for (int aa = a, bb = b, i = aa; i >= bb; i--)
using namespace std;
template<typename T>
void cc(const vector<T> &tem) {
for (const auto &x: tem) cout << x << ' ';
cout << endl;
}
template<typename T>
void cc(const T &a) { cout << a << endl; }
template<typename T1, typename T2>
void cc(const T1 &a, const T2 &b) { cout << a << ' ' << b << endl; }
template<typename T1, typename T2, typename T3>
void cc(const T1 &a, const T2 &b, const T3 &c) { cout << a << ' ' << b << ' ' << c << endl; }
void fileRead() {
#ifdef LOCALL
freopen("D:\\AADVISE\\Clioncode\\untitled2\\in.txt", "r", stdin);
freopen("D:\\AADVISE\\Clioncode\\untitled2\\out.txt", "w", stdout);
#endif
}
void kuaidu() { ios::sync_with_stdio(false), cin.tie(0), cout.tie(0); }
inline int max(int a, int b) {
if (a < b) return b;
return a;
}
inline double max(double a, double b) {
if (a < b) return b;
return a;
}
inline int min(int a, int b) {
if (a < b) return a;
return b;
}
inline double min(double a, double b) {
if (a < b) return a;
return b;
}
void cmax(int &a, const int &b) { if (b > a) a = b; }
void cmin(int &a, const int &b) { if (b < a) a = b; }
void cmin(double &a, const double &b) { if (b < a) a = b; }
void cmax(double &a, const double &b) { if (b > a) a = b; }
int gcd(int a,int b) {
if (!b) return a;
return gcd(b, a % b);
}
template<const int T>
int Kuai(int a,int b) {
int l = 1;
while (b) {
if (b % 2) l = l * a % T;
a = a * a % T, b /= 2;
}
return l;
}
template<typename T>
using vec = vector<T>;
using PII = pair<int, int>;
using INT = __int128;
Problem A. Once In My Life
我们直接构造一个最后的
//--------------------------------------------------------------------------------
const int N = 1e5 + 10;
const int M = 1e6 + 10;
const int mod = 1e9 + 7;
const int INF = 1e16;
int n, m, T;
//--------------------------------------------------------------------------------
//struct or namespace:
//--------------------------------------------------------------------------------
signed main() {
fileRead();
kuaidu();
T = 1;
cin >> T;
while (T--) {
cin >> n;
int d;
cin >> d;
string s;
int len = to_string(n).size();
s = "123456789";
s += to_string(d);
rep(i, 1, len) {
s = s + "0";
}
// cc(s);
int val = atoll(s.c_str());
// cc(val);
int k = val % n;
val += n - k;
cc(val / n);
}
return 0;
}
Problem B. 扫雷 1
我们显然想到,如果购买的东西一定是价格越来越高的,如果随着时间的增长,买到的价格变低了,我们肯定就能直接买那个价格低的了。
所以首先我们用一个
然后用一个cos来记录之前消耗的金币,物品的下标能够代表我们得到的金币数量,作差就是实际的金币数量。这个数量除以物品价格就是我们这次购买的数量了。
//--------------------------------------------------------------------------------
const int N = 2e5 + 10;
const int M = 1e6 + 10;
const int mod = 1e9 + 7;
const int INF = 1e16;
int n, m, T;
//--------------------------------------------------------------------------------
//struct or namespace:
//--------------------------------------------------------------------------------
int zuo[N];
map<int,int> mp;
signed main() {
fileRead();
kuaidu();
T = 1;
//cin >> T;
while (T--) {
cin >> n;
rep(i, 1, n) {
int a;
cin >> a;
mp[a] = i;
}
int ans = 0;
int r = 0;
int cos = 0;
for (auto [val,pos]: mp) {
if (pos - cos <= 0) continue;
int ad = (pos - cos) / val;
ans += ad;
cos += val * ad;
}
cc(ans);
}
return 0;
}
/*
*/
Problem C. 中二病也要打比赛
我们先想到一个很简单的地方,就是如果数字没有相同的话,那么其实就是一个很简单的最长上升子序列。
那么有相同的呢?
其实也很简单,看一组样例:5 2 7 8 5。
两边的数字都是相同的,你会发现,这一坨数字都需要变成一个数字才可以(不是一定要变成两端的),不然肯定不可以。
考虑联通块,那么这些数字就变成了联通块(注意,联通块代表的是数字的值,不是下标)。然后联通块里要选择出来其中的一个数字(也有可能是全部都变成一个新的)作为他们的代表。
接下来用括号来分割开来联通块:
(1 5 4 1)(8)(3 7 6 5 4 3)(10)
这些联通块里我们其实只需要各个选出各自的代表,来组成最后的上升子序列就好了。
此处我们不需要单独处理全部都变成一个新的的那种情况,这里给出的办法是:
把每一个联通块的数字从大到小排列出来(这一步是为了防止最长上升序列一个联通块里找到2个及以上),变成(5 4 1)(8)(7 6 5 4 3)(10)
然后对这个数组求最长上升子序列len,然后用这个数组的总长度减去len就是答案了。
//--------------------------------------------------------------------------------
const int N = 2e5 + 10;
const int M = 1e6 + 10;
const int mod = 1e9 + 7;
const int INF = 1e16;
int n, m, T;
//--------------------------------------------------------------------------------
//struct or namespace:
class DSU {
struct Info {
int fa;
int siz;
list<int> A;
} dsu[N];
public:
Info &operator()(const int &x) { return dsu[find(x)]; }
Info &operator[](const int &x) { return dsu[x]; }
void clear(int n) {
//TODO 初始化
rep(i, 1, n) dsu[i].fa = i, dsu[i].siz = 1, dsu[i].A.push_back(i);
}
void merge(int x, int y) {
x = find(x), y = find(y);
if (x == y) return;
dsu[y].fa = dsu[x].fa;
if (dsu[x].siz < dsu[y].siz) swap(dsu[x], dsu[y]);
dsu[y].fa = dsu[x].fa, dsu[x].siz += dsu[y].siz;
//TODO 合并操作
dsu[x].A.splice(dsu[x].A.end(), dsu[y].A);
dsu[y].A.clear();
}
int find(int x) { return x == dsu[x].fa ? x : dsu[x].fa = find(dsu[x].fa); }
bool same(int x, int y) { return (find(x) == find(y)); }
} dsu;
int A[N];
int you[N];
bool fl[N];
vec<PII> Q;
int D[N];
int dp[N];
//--------------------------------------------------------------------------------
int dfs(int *D,int n) {
int len = 0;
rep(i, 1, n) {
if (i == 1) {
dp[++len] = D[i];
continue;
}
if (D[i] >= dp[len]) {
dp[++len] = D[i];
continue;
}
else {
int x = lower_bound(dp + 1, dp + len, D[i]) - dp;
dp[x] = D[i];
}
}
return len;
}
signed main() {
fileRead();
kuaidu();
T = 1;
//cin >> T;
while (T--) {
cin >> n;
dsu.clear(n);
rep(i, 1, n) fl[i] = 0;
rep(i, 1, n) {
cin >> A[i];
you[A[i]] = i;
}
rep(i, 1, n) {
if (fl[A[i]]) continue;
fl[A[i]] = 1;
Q.push_back({i, you[A[i]]});
}
Q.push_back({n + 1, n + 1});
int l = 0, r = 0;
for (auto &[l1,r1]: Q) {
if (l == 0 and r == 0) {
l = l1, r = r1;
continue;
}
if (l1 <= r) {
cmax(r, r1);
continue;
}
else {
rep(j, l, r) {
dsu.merge(A[l], A[j]);
}
l = l1, r = r1;
}
}
rep(i, 1, n) {
fl[i] = 0;
}
int len = 0;
rep(i, 1, n) {
int fa = dsu(A[i]).fa;
// cc(A[i], fa);
if (fl[fa]) continue;
fl[fa] = 1;
list<int> &son = dsu[fa].A;
priority_queue<int> F;
for (auto &x: son) F.push(x);
while (!F.empty()) {
auto val = F.top();
F.pop();
D[++len] = val;
}
}
// rep(i, 1, len) {
// cout << D[i] << " ";
// }
cc(len - dfs(D, len));
}
return 0;
}
/*
*/
Problem D. 距离之比
老实说这个题不应该写的那么久,最开始总觉得需要用个线段树去维护,脑子越写越乱,都不知道自己在写什么了。
也是因为被当时A红温的脑子都不着遍了,后来直接全删重写,脑子清醒了不少。然后一发过了,舒服。
首先通过打表,发现出来,我们要找那种两个点组成的斜率是
那现在我们可以考虑枚举每一个点,找到对于这个点来说
尝试写一下直线解析式:
怎么找出来最近的呢?把点到直线的距离公式写出来,发现变化的只有
后面细节的地方以及实现就写在代码里了:
//--------------------------------------------------------------------------------
//struct or namespace:
struct node {
int val;
int x;
int y;
};
node cha[N];
node he[N];
//--------------------------------------------------------------------------------
double dfs(int x1,int y1,int x2,int y2) {
int a = abs(x1 - x2);
int b = abs(y1 - y2);
return (a + b) * 1.0 / (pow(a * a + b * b, 0.5));
}
//第一个大于等于k
int efda(int k, node *A) {
int l = 0, r = n + 1;
while (l + 1 != r) {
int mid = (l + r) / 2;
if (A[mid].val < k) l = mid;
else r = mid;
}
return r;
}
//最后一个小于等于k
int efxi(int k, node *A) {
int l = 0, r = n + 1;
while (l + 1 != r) {
int mid = (l + r) / 2;
if (A[mid].val <= k) l = mid;
else r = mid;
}
return l;
}
signed main() {
fileRead();
kuaidu();
T = 1;
cin >> T;
while (T--) {
cin >> n;
rep(i, 1, n) {
cin >> cha[i].x >> cha[i].y;
cha[i].val = cha[i].x - cha[i].y;//用来排序的,因为要找最值,需要二分,因此数组需要有序。
he[i].x = cha[i].x, he[i].y = cha[i].y;//cha和he数组一个意义,代表135度
he[i].val = -he[i].x - he[i].y;
}
sort(cha + 1, cha + n + 1, [](node q1, node q2) {
return q1.val < q2.val;
});
sort(he + 1, he + n + 1, [](node q1, node q2) {
return q1.val < q2.val;
});
double ans = 0;
rep(i, 1, n) {
int da, xiao;
int b = cha[i].y - cha[i].x;
//枚举的是cha数组,二分也是在cha数组,da是返回的符合要求的下标,如果等于i了要+1。
da = efda(-b, cha);
if (da == i) da += 1;
if (da <= n) cmax(ans, dfs(cha[i].x, cha[i].y, cha[da].x, cha[da].y));
xiao = efxi(-b, cha);//这个也是同理
if (xiao == i) xiao -= 1;
if (xiao >= 1) cmax(ans, dfs(cha[i].x, cha[i].y, cha[xiao].x, cha[xiao].y));
}
//对45度做的事情,也对135度再来一遍就好了,直接复制粘贴
rep(i, 1, n) {
int da, xiao;
int b = he[i].y + he[i].x;
da = efda(-b, he);
if (da == i) da += 1;
if (da <= n) cmax(ans, dfs(he[i].x, he[i].y, he[da].x, he[da].y));
xiao = efxi(-b, he);
if (xiao == i) xiao -= 1;
if (xiao >= 1) cmax(ans, dfs(he[i].x, he[i].y, he[xiao].x, he[xiao].y));
}
cout << fixed << setprecision(10) << ans << endl;
}
return 0;
}
/*
*/
Problem F. 优秀字符串
直接按照题意,模拟就好了。没有可以讲的。
//--------------------------------------------------------------------------------
const int N = 1e5 + 10;
const int M = 1e6 + 10;
const int mod = 1e9 + 7;
const int INF = 1e16;
int n, m, T;
//--------------------------------------------------------------------------------
//struct or namespace:
//--------------------------------------------------------------------------------
signed main() {
fileRead();
kuaidu();
T = 1;
//cin >> T;
while (T--) {
cin >> n;
int sum = 0;
rep(i, 1, n) {
string s;
cin >> s;
if (s.size() != 5) continue;
if (s[2] != s[4]) continue;
int fl = 0;
rep(j, 0, 3)
rep(p, j+1, 3) {
if (s[j] == s[p]) fl = 1;
}
if (fl) continue;
sum++;
}
cc(sum);
}
return 0;
}
/*
*/
Problem H. 随机栈
直接暴力枚举,也没有什么难的。用一个
记得找出来begin的次数要
还有一点,要记录下来我们取出来的数字的最大值,和当前这次取出来的数字做比较,如果比我们的最大值小,那就说明概率一定是0.(因为不会构成递增)
如果大的话,就概率乘上这个数字的次数/容器里的总数。
这里用了一个自动取模类,不用的话,这里要去学逆元。
//--------------------------------------------------------------------------------
const int N = 2e5 + 10;
const int M = 1e6 + 10;
const int mod = 1e9 + 7;
const int INF = 1e16;
int n, m, T;
//--------------------------------------------------------------------------------
//struct or namespace:
template<const int T>
struct ModInt {
const static int mod = T;
int x;
ModInt(int x = 0) : x(x % mod) {
}
int val() { return x; }
ModInt operator +(const ModInt &a) const {
int x0 = x + a.x;
return ModInt(x0 < mod ? x0 : x0 - mod);
}
ModInt operator -(const ModInt &a) const {
int x0 = x - a.x;
return ModInt(x0 < 0 ? x0 + mod : x0);
}
ModInt operator *(const ModInt &a) const { return ModInt(1LL * x * a.x % mod); }
ModInt operator /(const ModInt &a) const { return *this * a.inv(); }
bool operator ==(const ModInt &a) const { return x == a.x; };
bool operator !=(const ModInt &a) const { return x != a.x; };
void operator +=(const ModInt &a) {
x += a.x;
if (x >= mod) x -= mod;
}
void operator -=(const ModInt &a) {
x -= a.x;
if (x < 0) x += mod;
}
void operator *=(const ModInt &a) { x = 1LL * x * a.x % mod; }
void operator /=(const ModInt &a) { *this = *this / a; }
friend ModInt operator +(int y, const ModInt &a) {
int x0 = y + a.x;
return ModInt(x0 < mod ? x0 : x0 - mod);
}
friend ModInt operator -(int y, const ModInt &a) {
int x0 = y - a.x;
return ModInt(x0 < 0 ? x0 + mod : x0);
}
friend ModInt operator *(int y, const ModInt &a) { return ModInt(1LL * y * a.x % mod); }
friend ModInt operator /(int y, const ModInt &a) { return ModInt(y) / a; }
friend ostream &operator<<(ostream &os, const ModInt &a) { return os << a.x; }
friend istream &operator>>(istream &is, ModInt &t) { return is >> t.x; }
ModInt pow(int n) const {
ModInt res(1), mul(x);
while (n) {
if (n & 1) res *= mul;
mul *= mul;
n >>= 1;
}
return res;
}
ModInt inv() const {
int a = x, b = mod, u = 1, v = 0;
while (b) {
int t = a / b;
a -= t * b;
swap(a, b);
u -= t * v;
swap(u, v);
}
if (u < 0) u += mod;
return u;
}
};
using mo = ModInt<998244353>;
map<int,int> mp;
//--------------------------------------------------------------------------------
signed main() {
fileRead();
kuaidu();
T = 1;
//cin >> T;
while (T--) {
cin >> n;
mo ans = 1;
int sum = 0;
int mmax = -INF;
rep(i, 1, n+n) {
int a;
cin >> a;
if (a == -1) {
auto [val,cnt] = *mp.begin();
mo de = cnt;
de /= sum;
ans *= de;
if (val < mmax) ans = 0;
cmax(mmax, val);
sum -= 1;
(*mp.begin()).second -= 1;
if ((*mp.begin()).second == 0) mp.erase(mp.begin());
}
else {
mp[a]++;
sum++;
}
}
cc(ans);
}
return 0;
}
/*
*/
Problem J. 排列与合数
直接暴力枚举dfs就好了,记得要预处理出来质数,这样可以O1判断出来是不是合数。
//--------------------------------------------------------------------------------
const int N = 1e5 + 10;
const int M = 1e6 + 10;
const int mod = 1e9 + 7;
const int INF = 1e16;
int n, m, T;
//--------------------------------------------------------------------------------
//struct or namespace:
namespace pri {
const int N = 1e5 + 10;;
vector<int> pri;
bool ispri[N], biao[N];
void cal_pri() {
for (int i = 2; i < N; i++) {
if (biao[i]) continue;
pri.push_back(i);
for (int j = i; j < N; j += i) biao[j] = 1;
}
for (auto &x: pri) ispri[x] = 1;
}
void clear() {
rep(i, 0, N-1) {
ispri[i] = 0;
biao[i] = 0;
}
}
}
//--------------------------------------------------------------------------------
int cnt[10];
int A[10];
bool fl[10];
string s;
bool cal() {
int val = 0;
if (A[1] == 0) return 0;
rep(i, 1, 5) {
val = val * 10 + A[i];
}
if (pri::ispri[val]) return 0;
cc(val);
return 1;
}
bool dfs(int x) {
if (x == 6) {
return cal();
}
for (int i = 0; i < 5; i++) {
if (fl[i]) continue;
fl[i] = 1;
A[x] = s[i] - '0';
if (dfs(x + 1)) return 1;
fl[i] = 0;
}
return 0;
}
signed main() {
fileRead();
kuaidu();
pri::clear();
pri::cal_pri();
// cc(pri::ispri[4]);
T = 1;
cin >> T;
while (T--) {
cin >> s;
rep(i, 0, 9) {
fl[i] = 0;
A[i] = 0;
}
if (dfs(1) == 0) {
cc(-1);
}
}
return 0;
}
/*
*/
Problem K. 树上问题
非常典型的一个树上dp,非常板子感觉。
此处我们定义一个逆边代表子节点和父节点的那个边不满足题干说的那个关系(也就是子节点小于了父节点的二分之一)。
fx数组代表以这个点为子树的有多少条逆的边。那么fx维护很简单,fy求和,然后再判断一下x和y是否是逆边,是的话就+1.
gx数字代表以这个点为子树的有多少条逆边,那么gy=gx减去了fy(也就是先减去y的子树的干扰)再减去x和y之间是否是逆边,这样现在得到的就是x去掉了y这个子树的逆边的和。然后再加上fy和y与x之间是否存在逆边(注意这里和刚才提到的逆边是反着的),再加上fy,便是gy了。
其实直接通过gy是不是等于0,就能知道是不是满足要求的魅力点。
然后就出来了。
//--------------------------------------------------------------------------------
const int N = 1e5 + 10;
const int M = 1e6 + 10;
const int mod = 1e9 + 7;
const int INF = 1e16;
int n, m, T;
//--------------------------------------------------------------------------------
//struct or namespace:
namespace z {
struct ED {
int y;
int val;
};
vector<ED> A[N];
int son[N], dep[N];
int f[N], g[N];
int val[N];
bool fl[N];
bool pan(int x,int y) {
if (2 * val[y] < val[x]) return 1;
return 0;
}
void dfs(const int x, const int pa) {
f[x] = 0;
for (auto &[y, val]: A[x]) {
if (y == pa) continue;
dfs(y, x);
f[x] += f[y];
f[x] += pan(x, y);
}
}
void pass() {
g[1] = f[1];
if (g[1] == 0) fl[1] = 1;
else fl[1] = 0;
}
void dfs2(const int x, const int pa) {
for (auto &[y, val]: A[x]) {
if (y == pa) continue;
g[y] = f[y] + pan(y, x) + (g[x] - f[y] - pan(x, y));
fl[y] = 1;
if (f[y]) fl[y] = 0;
else if (pan(y, x)) fl[y] = 0;
else if (g[x] - f[y] - pan(x, y)) fl[y] = 0;
dfs2(y, x);
}
}
void clear(const int &n) {
rep(i, 1, n) {
A[i].clear();
}
}
void add(const int &x, const int &y, int c = 1) {
A[x].push_back({y, c});
}
};
//--------------------------------------------------------------------------------
signed main() {
fileRead();
kuaidu();
T = 1;
cin >> T;
while (T--) {
cin >> n;
z::clear(n);
rep(i, 1, n) {
cin >> z::val[i];
}
rep(i, 1, n-1) {
int a, b;
cin >> a >> b;
z::add(a, b);
z::add(b, a);
}
z::dfs(1, 0);
z::pass();
z::dfs2(1, 0);
int ans = 0;
rep(i, 1, n) {
ans += z::fl[i];
}
cc(ans);
}
return 0;
}
/*
*/
Problem L. Toxel 与 PCPC II
非常ez的一个
写出dp式子:
这样是
所以时间复杂度就少了,可以满足。
//--------------------------------------------------------------------------------
const int N = 2e5 + 10;
const int M = 1e6 + 10;
const int mod = 1e9 + 7;
const int INF = 1e18;
int n, m, T;
int A[N];
int dp[N];
//--------------------------------------------------------------------------------
//struct or namespace:
//--------------------------------------------------------------------------------
signed main() {
fileRead();
kuaidu();
T = 1;
//cin >> T;
while (T--) {
cin >> n >> m;
rep(i, 1, m) {
cin >> A[i];
}
dp[1] = A[1] + 1;
rep(i, 2, m) {
dp[i] = INF;
rep(j, max(0,i-500), i-1) {
cmin(dp[i], dp[j] + (i - j) * (i - j) * (i - j) * (i - j) + A[i]);
}
}
cc(dp[m]);
}
return 0;
}
/*
*/
Problem M. 有效算法
我们可以推出来,
我们直接二分,判断
这里判断交集写麻烦了,不需要
然后二分的边界直接无脑了,也没有计算具体开多大。
//--------------------------------------------------------------------------------
const int N = 3e5 + 10;
const int M = 1e6 + 10;
const int mod = 1e9 + 7;
const int INF = 1e16;
int n, m, T;
//--------------------------------------------------------------------------------
//struct or namespace:
//--------------------------------------------------------------------------------
int C[N], D[N];
PII tem[N];
bool check(int mid) {
rep(i, 1, n) {
tem[i].first = C[i] - mid * D[i];
tem[i].second = C[i] + mid * D[i];
}
sort(tem + 1, tem + n + 1, [](PII q1, PII q2) {
if (q1.first == q2.first) return q1.second < q2.second;
return q1.first < q2.first;
});
auto [l,r] = tem[1];
if (l > r) return false;
rep(i, 2, n) {
auto [l1,r1] = tem[i];
if (l1 > r1) return false;
cmax(l, l1), cmin(r, r1);
if (l > r) return false;
}
return true;
}
signed main() {
fileRead();
kuaidu();
T = 1;
cin >> T;
while (T--) {
cin >> n;
rep(i, 1, n) {
C[i] = 0, D[i] = 0;
}
rep(i, 1, n) {
int a;
cin >> a;
C[i] = a;
}
rep(i, 1, n) {
int b;
cin >> b;
D[i] = b;
}
int l = -1, r = 1 * 1000000000 + 10;
while (l + 1 != r) {
int mid = (l + r) / 2;
if (check(mid)) r = mid;
else l = mid;
}
cc(r);
}
return 0;
}
PostScript
这场被数学折磨的难受,后面的几道没开,也没有多少人做,唯一一个过的多一点的看了下,貌似是个很恶心的大型分类讨论,不是我所能触及的。
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 分享一个免费、快速、无限量使用的满血 DeepSeek R1 模型,支持深度思考和联网搜索!
· 基于 Docker 搭建 FRP 内网穿透开源项目(很简单哒)
· ollama系列01:轻松3步本地部署deepseek,普通电脑可用
· 25岁的心里话
· 按钮权限的设计及实现