第四周训练
第一题 A-B 数对
题目1
给出一串数和数字 C C C 计算出所有 A − B = C A - B = C A−B=C 数对的个数。
思路1
可以通过一个map记录所有数出现的次数,则答案即为所有 m a p [ i ] ∗ m a p [ i + c ] map[i] * map[i + c] map[i]∗map[i+c] 的和。
代码1
#include<iostream>
#include<map>
#define int long long
using namespace std;
constexpr int maxn = 2e5 + 5;
int n, c, ans;
map<int, int> m;
signed main()
{
cin >> n >> c;
for (int i = 1; i <= n; i++)
{
int t;
cin >> t;
//在map中记录数量
m[t]++;
}
//遍历map中所有出现过的数
for (auto p : m)
{
//如果能组成数对
auto f = m.find(p.first + c);
if (f != m.end())
{
//累加
ans += p.second * f->second;
}
}
cout << ans << endl;
return 0;
}
第二题 数位计算
题目2
f
(
x
)
=
f(x) =
f(x)= (不超过
x
x
x 且与
x
x
x 具有相同位数的正整数的个数)
给出一个整数
n
n
n。求 $F(1) + f(2) + \dots + f(n) $。
思路2
对于一个
n
n
n 位数
x
x
x ,
f
(
x
)
=
x
−
(
n
−
1
)
×
10
+
1
f(x) = x - (n - 1) \times 10 + 1
f(x)=x−(n−1)×10+1。
此外,
f
(
x
)
f(x)
f(x) 可以组成一个等差数列,使用求和公式即可求出同位数的数的
f
f
f
代码2
#include<iostream>
using namespace std;
constexpr int mod = 998244353;
string s;
long long n, ans;
size_t length;
//从字符串得到整数
long long getNumber(const string& s)
{
long long result = 0;
for (int i = 0; i < s.size(); i++)
{
result *= 10;
result += s[i] - '0';
}
return result;
}
int main()
{
cin >> s;
length = s.size();
n = getNumber(s);
//与n同位数的数量求法与别的不同
//与n同位数的数量
//temp记录位数
long long temp = 1;
for (int i = 0; i < length - 1; i++)
{
temp *= 10;
}
//n变成上述的x - (n - 1) * 10
n -= temp;
//求和公式
//n + 1是x - (n - 1) * 10 + 1, n + 1 + 1是个数
ans += (((n + 1) % mod) * ((n + 1 + 1) % mod) / 2) % mod;
ans %= mod;
//求其他位数的数量
temp = 1;
for (int i = 0; i < length - 1; i++)
{
//通过上面的式子可以得到某个位数所有数的数量和
ans += (((temp * 9 + 1) % mod) * ((temp * 9) % mod) / 2) % mod;
ans %= mod;
temp *= 10;
}
cout << ans << endl;
return 0;
}
第三题 新国王游戏
题目3
每位大臣在左、右手上面分别写下一个正整数。大臣排成一排。排好队后,所有的大臣都会获得国王奖赏的若千金币,每位大臣获得的金币数分别是:排在该大臣后面的所有人的左手上的数的乘积乘以他自己右手上的数。安排一下队伍的顺序使所有大臣获得的金币数之和最多。
思路3
使用贪心思想。如果将后面人左手上的数乘以自己右手的数(对自己获得的钱的影响)加上后面人右手上的数(对后面人获得的钱影响)大,则这个站位有利于得到更多的金币。
按照这个方法排序后,得到的就是答案。
代码3
#include<iostream>
#include<algorithm>
using namespace std;
constexpr int maxn = 1e6 + 5;
constexpr long long mod = 1000000007;
struct person
{
long long a, b;
//排序的方法
bool operator<(const person& p) const
{
return p.a * b + p.b > a * p.b + b;
}
} people[maxn];
int n;
long long ans;
int main()
{
cin >> n;
for (int i = 1; i <= n; i++)
{
cin >> people[i].a >> people[i].b;
}
//排序
sort(people + 1, people + n + 1);
//存储后面所有人左手上数的积
long long product = 1;
for (int i = n; i > 0; i--)
{
ans += (people[i].b * product) % mod;
ans %= mod;
product *= people[i].a % mod;
product %= mod;
}
cout << ans << endl;
return 0;
}
第四题 完美数
题目4
对于给定的数字
a
a
a,
b
b
b ,当整数
n
n
n 在十进制下的所有数位都为
a
a
a 或
b
b
b 时,我们称
n
n
n 是“好数”。
对于好数
n
n
n ,当
n
n
n 在十进制下每一位的数字之和也为“好数”时,我们称
n
n
n 是一个“完美数”。
请你求出有多少
m
m
m 位数是“完美数”。
思路4
由于相同数量的
a
a
a ,
b
b
b 组成的所有数的和相等。所以只需要判断和是否为完美数,再计算组合数即可。
由于组合数公式中有除法,而除法运算不能使用取模。
逆元:在余数中,除以一个数等于乘以一个数的逆元。
常用的求逆元方法:费马小定理求法
i
n
v
(
x
)
=
p
o
w
(
x
,
p
−
2
)
inv(x) = pow(x, p - 2)
inv(x)=pow(x,p−2)
其中p为模数,且必须为质数。
代码4
#include<iostream>
#define int long long
using namespace std;
constexpr int mod = 1e9 + 7;
//判断好数
bool isGood(int n, int a, int b)
{
//逐位判断
while (n > 0)
{
int d = n % 10;
if (d != a && d != b)
{
return false;
}
n /= 10;
}
return true;
}
//快速幂
int pow(int a, int n)
{
if (n == 1)
{
return a % mod;
}
else if (n % 2 == 0)
{
int p = pow(a, n / 2) % mod;
return (p * p) % mod;
}
else
{
return (a * pow(a, n - 1)) % mod;
}
}
//求解逆元
int inv(int a)
{
if (a == 1)
{
return a;
}
return pow(a, mod - 2);
}
//求解阶乘
int fact(int a)
{
int ans = 1;
for (int i = 1; i <= a; i++)
{
ans *= i;
ans %= mod;
}
return ans;
}
//求解组合数
int c(int a, int b)
{
return (((fact(a) * inv(fact(a - b))) % mod) * inv(fact(b))) % mod;
}
signed main()
{
int a, b, m;
int ans = 0;
cin >> a >> b >> m;
//遍历a可能出现的次数
for (int i = 0; i <= m; i++)
{
//判断和是否为好数
if (!isGood(i * a + (m - i) * b, a, b))
{
continue;
}
ans += c(m, i);
ans %= mod;
}
cout << ans << endl;
return 0;
}
第五题 Lusir的游戏
题目5
游戏中有
N
+
1
N + 1
N+1 座建筑——从
0
0
0 到
N
N
N 编号,从左到右排列。编号为
0
0
0 的建筑高度为
0
0
0 个单位,编号为
i
i
i 的建筑高度为
H
(
i
)
H(i)
H(i) 个单位。 起初,Lusir在编号为
0
0
0 的建筑处。每一步,它跳到下一个(右边)建筑。假设Lusir在第
k
k
k 个建筑,且它现在的能量值是
E
E
E ,下一步它将跳到第
k
+
1
k+1
k+1 个建筑。
如果
H
(
k
+
1
)
>
E
H(k + 1) > E
H(k+1)>E ,那么Lusir就失去
H
(
k
+
1
)
−
E
H(k + 1) − E
H(k+1)−E 的能量值,否则他将得到
E
−
H
(
k
+
1
)
E − H(k + 1)
E−H(k+1) 的能量值。
游戏目标是到达第
N
N
N 个建筑,在这个过程中能量值不能为负数个单位。
现在的问题是Lusir至少以多少能量值开始游戏,才可以保证成功完成游戏?
思路5
通过二分答案查找答案。
此外题目中得到和失去能量都可以写成 e += e - h(k + 1)
代码5
#include<iostream>
#define int long long
using namespace std;
constexpr int maxn = 1e5 + 5;
int n, ans = 0x3f3f3f3f, maxh;
int h[maxn];
//判断答案是否正确
bool judge(int e)
{
for (int i = 1; i <= n; i++)
{
e += e - h[i];
//小于0则不可以
if (e < 0)
{
return false;
}
//大于最大高度时,e会一直增加,肯定可以。同时防止溢出。
if (e > maxh)
{
return true;
}
}
return true;
}
signed main()
{
cin >> n;
for (int i = 1; i <= n; i++)
{
cin >> h[i];
maxh = max(maxh, h[i]);
}
//二分
int l = 0, r = 1e6;
while (l <= r)
{
int mid = (l + r) / 2;
if (judge(mid))
{
ans = min(ans, mid);
r = mid - 1;
}
else
{
l = mid + 1;
}
}
cout << ans << endl;
return 0;
}
第六题 BFS练习1
题目6
给你一个数字 a a a ,每次可以选择下面四种操作中的一种:
- 把数字 a a a 加上一。
- 把数字 a a a 乘以 2 2 2 。
- 把数字 a a a 乘以 3 3 3 。
- 把数字 a a a 减去一。
问把这个 a a a 变成 b b b 最少需要多少步。
思路6
通过bfs求解。
由于题目有多个询问,逐个求解可能超时。可以边求解边边存储答案并回答询问。
代码6
#include<iostream>
#include<utility>
#include<queue>
#include<cstring>
using namespace std;
constexpr int maxb = 1e5 + 5;
int a, q;
//变换到b需要的步数
int steps[maxb];
//bfs的队列
queue<pair<int, int> > que;
//判断某个b是否已经遍历过
bool vis[maxb];
//判断n是否在b的范围内并且没有别遍历过
bool judge(int n)
{
return n >= 1 && n < maxb && !vis[n];
}
int main()
{
//初始值设置为无限大
memset(steps, 0x3f, sizeof(steps));
cin >> a >> q;
//bfs的起始
que.push({ a, 0 });
while (q--)
{
int b;
cin >> b;
//如果答案已求解直接输出,为求解就求解
if (steps[b] == 0x3f3f3f3f)
{
//bfs,求解到询问需要的b为止
while (!que.empty() && steps[b] == 0x3f3f3f3f)
{
int now = que.front().first;
int step = que.front().second;
que.pop();
if (vis[now])
{
continue;
}
vis[now] = true;
//更新答案
steps[now] = step;
//四种方式
if (judge(now + 1))
{
que.push({ now + 1, step + 1 });
}
if (judge(now * 2))
{
que.push({ now * 2, step + 1});
}
if (judge(now * 3))
{
que.push({ now * 3, step + 1 });
}
if (judge(now - 1))
{
que.push({now - 1, step + 1});
}
}
}
//输出当前询问的答案
cout << steps[b] << ' ';
}
return 0;
}
第七题 01序列2
题目7
一个01序列中两个1之间至少要有 k k k 个0,现在要构造出一个长度为 n n n 的01序列,请问有多少种不同的构造方法。
思路7
可以先把1之间的0省略,然后将剩下的数进行排列,再在每两个1之间插入
k
k
k 个0,就可以保证所有1之间都有
k
k
k 个0。
然后只需要枚举1可能出现的次数。
这道题依然会用到组合数,同样需要逆元的方法求解。
此外需要将阶乘结果存储下来,防止超时。
代码7
#include<iostream>
#define int long long
using namespace std;
constexpr int mod = 1e9 + 7, maxn = 1e6 + 5;
int n, k, ans;
//记录阶乘结果和阶乘逆元的结果
int factans[maxn], invfactans[maxn];
//阶乘
int fact(int a)
{
return factans[a];
}
//计算所有需要的阶乘
void initalFact()
{
factans[1] = factans[0] = 1;
for (int i = 2; i < maxn; i++)
{
factans[i] = (factans[i - 1] * i) % mod;
}
}
//快速幂
int pow(int a, int num)
{
if (num == 1)
{
return a;
}
else if (num % 2 == 0)
{
int p = pow(a, num / 2);
return (p * p) % mod;
}
else
{
return (a * pow(a, num - 1)) % mod;
}
}
//逆元
int inv(int a)
{
return pow(a,mod - 2);
}
//求阶乘的逆元
int factinv(int a)
{
//先判断是否求解过
if (invfactans[a] != 0)
{
return invfactans[a];
}
return invfactans[a] = inv(fact(a));
}
//组合数
int c(int a, int b)
{
return (((fact(a) * factinv(a - b)) % mod) * factinv(b)) % mod;
}
signed main()
{
cin >> n >> k;
initalFact();
//遍历1的个数,(i - 1) * k + i是i个1时序列最小长度
for (int i = 0; (i - 1) * k + i <= n; i++)
{
//剩余的01个数,min用来保证i为0是left不会比n大
int left = min(n, n - (i - 1) * k);
ans += c(left, i);
ans %= mod;
}
cout << ans << endl;
return 0;
}
第八题 整除光棍
题目8
读入一个整数
x
x
x ,这个整数一定是奇数并且不以
5
5
5 结尾。然后,经过计算,输出两个数字:第一个数字
s
s
s,表示x
乘以
s
s
s 是一个光棍,第二个数字
n
n
n 是这个光棍的位数。
题目要求输出最小的解。
光棍:只由
1
1
1 组成的数。
思路8
枚举光棍,然后判断整除。但是光棍的长度可能很长,所以要使用高精。
代码8
#include<iostream>
#include<vector>
#include<algorithm>
using namespace std;
//高精除int,i为是否能整除
vector<int> divide(const vector<int>& h, int n, bool& i)
{
//结果
vector<int> v;
//l是当前位数上需要除的数
int l = 0;
for (int i = h.size() - 1; i >= 0; i--)
{
//加上当前位数的数
l *= 10;
l += h[i];
//除
v.push_back(l / n);
//取余,留到下一位
l %= n;
}
//v为反转的数,要进行反转
reverse(v.begin(), v.end());
//去高位0
while (v.back() == 0 && v.size() > 1)
{
v.pop_back();
}
//剩下的数是0就是整除了
i = l == 0;
return v;
}
int main()
{
int x;
vector<int> v;
cin >> x;
//光棍长度
int i = 0;
while (true)
{
//记录是否整除
bool b;
i++;
v.push_back(1);
vector<int> r = divide(v, x, b);
//是整除时
if (b)
{
for (int i = r.size() - 1; i >= 0; i--)
{
cout << r[i];
}
cout << ' ' << i << endl;
break;
}
}
return 0;
}