11界蓝桥杯省赛第二场 C++ B组
试题A: 门牌制作
问 \(1\sim 2020\) 中包含多少个字符 \(2\) 。
暴力枚举。
#include <cstdio>
int getCnt(int x)
{
int cnt = 0;
while (x) {
if (x%10 == 2) ++cnt;
x /= 10;
}
return cnt;
}
int main()
{
int ans = 0;
for (int i = 1; i <= 2020; ++i) {
ans += getCnt(i);
}
printf("%d", ans); // 624
return 0;
}
试题B: 既约分数
有多少个 \(\frac ba(a,b\in N_+\and 1\le a,b\le2020)\) 满足 \(gcd(a,b)=1\) 。
暴力枚举。
#include <cstdio>
int gcd(int a, int b)
{
while (b) {
int t = a%b;
a = b;
b = t;
}
return a;
}
int main()
{
int ans = 0;
for (int a = 1; a <= 2020; ++a) {
for (int b = 1; b <= 2020; ++b) {
if (gcd(a,b) == 1) ++ans;
}
}
printf("%d", ans); // 2481215
return 0;
}
试题C: 蛇形填数
数学求解: \(ans=1+2+\cdots+38+20=\frac{1+38}{2}\times 38+20=761\) .
或者直接模拟。
#include <cstdio>
int num[41][41];
int main()
{
int t = 0;
for (int k = 2; k <= 40; ++k) {
for (int i = 1; i < k; ++i) { // 不妨认为每次都是从左下向右上填数,结果一样
int j = k-i;
num[i][j] = ++t;
}
}
printf("%d", num[20][20]); // 761
return 0;
}
试题D: 跑步锻炼
模拟日期变化。
#include <cstdio>
struct Date
{
int year, month, day;
Date operator ++ ()
{
int mdays[] = {0,31,28,31,30,31,30,31,31,30,31,30,31};
if (year%400 == 0 || year%4 == 0 && year%100 != 0) mdays[2] = 29;
++day;
if (day > mdays[month]) {
day = 1; ++month;
if (month > 12) month = 1, ++year;
}
}
bool operator != (Date t) const
{
return year != t.year || month != t.month || day != t.day;
}
};
int main()
{
Date date = {2000,1,1};
int d = 6;
int ans = 0;
for (; date != Date{2020,10,2}; ++date, d = (d+1)%7) {
++ans;
if (d == 1 || date.day == 1) {
++ans;
}
}
printf("%d", ans); // 8879
return 0;
}
试题E: 七段码
求七段码数码管的所有发光数码管连成一片的情况的种类数。
暴力枚举或者手算应该都可以,感觉比较麻烦。
可以将数码管看成有 \(6\) 个顶点, \(7\) 条边 \(a,b,c,d,e,f,g\) 构成的图,容易想到发光数码管连成一片等价于一个集合。可以用并查集解决。
/*
6顶点,7条边
1-2
| |
6-3
| |
5-4
*/
#include <cstdio>
#include <vector>
using namespace std;
/**
* Disjoint Set Union
*/
class DSU
{
vector<int> s;
public:
DSU(int n) {
s.resize(n+1);
for (int i = 1; i <= n; ++i) s[i] = i;
}
int find(int x) {
if (x != s[x]) s[x] = find(s[x]);
return s[x];
}
bool unite(int u, int v) {
int x = find(u);
int y = find(v);
if (x != y) {
s[y] = x;
return true;
}
return false;
}
};
int main()
{
int ans = 0;
for (int state = 0; state < (1<<7); ++state) {
int x = state;
DSU dsu(6);
for (int i = 0; i < 7; ++i) {
int t = x&1;
int u = i,v = (i+1)%6;
if (i == 6) u = 2, v = 5;
++u, ++v;
if (t) dsu.unite(u,v) ;
x >>= 1;
}
int t = -1;
for (int i = 1; i <= 6; ++i) {
int x = dsu.find(i);
if (x == i) continue;
if (t == -1) t = x;
else if (t != x) {
t = -1;
break;
}
}
if (t != -1) {
++ans;
}
}
printf("%d", ans); // 80
return 0;
}
试题F: 成绩统计
#include <cstdio>
int main()
{
int n;
scanf("%d", &n);
int ok = 0, great = 0;
for (int i = 0; i < n; ++i) {
int x;
scanf("%d", &x);
if (x >= 60) ++ok;
if (x >= 85) ++great;
}
printf("%.0f%%\n%.0f%%", 100.0*ok/n, 100.0*great/n);
return 0;
}
试题G: 回文日期
枚举年份即可枚举所有回文日期。
#include <cstdio>
int rev(int n)
{
int x = 0;
while (n) {
x *= 10;
x += n%10;
n /= 10;
}
return x;
}
int main()
{
int n;
scanf("%d", &n);
int ans1 = 0, ans2 = 0;
for (int i = n/10000; ; ++i) {
int t = i*10000+rev(i);
if (t <= n) continue;
if (ans1 == 0) ans1 = t;
if (i%100 == i/100) {
ans2 = t;
break;
}
}
printf("%d\n%d\n", ans1, ans2);
return 0;
}
试题H: 子串分值和
维护以 \(j\) 为尾的所有子串的分值,可以推出以 \(j+1\) 为尾的所有子串的分值。
用线段树维护,时间复杂度 \(O(n\log n)\) 。
#include <cstdio>
#include <vector>
using namespace std;
typedef long long LL;
class SegTree
{
vector<LL> tree, lazy;
void pushUp(int rt) {
tree[rt] = tree[rt<<1] + tree[rt<<1|1];
}
void pushDown(int L, int R, int rt) {
if (L == R || !lazy[rt]) return;
LL &t = lazy[rt];
int m = L+R >> 1;
tree[rt<<1] += t*(m-L+1);
tree[rt<<1|1] += t*(R-m);
lazy[rt<<1] += t;
lazy[rt<<1|1] += t;
t = 0;
}
public:
SegTree(int n) {
tree.resize(n<<2);
lazy.resize(n<<2);
}
void update(int x, int y, LL val, int L, int R, int rt){
if (x <= L && R <= y) {
tree[rt] += val*(R-L+1);
lazy[rt] += val;
return;
}
pushDown(L, R, rt);
int m = L+R >> 1;
if (x <= m) update(x, y, val, L, m, rt<<1);
if (y > m) update(x, y, val, m+1, R, rt<<1|1);
pushUp(rt);
}
LL query(int x, int y, int L, int R, int rt) {
if (x <= L && R <= y) {
return tree[rt];
}
pushDown(L, R, rt);
int m = L+R >> 1;
LL ans = 0;
if (x <= m) ans += query(x, y, L, m, rt<<1);
if (y > m) ans += query(x, y, m+1, R, rt<<1|1);
return ans;
}
};
const int N = 1e5+5;
char s[N];
int pos[128];
int main()
{
scanf("%s", s+1);
int n = 0;
for (int i = 1; s[i]; ++i) ++n;
SegTree st(n);
LL ans = 0;
for (int i = 1; s[i]; ++i) {
int L = pos[s[i]]+1;
st.update(L, i, 1, 1, n, 1);
ans += st.query(1, n, 1, n, 1);
pos[s[i]] = i;
}
printf("%lld", ans);
return 0;
}
试题I: 平面切分
\(hint:\) 平面上每增加一条直线,若该直线上有 \(k\) 个交点,则新增区域 \(k+1\) 个。
为避免浮点误差,定义 Fraction 类表示分数。
首先将所有直线排序后去重,时间复杂度 \(O(n\log n)\) 。
然后依次在平面上添加直线,求交点个数,记录交点的横坐标即可,交点坐标需要排序后去重,总时间复杂度 \(O(n^2\log n)\) 。
#include <cstdio>
#include <algorithm>
#include <vector>
using namespace std;
typedef long long LL;
class Fraction
{
LL s, m;
LL gcd(LL a, LL b) {
while (b) {
LL r = a%b;
a = b;
b = r;
}
return a;
}
public:
Fraction(){}
Fraction(LL son, LL mom) : s(son), m(mom) {
LL d = gcd(s, m);
if (d < 0) d = -d;
s /= d, m /= d;
if(m < 0) s = -s;
}
bool operator < (const Fraction &f) const {
return s*f.m < m*f.s;
}
bool operator == (const Fraction &f) const {
return s == f.s && m == f.m;
}
};
const int N = 1003;
struct P
{
LL a, b;
bool operator < (const P &t) const
{
if (a != t.a) return a < t.a;
return b < t.b;
}
bool operator == (const P &t) const
{
return a == t.a && b == t.b;
}
}p[N];
Fraction dot[N];
int main()
{
int n;
scanf("%d", &n);
for (int i = 0; i < n; ++i) {
scanf("%lld%lld", &p[i].a, &p[i].b);
}
sort(p, p+n);
n = unique(p, p+n) - p;
int ans = 1;
for (int i = 0; i < n; ++i) {
int k = 0;
for (int j = 0; j < i; ++j) {
if (p[i].a == p[j].a) continue;
dot[k++] = Fraction(p[i].b-p[j].b, p[j].a-p[i].a);
}
sort(dot, dot+k);
k = unique(dot, dot+k)-dot;
ans += k+1;
}
printf("%d", ans);
return 0;
}
试题J: 字串排序
看别人题解之前根本没想到动态规划。感觉有点类似数位 \(dp\) 。
冒泡排序交换次数即逆序对个数。
长度为 \(n\) 的字符串降序排列时逆序对最多,为 \(1+2+\cdots+n-1=\frac{n(n-1)}2\) 个。
不难发现:满足 \(\frac{n(n-1)}2>=V\) 最小的 \(n\) 即为所求字符串长度。
令 \(f_{i,j,k}\) 为长度为 \(i\) 开头连续 \(k\) 个字符为 \(j\) 的字符串的最大逆序对个数。
则
从高位往地位遍历,按字典序从小到大遍历,即可找到字典序最小解。
#include <cstdio>
#include <algorithm>
#include <vector>
using namespace std;
const int N = 144;
int f[N][26][N]; // f[i][j][k]代表长度为i首k个字母为'a'+j的字符串的最大逆序对个数
void init()
{
for (int i = 2; i < N; ++i) {
for (int j = 1; j < 26; ++j) {
for (int k = 1; k < i; ++k) {
f[i][j][1] = max(f[i][j][1], f[i-1][j-1][k]+i-1);
}
for (int k = 2; k < i; ++k) {
f[i][j][k] = max(f[i][j][k], f[i-1][j][k-1]+i-k);
}
}
}
}
int main()
{
init();
int V;
scanf("%d", &V);
int n = 2;
for (; n*(n-1)/2 < V; ++n); // V == 10000 时 n == 142
int tmp = 0;
for (int i = n; i >= 1;) {
int j, k;
for (j = 0; j < 26; ++j) {
for (k = 1; k <= i; ++k) {
if (f[i][j][k] >= V) goto outer;
}
}
outer:;
i -= k; V -= i*k;
while (k--) putchar(j+'a');
}
return 0;
}