十二
1.卡片
挺好写的,其实手写用笔搓也能搓出来。
#include<iostream>
using namespace std;
int sum = 1;
int t;
int h;
int main() {
for (int i=1; sum <= 2021; i++) {
for (t = i; t != 0; t = t / 10) { //测定一个数有几个1
if (t%10 == 1)
sum++;
}
h = i; //记录一下数字
}
cout << h;
return 0;
}
2.直线
思路:主要是通过set容器去除重复值,再就是四重循环来去到两个点的坐标。为了避免重复,在第二个点处去x值加一为起始点。注意最后加的20为斜率不存在的情况。
代码:
#include<iostream>
#include<set>
using namespace std;
int main()
{
set<pair<double, double> > line;
double k, b;
for (int x1 = 0; x1 < 20; x1++) {
for (int y1 = 0; y1 < 21; y1++) {
for (int x2 = x1 + 1; x2 < 20; x2++) { //给x加
for (int y2 = 0; y2 < 21; y2++) {
k = (double)(y1 - y2) / (x1 - x2);
b = (double)(x1 * y2 - x2 * y1) / (x1 - x2); //将公式通分,不然会有精度损失
line.insert({ k,b });
}
}
}
}
cout << line.size() + 20; //斜率不存在的情况加20
return 0;
}
3.货物摆放
求因子计算方案
#include<iostream>
using namespace std;
#include<cmath>
#include<vector>
int main() {
//其实为什么我们那样去想,因为那样去想最简单,最不费脑子。但是我们发现那种方法很多组都是无效的,也就是出现不是num因子的数字出现
//因此这样的计算毫无意义反而给我们的CPU增加负担;
//好,简化的目的摆明了,找num的因子呗~
//但是我们事先不知道num到底有多少因子,因此这里可以设置一个vector容器,产生一个就压进vector容器里
//因为因子也可能是num本身,所以咱们的因子容器设置为long long类型的
//我们开始找因子,只需要找到num的平方根即可,因为如果再往后找的话,就找到之前小于num平方根的i除得的num中对应于i的较大的因子;
//这里对于较大的因子我们可以通过循环里的j得到
long long n= 2021041820210418;
vector<long long>S;
int sum = 0;
for (long long i = 1; i<=sqrt(n); i++) {
if (n % i == 0) {
S.push_back(i);
long long j = n / i;
S.push_back(j);
}
}
for (int i = 0; i < S.size(); i++) {
// cout << S[i] << endl;
for (int j = 0; j < S.size(); j++) {
for (int k = 0; k < S.size(); k++) {
if (S[i] * S[j] * S[k] == n)
sum++;
}
}
}
cout << sum << endl;
}
4.路径
自己写,写麻烦了。。。。。。。。
题解他们用动态规划写了,我用迪杰维斯特。。麻了
迪杰韦斯特:
#include<iostream>
using namespace std;
const int N = 2021,n=2021;
long long dist[N]; //起点到第N点的距离
long long g[N][N]; //构建邻接矩阵
int vis[N]; //判断第N个节点是否被访问过
int gcd(int c, int d) { //辗转相除法求的最大公约数
if (c % d == 0)
return d;
else
return gcd(d, c % d);
}
int func_Q(int a, int b) { //再求得最小公倍数
return a * b / gcd(a, b);
}
void Store() { //存图
memset(g, 0x3f3f3f3f, sizeof(g));
for (int i = 1; i <= 2021; i++) {
for (int j = 1; j <= 2021; j++) {
if (i == j)
g[i][j] = g[j][i] = 0;
if ((i - j) <= 21 && (i - j) >0)
g[i][j] =g[j][i] = func_Q(i, j);
/*if((j - i) <= 21 && (j - i) > 0)
g[i][j] = func_Q(j, i); */
}
}
}
int dijkstra() {
memset(dist, 0x3f3f3f3f, sizeof(dist)); //初始化最短距离数组
for (int i = 1; i <= n; i++) { //初始化距离数组第一次和记录访问数组
dist[i] = g[1][i];
vis[i] = 0;
}
dist[1] = 0; //第一个点到第一个点的距离为0
for (int i = 0; i < n; i++) {
int t = -1;
for (int j = 1; j <= n; j++) {
if (!vis[j] && (t == -1 || dist[j] < dist[t])) {//选取未访问的且距离起点最近的点
t=j;
}
}
vis[t] = 1; //标记该点已访问
for (int j = 1; j <=n; j++) { //更新未访问点的最短距离
if (vis[j] == 0)
dist[j]= min(dist[j],dist[t]+g[t][j]);
}
}
return dist[n];
}
int main() {
Store();
cout << dijkstra()<<endl;
return 0;
}
动态规划,第二种办法:
注意:#include <bits/stdc++.h>这个头文件在蓝桥杯里加入
#include<iostream>
using namespace std;
int gcd(int x, int y)
{
return !y ? x : gcd(y, x % y); //辗转相除法,背过背过
}
int main()
{
int f[2022]; //储存最短路径
memset(f, 0, sizeof f); //初始化为0
for (int i = 1; i <= 2021; i++) { //一个一个节点过
for (int j = i + 1; j <= i + 21; j++) { //赋权以及计算
if (j > 2021)
break;
if (f[j] == 0) //f[j]=0则代表第一次计算这个点,到这个点的距离为到i的加上i到j的权
f[j] = f[i] + j * i / gcd(i, j);
else //不是第一来的话就比较比较
f[j] = min(f[j], f[i] + j * i / gcd(i, j));
}
}
cout << f[2021] << endl;
}
5.时间显示
简单
#include<iostream>
using namespace std;
int main() {
long long n;
cin >> n;
n = n % 86400000;
int hour=n;
hour = hour / 3600000;
/*while (hour >= 24) {
hour=hour%
}*/
int min = (n - hour * 3600000) / 60000;
int miao = (n - hour * 3600000 - min * 60000) / 1000;
printf("%02d:%02d:%02d", hour, min, miao);
}
6.砝码称重
思路在注释里面了
#include<iostream>
using namespace std;
long long N;
long long sum = 0;
long long ans = 0;
long long a[101];
int dp[101][100001]; //dp[i][j]表示前i个砝码能得到j的重量
int main() {
cin >> N;
for (int i = 1; i <= N; i++) {
cin >> a[i];
sum += a[i];
}
for (int i = 1; i <= N; i++) {
for (long long j = 1; j <= sum; j++) {
dp[i][j] = dp[i - 1][j]; //先继承前i-1个的状态
if (dp[i][j] == 0) { //如果前i-1个算不出j的重量,那么就使用第i个砝码试试
//使用第i个砝码有三种情况
if (j == a[i]) dp[i][j] = 1; //第一种情况,直接用第i个砝码
if (dp[i - 1][abs(j - a[i])] == 1) dp[i][j] = 1; //第二种情况,前i-1个能称出j-a[i]的重量,那直接加就行
if (dp[i - 1][j + a[i]] == 1) dp[i][j] = 1; //第三种情况,减第i个砝码;
}
}
}
for (long long j = 1; j <= sum; j++) {
if (dp[N][j] == 1) ans++;
}
cout <<ans;
return 0;
}
7.异或数列
题解的大佬,真牛
#include <iostream>
#include <vector>
using namespace std;
int main()
{
int t;
cin >> t;
while (t--)
{
int n = 0;
cin >> n;
int res = 0;
vector<int> v(32); // 统计每一个bit位总共有多少个1
int x;
for (int i = 0; i < n; ++i)
{
cin >> x;
res ^= x;
for (int j = 0; (j < 32) && x; ++j)
{
if (x & 1)
{
v[j]++;
}
x >>= 1;
}
}
/*
* 最优策略:
* 为了让自己的数更大,优先选择将高位的1异或到自己的数上
* 如果自己高位已是1,对方对应高位也是1,则优先将1异或到对方身上,使其为0
*/
// 特殊情况:所有数异或起来是0,说明每个比特位上的1都有偶数个
// 此时采取最优策略,最终Alice和Bob得到的数是相同的,因此平局
if (res == 0)
{
cout << 0 << endl;
}
else
{
// 一般情况:从高位开始选数进行异或
for (int i = 31; i >= 0; --i)
{
if (v[i] > 0)
{
if (v[i] % 2 == 0) // 如果是偶数个,则无法通过这一位决出胜负
{
continue;
}
else // 如果是奇数个,分类讨论
{
if (v[i] == 1) // 由于Alice先拿,此时必赢
{
cout << 1 << endl;
}
// 如果数列大小n是偶数,那么由于该bit位1是奇数个,故0也是奇数个
else if (n % 2 == 0)
{
/*
* 若Alice先拿1,那么Bob必然拿0,而Alice也因此只能拿0
* 最终Bob拿最后1个0, 此时由于1有偶数个,Alice只能用1给Bob异或,此时Bob该位是1
* Bob再给A异或使其为0,A再给自己异或使其为1,最终,Bob拿最后一个1给Alice异或使其为0
*
* 若Alice先拿0,那么Bob也拿0,最后1个0由Alice拿,此时Bob拿第一个1给自己异或
* 然后Alice也拿1给自己异或,然后Bob拿1给Alice异或,Alice拿1给自己异或...
* 最终,Bob拿最后一个1给Alice异或,此时Alice为0,Bob为1
*
* 因此这种情况,Bob必赢
*/
cout << -1 << endl;
}
// 如果数列大小n是奇数,那么由于该bit位1是奇数,故0是偶数
else
{
/*
* 偶数个0不会对结果造成影响
* 因此Alice先拿1给自己,Bob也拿1给自己,Alice再拿1给Bob,Bob拿1给自己...
* 最终,Alice把最后一个1给Bob使其为0,而自己依然是1
* 因此,这种情况Alice必赢
*/
cout << 1 << endl;
}
break;
}
}
}
}
}
return 0;
}
8.双向排序
原题是得用线段树写的,不会,整个骗分的,能过60%也可以了
#include<iostream>
using namespace std;
#include<algorithm>
bool up(int a, int b) {
return a < b;
}
bool down(int a, int b) {
return a > b;
}
int main() {
int n, m;
cin >> n >> m;
int a[100000];
for (int i = 0; i < n; i++) {
a[i] = i + 1;
}
for (int i = 0; i < m; i++) {
int p, q;
cin >> p >> q;
if (p)
sort(a + q-1, a + n, up); //sort自定义排序顺序时以这种方式
else
sort(a, a + q, down);
}
for (int i = 0; i < n; i++) {
cout << a[i]<<" ";
}
return 0;
}
十四
1.工作时长
excel直接做
参考博客:蓝桥杯2023年第十四届省赛真题-工作时长-CSDN博客
注意设置单元格格式,排序,第二行减第一行。。。然后求和就完事了。
2.与或异或
暴力法:
#include<iostream>
using namespace std;
#include<math.h>
//暴力法
int main() {
int a[5][5];
a[0][0] = a[0][2] = a[0][4] = 1;
a[0][1] = a[0][3] = 0; //输入初始条件
int max = (int)pow(3, 10); //定义循环次数 3的10次方次
int t = 0,op=0,ans=0;
for (int k = 0; k < max; k++) {
t = k;
for (int i = 1; i < 5; i++) {
for (int j = 0; j < 5 - i; j++) { //循环到每一个数
op = t % 3; //确定该哪一个操作了
t /= 3; //每一个操作完 除3 到下一对数字
switch (op) {
case 0:a[i][j] = a[i-1][j] & a[i-1][j + 1];
break;
case 1:a[i][j] = a[i-1][j] | a[i-1][j + 1];
break;
case 2:a[i][j] = a[i-1][j] ^ a[i-1][j + 1];
}
}
}
if (a[4][0] == 1)
ans++;
}
cout << ans;
return 0;
}
3.翻转
#include<iostream>
#include<string>
using namespace std;
string S,T;
int n; //组数
int main() {
cin >> n;
for (int i = 0; i < n; i++) {
cin >> T;
cin >> S;
int count=0; //记录变更数据
int l = S.size();
for (int j = 0; j < l; j++) {
if (S[j] == T[j])
continue;
else {
if (S[0] != T[0]) { //第一个不对直接不行
count = -1;
break;
}
if (S[j] != S[j - 1] && S[j] != S[j + 1]) { //变更
S[j] = S[j - 1];
count++; //计数
}
else { //两个数不对又不能改变肯定不行
count = -1;
break;
}
}
}
cout << count << endl;
}
return 0;
}
4.阶乘的和
尝试使用暴力法,但是一个也通不过
看了题解,发现对于阶乘的特性 因数其实就是最小的阶乘,毕竟每一个小的阶乘都是大的阶乘的因数(一堆阶乘的和也还是最小阶乘的倍数嘛),所以其实就是求最小阶乘的问题。但是有一种情况就是可以阶乘合并,比如3个2!就是3!
这个直接转化就行 num的个数cnt满足 cnt%(num+1)==0的话 cnt*num!=cnt/(num+1) *(num+1)*num!=cnt/(num+1)*(num+1)! 这样操作之后,输出最小阶乘就行了。
这里写代码用了map,可以自动的排序保存一下,调用遍历的时候用迭代器就行。
#include<iostream>
using namespace std;
#include<map>
typedef long long ll;
map<ll, int>mp;
int main() {
int n;
cin >> n;
int num;
for (int i = 0; i < n; i++) {
cin >> num;
mp[num]++; //统计各个数出现几次
}
map<ll, int>::iterator it; //定义一个map类型的迭代器
for (it = mp.begin(); it != mp.end(); it++) { //遍历 mp会自己排序
int num = it->first;
int cnt = it->second;
if (cnt % (num + 1) == 0) //如果满足cnt整除num+1的话 cnt*num!=cnt/(num+1)*(num+1)!
mp[num + 1] += cnt / (num + 1);
else { //不能升阶的话直接就是最小的了,升不动也就不行了
cout << num << endl;
break;
}
}
return 0;
}
5.公因数匹配
暴力法:44.4%
#include<iostream>
using namespace std;
const int MAX = 1e5;
int GCD(int a, int b) { //辗转相除法
if (a % b == 0)
return b;
else
return GCD(b, a % b);
}
int main() {
int n;
cin >> n;
long long A[MAX];
for (int i = 0; i < n; i++) {
cin >> A[i];
}
bool found = false;
for (int i = 0; i < n && !found; i++) { //找到后把标志设置为找到了,可以直接退出两层循环
for (int j = i + 1; j < n; j++) {
if (GCD(A[i], A[j]) > 1)
{
cout << i+1 << " " << j+1 << endl; //要输出的是第几个数,下标从0开始,所以要加1
found = true;
break;
}
}
}
return 0;
}
分解质因数存储的办法:参考蓝桥杯真题讲解:公因数匹配(数论:分解质因数)-CSDN博客
存储和分解参考,后面遍历的时候自己弄
#include<iostream>
using namespace std;
#include<vector>
#include<map>
map<int, vector<int>>mp;
void prim(int x, int pos) { //将所有的质数对应的数组坐标归到map里 就是一个质数对应一个数组,数组中存放的是数的下标
for (int i = 2; i*i <= x; i++) {
if (x % i == 0){
mp[i].push_back(pos);
while (x % i == 0) { //一步一步把所有的2.3..除掉
x /= i;
}
}
}
if (x > 1) //将除剩下的也加入
mp[x].push_back(pos);
}
int main() {
int n;
cin >> n;
for (int i = 0; i < n; i++) {
int x;
cin >> x;
prim(x, i + 1);
}
int x=0x3f3f3f3f, y=0x3f3f3f3f;
int m = size(mp);
for (int i = 2;i<1e6; i++) { //循环所有的质数,找质数对应map存储的数组中的数
if (size(mp[i]) >= 2)
{
if (mp[i][0] < x|| (mp[i][0] == x && mp[i][1] < y)) {
x = mp[i][0]; //存的时候从小到大存的,数组第一个和第二个就是需要的
y = mp[i][1];
}
}
}
cout << x << " " << y << endl;
return 0;
}
6.子树的大小
都在注释里面了,注意把数据类型都设置为long long
#include<iostream>
using namespace std;
#include<math.h>
//主要思路是不断计算k节点在每一层的左节点和右节点
//左节点的左节点是最左节点,右节点的右节点是最右节点,右节点减左节点再加和
//左节点公式: 第k个节点 前面有(k-1)个节点 则 左节点前有(k-1)*m+2个节点 2是根节点和第k个节点
//右节点公式: k*m+1 1是根节点
long long n, m, k;
long long solve() {
long long l = k, r = k;
long long sum = 1; //计算结点数,初始值为1是算上k节点
bool pos=true;
while (pos) {
l = (l - 1) * m + 2;
r = r * m + 1;
if (r >= n) { //处理最后一个节点,最后一个节点在左右子节点中间 结束条件
r = n;
pos = false;
}
if (l<=n) { //如果l>n 则表明节点已经在这个子树之前结束,不进行求和
sum += r - l + 1; //r-n的话多减了一个同一级的节点
}
}
return sum;
}
int main() {
int T;
cin >> T;
for (int i = 0; i < T; i++) {
cin >> n >> m >> k;
cout << solve() << endl;
}
return 0;
}
7.反异或01串
使用马拉车算法:Manacher (马拉车算法)-CSDN博客
思路都在注释里面。
#include<iostream>
using namespace std;
#include<string>
#include<vector>
typedef long long ll;
//进行反异或操作后得到的一定是一个 回文串
//进行一次反异或操作最多可以节省 l/2 个1。
//思路:找到给定T的含1最多的回文串,使得这一部分是由反异或操作得到的, 剩下的1是一次一次添加得到的
//查找含一最多的回文串 由manacher算法查找
ll manacher(string s) {
string temp = "#";
ll l = s.size();
for (ll i = 0; i < l; i++) { //改造字符串;
(temp += s[i]) += '#';
}
ll newL = temp.size(); //新字符串长度
vector<ll>r(newL+100, 0);
vector<ll>pre(newL+100, 0);
//int r[MAX] = {0}; //存储半径长度
ll c = 0; //存储要进行马拉车字符串的 中心位置
//int pre[MAX] = { 0 }; //存储1的前缀和
ll ans=0; //存储1的数量
for (ll i = 1; i < newL; i++) { //第一个和最后一个字符都是#,直接从1开始
pre[i] = pre[i - 1];
if (temp[i] == '1') //记录1的数量 //
pre[i]++;
}
for (ll i = 1; i < newL; i++) { //第一个和最后一个字符都是#,直接从1开始
if (c + r[c] > i) //i小于回文字符串的范围 可以对称
r[i] = min(r[2 * c - i], c + r[c] - i); //2*c-i是直接对称
//c+r[c]-i是 如果对称点的回文串太长,不超过大的回文串范围
while (i - r[i] >= 0 && i + r[i] < newL && temp[i - r[i]] == temp[i + r[i]])
r[i]++; //暴力求
--r[i]; //相当于减掉自身,因为循环必然会最开始的时候进入一次,比如 i=0,r[i]=0,r会加一次
if (i + r[i] > c + r[c]) //更新c的位置
c = i;
ans = max(ans, pre[i + r[i]]- pre[i - r[i]-1]);
}
return pre[newL-1]-ans/2;
}
int main() {
string T;
cin >> T;
cout<<manacher(T)<<endl;
return 0;
}