2024ccpc中国大学生程序设计竞赛(郑州站)
\(\Huge{2024ccpc中国大学生程序设计竞赛(郑州全国邀请赛)}\)
\(\huge{Problems~A、B、F、H、J、K、L、M}\)
写在前面...破铜烂铁选手,这次拿到了邀请赛的铜牌🥉。再接再励!
补题环节...
Problem A. Once In My Life
题意
给定两个整数\(n,d\),然后要求构造一个数字\(k\),要求\(n\times k\)的值的数位中包含\(0...9\)至少一次,并且\(d(1\le d\le 9)\)至少两次。
思路
赛时的一道签到题,可是过题数好少。
我们按照顺序来构造即可:
- 我们考虑先构造出\(N=n \times k\),那么\(1234567890+d\)即符合题意。
- 然后我们考虑在\(N\)后面加上若干位数字使得在不改变\(N\)的前面\(10\)位的情况下能够被\(n\)整除。
- 上一步的具体方法为:
- 让\(N\)左移\(n\)的位数位,然后加上\(n\)(把\(n\)放在\(N\)后边),然后减去\(N\%n\),就可以被\(n\)整除了。
标程
void Solved() {
int n, d; cin >> n >> d;
int len = to_string(n).size();
int luck = (1234567890 + d) * pow(10, len);
luck += n;
luck -= luck % n;
cout << luck / n << endl;
}
Problem B. 扫雷 1
题意
进行\(n\)轮游戏,每轮会获得一个扫雷币,每轮可以买地雷探测器,给出每轮的地雷探测器的价格,求最多能买多少个地雷探测器?
思路
可以维护一个单调队列,每次存这位置地雷探测器的价格和下标。在单调队列里第\(i\)个位置下标前攒的扫雷币都可以用这个价格来买。
标程
#define int long long
#define fi first
#define se second
void Solved() {
int n; cin >> n;
vector<PII> a;
for(int i = 1; i <= n; i ++ ) {
int x; cin >> x;
while(!a.empty() && a.back().fi >= x) a.pop_back();
a.push_back({x, i});
}
int res = a[0].se / a[0].fi;
int t = a[0].se % a[0].fi, len = a.size();
for(int i = 1; i < len; i ++ ) {
res += (a[i].se - a[i - 1].se + t) / a[i].fi;
t = (a[i].se - a[i - 1].se + t) % a[i].fi;
}
cout << res << endl;
}
Problem F. 优秀字符串
题意
给出优秀字符串的定义:
- 长度为5。
- 第三个字符和第五个字符相同。
- 前四个字符互不相同。
求优秀字符串个数。
思路
签到题,模拟即可。
标程
void Solved() {
int n; cin >> n;
int res = 0;
for(int i = 1; i <= n; i ++ ) {
string s; cin >> s;
if(s.size() != 5) continue;
if(s[2] != s[4]) continue;
bool f = 1;
for(int i = 0; i < 4; i ++ )
for(int j = i + 1; j < 4; j ++ )
if(s[i] == s[j]) f = 0;
res += f;
}
cout << res << endl;
}
Problem H. 随机栈
题意
题目给出\(2n\)次操作,每次操作有两种情况:
- \(-1\):从当前集合中取出一个数。
- 非\(-1\):将当前数字放入集合中。
两种情况各\(n\)次,求最后取出的数字数组为递增(小于等于后一项)的概率,概率\(\frac{p}{q}\)表示为:\(p\times q^{-1} mod~~998244353\)。
思路
题目要求输出的数字数组为递增,我们可以通过贪心策略每次只取当前集合中最小的数字;如果当前的最小数字小于前面已选择的数字,那么将不可能构造出升序序列,概率为\(0\)。
题中对应的两种操作我们可以通过大根堆和\(map\)实现。
但是这道题的一个难点是在求概率上:
-
容易想到,概率中的分子\(p\)即为当前集合中最小数的个数;分母\(q\)即为当前集合中的数字个数。
-
由于概率需要取模,所以需要用到乘法逆元。
-
在循环模拟的过程中,分子\(p\)和分母\(q\)会非常大,但是我们如果在循环中直接求逆元,会超时。
-
可以在循环过程中将分子分母分别保存,然后在循环外求逆元即可。
标程
const int mod = 998244353;
int quick_mi(int a,int b) {
int ans = 1;
while(b) {
while(b % 2 == 0)
a = a * a % mod, b = b / 2;
ans = ans * a % mod; b = b - 1;
}
return ans ;
}
void solve() {
int n; cin >> n;
for(int i = 1; i<= 2 * n; i++){
cin >> arr[i];
}
priority_queue<int,vector<int>,greater<int>> que;
int maxx = 0;
vector<int> z, m;
for(int i = 1; i <= 2 * n; i++){
if(arr[i] > -1){
que.push(arr[i]); mp[arr[i]] ++;
} else {
int temp = que.top();
if(temp < maxx){
cout << "0" << endl; return;
}
maxx = max(maxx,temp);
z.push_back(mp[temp]); m.push_back(que.size());
que.pop(); mp[temp]--;
}
}
int ans = 1;
for(int i : z){
ans *= i; ans %= mod;
}
for(int i : m){
ans = ans * quick_mi(i, mod - 2); ans = ans % mod;
}
cout << ans <<endl;
}
Problem J. 排列与合数
题意
给出一个五位整数,然后将其每位重新排列,组成一个合数并输出;如果无法构造,则输出-1。
思路
签到题
构造合数只需将其中的合数位放在最后即可(注意前导零的情况)。
但是如果没有合数的情况呢?
题目样例中已经给出,五位都是奇数的情况直接输出97531即可。
所以说没有\(-1\)的情况,不用考虑。
标程
void Solved() {
string s; cin >> s;
deque<int> dq;
int sum = 0;
for(int i = 0; i < 5; i ++ ) {
int x = s[i] - '0';
if(x & 1) dq.push_front(x), sum ++;
else dq.push_back(x);
}
if(sum == 5) {
cout << "97531\n";
} else {
for(int i : dq) cout << i; cout << endl;
}
}
Problem K. 树上问题
题意
给出一个由\(n\)各节点组成的无根树,编号为\(1...n\),每个节点有一个正整数点权a[i]。
现在定义美丽节点:如果一个节点作为根节点,当其他所有节点的点权都不小于其父节点点权的\(\frac{1}{2}\)时,当前根节点为美丽节点。
思路
考虑从边入手:
-
若x与y之间有边,那么共有两种情况:
- \(a[x] \times 2 < a[y]\),将\(x\)看作子节点,那么y及其所有祖宗节点都不为美丽节点。
- \(a[x] \times 2 > a[y]\),将\(y\)看作子节点,那么x及其所有祖宗节点都不为美丽节点。
-
所以我们只需遍历所有边,并且将上述所有情况的祖宗节点标记即可,最后没有被标记的即为美丽节点。
-
直接遍历会导致超时,通过观察会发现,被标记过的点的祖宗节点必定被标记,不需要再次进行标记,在循环的时候可以提前返回。
-
但是剪枝后会出现如下情况:
-
1
3
3 1 1
1 2
1 3 -
这种情况是因为可行解有超过一个父节点,不符合树的定义。这种情况需要记录每个节点的父节点,然后进行判断。
标程
vector<int> a(N), b[N], fa;
vector<bool> f;
vector<PII> edge;
int n, flag = 1;
void init() {
f.clear(); f.resize(n + 1);
fa.clear(); fa.resize(n + 1);
edge.clear(); flag = 1;
for(int i = 1; i <= n; i ++ ) b[i].clear(), fa[i] = -1;
}
void biaoji(int x) {
if(f[x] || fa[x] == -1) return;
f[x] = 1;
for(auto i : b[x]) {
if(i == fa[x]) continue;
if(fa[i] != -1 && fa[i] != x) {flag = 0; return;}
fa[i] = x;
biaoji(i);
}
}
void Solved() {
cin >> n;
init();
for(int i = 1; i <= n; i ++ ) cin >> a[i];
for(int i = 1; i < n; i ++ ) {
int x, y; cin >> x >> y;
b[x].push_back(y); b[y].push_back(x);
if(a[x] * 2 < a[y]) { //把x当作子节点,y当作父节点
if(fa[y] != -1) flag = 0;
fa[y] = x;
}
if(a[y] * 2 < a[x]){ //把y当作子节点,x当作父节点
if(fa[x] != -1) flag = 0;
fa[x] = y;
}
}
for(int i = 1; i <= n; i ++ ) {
biaoji(i);
}
int sum = 0;
for(int i = 1; i <= n; i ++ ) {
if(!f[i]) sum ++; //未被标记的即为美丽节点
}
if(flag == 0) cout << "0\n";
else cout << sum << endl;
}
Problem L. Toxel与PCPC II
题意
有一份长度为\(n\)的代码,一共有\(m\)行有错,并且给出有bug的行标,修复bug的规则为:
每次能选择一个数字\(i\),然后修复前\(i\)行的所有bug,设前\(i\)行的bug数为\(x\),则本次需要花费的时间为:\(t_i=i+x^4\)。
求最少的修复所有bug时间。
思路
这道题我们赛时通过数据范围pass了dp的思路,然后考虑贪心,后来发现不太对。
考虑用dp思路:
我们可以用\(f[i]\)表示修复前i个bug需要的最短时间,那么状态转移方程为:\(f[i]=\min_{1\le j\le i}(f[j]+a[i]+(i-j)^4)\)
但是\(O(n^2)\)的时间复杂度是无法通过的,我们考虑优化:
- \(21^4=194481,22^4=234256\)
- \(21^4<2e5\),是可能会被优化的,但是\(22^4>2e5\)不会被优化。所以说是不会出现同时修复超过22处bug的情况。
- 所以我们可以将第二维枚举的范围改为22就行,那么时间复杂度将优化为\(O(n\sqrt[4]{n})\)。
标程
#define int long long
vector<int> a(N), f(N, LONG_MAX);//init
void Solved() {
int n, m; cin >> m >> n;
for(int i = 1; i <= n; i ++ ) {
cin >> a[i];
}
f[0] = 0;
f[1] = a[1] + 1ll;//init
auto pow4 = [](int x)->int {return x * x * x * x;};
for(int i = 2; i <= n; i ++ ) {
int j = 1;
if(i - j + 1 >= 22) j = i - 21;//注意边界
for(; j <= i; j ++ )
f[i] = min(f[i], f[j - 1] + a[i] + pow4(i - j + 1));
}
cout << f[n] << endl;
}
Problem M. 有效算法
题意
给出两个长度为\(n\)的数组\(a,b\),要求对每个\(a_i\)进行以下操作正好一次:
- 将\(a_i\)变成满足\(|a_i−x|\le k\times b_i\)的任意整数\(x\)。
求出最小的非负整数\(k\),使得存在一个\(x\)能够将操作后的\(a\)数组按位等于\(b\)数组。
思路
题目的数据范围比较大\((2\le n \le 3 \times 10^5)\)。
根据数据范围猜测,这道题只能在\(O(nlog_n)\)的时间复杂度以内过掉。
然后看题,题目要求找出最小的\(k\),\(k\)能够确定出\(a_i\)变化的范围;那么\(k\)必定是有序的,即若\(k\)可行,那么\(k+1...\)也必定可行。
根据题目中的操作,我们可以将其分解为:
- \(a_i \ge x\):原不等式可化为:\(a_i-k\times b_i \le x\)。
- \(a_i \le x\):原不等式可化为:\(k \times b_i+a_i \ge x\)。
因此可以求出\(x\)的区间,若区间合法,则\(k\)满足要求,否则不满足要求。
标程
#define int long long
vector<int> a, b;
int n;
bool check(int k) {
int mi = 0, mx = LONG_MAX;
for(int i = 1; i <= n; i ++ ) {
mi = max(mi, a[i] - k * b[i]);
mx = min(mx, k * b[i] + a[i]);
if(mi > mx) return false;
}
return true;
}
void Solved() {
cin >> n;
a.resize(n + 1); b.resize(n + 1);
for(int i = 1; i <= n; i ++ ) cin >> a[i];
for(int i = 1; i <= n; i ++ ) cin >> b[i];
int l = 0, r = 1e9, mid;
while(l < r) {
mid = l + r >> 1;
if(check(mid)) r = mid;
else l = mid + 1;
}
cout << l << endl;
}

浙公网安备 33010602011771号