第十六届蓝桥杯c++研究生组省赛题解
前言
由于没地方提交代码,不保证代码正确,重点看思路
A 签到
B IPv6
考虑每个段按前导零个数区分就5种情况,算出每种情况的种数(如0个前导0的情况为15x16x16x16),所以最后复杂度为5的八次方,最后计算长度注意相邻的全零可以省略。(思路感觉没问题,代码写的不一定对)
#include<bits/stdc++.h>
using namespace std;
#define ctz __builtin_ctzll
#define ppc __builtin_popcountll
#define par __builtin_parityll
#define all(x) (x).begin(), (x).end()
using LL = long long;
using ull = unsigned long long;
using db = double;
using ldb = long double;
const int INF = 1e9;
const db EPS = 1e-8;
const db PI = acos(-1);
const int mod = 1e9 + 7;
int get(int x) {
if(x == 0) return 1; // 0000
if(x == 1) return 15 * 16 * 16 * 16; // x...
if(x == 2) return 15 * 16 * 16; // 0x..
if(x == 3) return 15 * 16; // 00x.
return 15; // 000x
}
int len(int x) {
if(x == 0) return 1; // 0000
if(x == 1) return 4; // x...
if(x == 2) return 3; // 0x..
if(x == 3) return 2; // 00x.
return 1; // 000x
}
int main() {
ios::sync_with_stdio(false), cin.tie(nullptr);
cout << fixed << setprecision(15);
long long ans = 0;
for(int i = 0; i < pow(5, 8); i ++) {
vector<int> bit(8);
int x = i, cur_len = 7;
//计算每段压缩长度
for(int j = 0; j <8; j ++) {
bit[j] = x % 5;
cur_len += len(bit[j]);
x /= 5;
}
int mx = 0;
int l = -1;
//多个连续的全零可压缩
for(int j = 0; j < 8; j ++) {
if(bit[j] == 0) {
if(j - l + 1 >= 2) {
int sum = (j - l + 1) * 2 - 1;
if (l == 0) sum--;
if (j == 7) sum--;
mx = max(mx, sum);
}
} else {
l = j + 1;
}
}
cur_len -= mx;
//乘上每种的情况数
for(int j = 0; j < 8; j ++) {
cur_len = (1ll * cur_len * get(bit[j])) % mod;
}
ans = (ans + cur_len) % mod;
}
cout << ans;
return 0;
}
C签到
D最大数字
直接先x+y>y+x排序,然后二进制转十进制,注意使用高精度, 一位一位算只能过80%,我这里是30位一取(比一位快,但不知道能不能过)
#include<bits/stdc++.h>
using namespace std;
#define ctz __builtin_ctzll
#define ppc __builtin_popcountll
#define par __builtin_parityll
#define all(x) (x).begin(), (x).end()
using LL = long long;
using ull = unsigned long long;
using db = double;
using ldb = long double;
const int INF = 1e9;
const db EPS = 1e-8;
const db PI = acos(-1);
const int mod = 1e9 + 7;
const int maxn = 1e7 + 10;
long long now[maxn], sum[maxn];
int main() {
ios::sync_with_stdio(false), cin.tie(nullptr);
cout << fixed << setprecision(15);
int n;
cin >> n;
vector<string> a(n);
for(int i = 1; i <= n; i ++) {
string t;
int x = i;
while(x) {
t += char(x % 2 + '0');
x /= 2;
}
reverse(t.begin(), t.end());
a[i - 1] = t;
}
sort(a.begin(), a.end(),[&](string &s, string &t){
return s + t > t + s;
});
string ans;
for(int i = 0; i < n; i ++) {
ans += a[i];
}
int pk = 0;
for(int i = 0; i < ans.size(); i += 30) {
int cur = 0, pre = 1;
for(int j = i; j < min((int)ans.size(), i + 30); j ++) {
cur = cur * 2 + (ans[j] - '0');
pre *= 2;
}
for(int j = 0; j <= pk; j ++) {
now[j] *= pre;
}
now[0] += cur;
for(int j = 0; j <= pk; j ++) {
now[j + 1] += now[j] / 10;
now[j] %= 10;
}
while(now[pk + 1] != 0) {
pk ++;
now[pk + 1] += now[pk] / 10;
now[pk] %= 10;
}
for(int j = 0; j <= pk; j ++) {
sum[j] += now[j];
}
}
for(int i = 0; i <= pk; i ++){
sum[i + 1] += sum[i] / 10;
sum[i] %= 10;
}
while(sum[pk + 1] != 0) {
pk ++;
sum[pk + 1] += now[pk] / 10;
sum[pk] %= 10;
}
for(int i = pk; i >= 0; i --) {
cout << sum[i];
}
return 0;
}
E冷热队列
用两个set模拟,用被访问的时间作为关键字排序,记录每个元素当前在的队列,然后按照题意模拟即可。
#include<bits/stdc++.h>
using namespace std;
#define ctz __builtin_ctzll
#define ppc __builtin_popcountll
#define par __builtin_parityll
#define all(x) (x).begin(), (x).end()
using LL = long long;
using ull = unsigned long long;
using db = double;
using ldb = long double;
const int INF = 1e9;
const db EPS = 1e-8;
const db PI = acos(-1);
const int mod = 1e9 + 7;
const int maxn = 1e5 + 10;
int type[maxn], tim[maxn];
int main() {
ios::sync_with_stdio(false), cin.tie(nullptr);
cout << fixed << setprecision(15);
int n1, n2;
cin >> n1 >> n2;
set<pair<int, int>> q1, q2;
int m;
cin >> m;
for(int i = 1; i <= m; i ++){
int x;
cin >> x;
if(type[x] == 0) {
if(q2.size() == n2) {
auto it = *q2.begin();
q2.erase(q2.begin());
type[it.second] = 0;
tim[it.second] = 0;
}
q2.insert({i, x});
type[x] = 2;
tim[x] = i;
} else if(type[x] == 1) {
q1.erase({tim[x], x});
q1.insert({i, x});
tim[x] = i;
} else if(type[x] == 2) {
q2.erase({tim[x], x});
if(q1.size() == n1) {
auto it = *q1.begin();
q1.erase(q1.begin());
q2.insert(it);
type[it.second] = 2;
tim[it.second] = i;
}
q1.insert({i, x});
tim[x] = i;
type[x] = 1;
}
}
for(auto it = q1.rbegin(); it != q1.rend(); it ++) {
cout << (*it).second << " ";
}
cout << '\n';
for(auto it = q2.rbegin(); it != q2.rend(); it ++) {
cout << (*it).second << " ";
}
return 0;
}
F01串(对拍已过)
我写的很乱,推出二进制长度为1到k的数拼成的长度以及1的个数,这部分答案去掉之后只需计算二进制长度为k+1的数的答案;由于后面的数拼上去的第一位都是1,只需考虑后面的位,假如后面还拼了y个数,及问题转化为0~y-1二进制中1的个数,类似于开始的推导计算。注意最后一个数可能拼不完,需要单独计算。
#include<bits/stdc++.h>
using namespace std;
#define ctz __builtin_ctzll
#define ppc __builtin_popcountll
#define par __builtin_parityll
#define all(x) (x).begin(), (x).end()
using LL = long long;
using ull = unsigned long long;
using db = double;
using ldb = long double;
const int INF = 1e9;
const db EPS = 1e-8;
const db PI = acos(-1);
const int mod = 1e9 + 7;
//long long check1(long long x) {
// x --;
// if(x == 0) {
// cout << 0 << '\n';
// return 0;
// }
//
// int k = 0;
// long long ans = 0;
// while(x >= (1ll << k) * (k + 1)) { // 长度为k的二进制个数为2^(k - 1)
// x -= (1ll << k) * (k + 1); // 减去长度为k的二进制个数数量
// ans += (1ll << k) + 1ll * k * (1ll << k) / 2; // 第一位全为1有2^(k-1)个,后面每位的次数为2^(k-2),有k-1位,和位(k-1)*2^(k-2)
// k ++;
// }
//
// //只有k+1的了
// long long cnt = x / (k + 1);
// int cur = x % (k + 1);
//
// x -= cnt * (k + 1);
//
// ans += cnt; // 第一位都为1
// long long now = powl(2, k) + cnt;
// // 求0~cnt-1中有多少个1
// for(int i = 60; i >= 0; i --) {
// if((cnt >> i) & 1) {
// // 0~2^i-1中1个个数
// ans += (1ll << i) * i / 2;
// cnt -= 1ll << i;
// // 后面的次高位都为1
// ans += cnt;
// }
// }
//
// //cerr << ans << " " << k << " " << x << '\n';
//
// // 没拼完的一位
//
// //cerr << now << '\n';
// for(int i = k; x > 0; i --, x --) {
// if((now >> i) & 1) {
// ans ++;
// }
// }
//
// //cout << ans;
// return ans;
//}
//
//long long check(long long x) {
// string s = "0";
// for(int i = 1; i <= 1e6; i ++) {
// int x = i;
// string t;
// while(x) {
// t += char(x % 2 + '0');
// x /= 2;
// }
// reverse(t.begin(), t.end());
// s += t;
// }
//
// long long cnt = 0;
// for(int i = 0; i < x; i ++) {
// cnt += s[i] == '1';
// cerr << i << ":" << cnt << " " << check1(i + 1) << '\n';
// assert(cnt == check1(i + 1));
// }
// return cnt;
//}
int main() {
ios::sync_with_stdio(false), cin.tie(nullptr);
cout << fixed << setprecision(15);
long long x;
cin >> x;
x --;
if(x == 0) {
cout << 0 << '\n';
return 0;
}
int k = 0;
long long ans = 0;
while(x >= (1ll << k) * (k + 1)) { // 长度为k的二进制个数为2^(k - 1)
x -= (1ll << k) * (k + 1); // 减去长度为k的二进制个数数量
ans += (1ll << k) + 1ll * k * (1ll << k) / 2; // 第一位全为1有2^(k-1)个,后面每位的次数为2^(k-2),有k-1位,和位(k-1)*2^(k-2)
k ++;
}
//只有k+1的了
long long cnt = x / (k + 1);
int cur = x % (k + 1);
x -= cnt * (k + 1);
ans += cnt; // 第一位都为1
long long now = powl(2, k) + cnt;
// 求0~cnt-1中有多少个1
for(int i = 60; i >= 0; i --) {
if((cnt >> i) & 1) {
// 0~2^i-1中1个个数
ans += (1ll << i) * i / 2;
cnt -= 1ll << i;
// 后面的次高位都为1
ans += cnt;
}
}
//cerr << ans << " " << k << " " << x << '\n';
// 没拼完的一位
//cerr << now << '\n';
for(int i = k; x > 0; i --, x --) {
if((now >> i) & 1) {
ans ++;
}
}
cout << ans;
return 0;
}
G甘蔗
明显的dp,三重循环,500x500x1000应该能卡过
#include<bits/stdc++.h>
using namespace std;
#define ctz __builtin_ctzll
#define ppc __builtin_popcountll
#define par __builtin_parityll
#define all(x) (x).begin(), (x).end()
using LL = long long;
using ull = unsigned long long;
using db = double;
using ldb = long double;
const int INF = 1e9;
const db EPS = 1e-8;
const db PI = acos(-1);
const int mod = 1e9 + 7;
int main() {
ios::sync_with_stdio(false), cin.tie(nullptr);
cout << fixed << setprecision(15);
int n, m;
cin >> n >> m;
vector<int> a(n);
for(int i = 0; i < n; i ++) {
cin >> a[i];
}
vector<int> b(m);
for(int i = 0; i < m; i ++) {
cin >> b[i];
}
vector<vector<int>> f(n, vector<int>(1001, 1e9));
f[0][a[0]] = 0;
for(int i = 0; i < a[i]; i ++) {
f[0][i] = 1;
}
for(int i = 1; i < n; i ++) {
for(int j = 0; j <= a[i - 1]; j ++) {
for(int k = 0; k < m; k ++) {
if(j + b[k] <= a[i]) {
f[i][j + b[k]] = min(f[i][j + b[k]], f[i - 1][j] + (j + b[k] != a[i]));
}
if(j - b[k] >= 0) {
f[i][j - b[k]] = min(f[i][j - b[k]], f[i - 1][j] + (j - b[k] != a[i]));
}
}
}
}
int ans = 1e9;
for(int j = 0; j <= 1000; j ++) {
ans = min(ans, f[n - 1][j]);
}
cout << (ans > n ? -1 : ans);
return 0;
}
H原料采购
使用优先队列,记录已购买的价格以及数量放入优先队列中,价格作为关键字。简单证明能发现,最大pop push次数是O(n)数量级的(不知道对不对)。
#include<bits/stdc++.h>
using namespace std;
#define ctz __builtin_ctzll
#define ppc __builtin_popcountll
#define par __builtin_parityll
#define all(x) (x).begin(), (x).end()
using LL = long long;
using ull = unsigned long long;
using db = double;
using ldb = long double;
const int INF = 1e9;
const db EPS = 1e-8;
const db PI = acos(-1);
const int mod = 1e9 + 7;
int main() {
ios::sync_with_stdio(false), cin.tie(nullptr);
cout << fixed << setprecision(15);
int n, m, o;
cin >> n >> m >> o;
long long ans = 1e18, sum = 0;
int cnt = 0;
priority_queue<pair<int, int>> q;
for(int i = 0; i < n; i ++) {
int a, b, c;
cin >> a >> b >> c;
if(cnt < m) {
int k = min(b, m - cnt);
q.push({a, k});
sum += 1ll * k * a;
cnt += k;
b -= k;
}
if(b > 0) {
int cnt_1 = 0;
while(!q.empty() && q.top().first > a && b) {
auto [price, res] = q.top();
q.pop();
if(res < b) {
cnt_1 += res;
b -= res;
sum -= 1ll * res * price;
} else {
cnt_1 += b;
res -= b;
sum -= 1ll * b * price;
b = 0;
q.push({price, res});
}
}
q.push({a, cnt_1});
sum += 1ll * a * cnt_1;
}
ans = min(ans, sum + 1ll * o * c);
}
cout << ans << '\n';
return 0;
}