2020牛客寒假算法基础集训营第一场比赛的解题记录。原博客链接:http://www.retrogogogo.top/2022/01/26/algorithm/nowcoder/winter2022_1/
链接:https://ac.nowcoder.com/acm/contest/23106/A
来源:牛客网
9位主人公被困在一座大型的豪华巨轮中,每个人手上都有一个奇怪的手表,手表上有一个数字,$ 9 $ 个人的数字分别是 1 − 9 1−9 1−9;在巨轮中,还有很多紧闭的数字门,每扇数字门上也有一个 $1−9 $的数字,要想打开数字门逃出生天,主角们必须要满足一个奇怪的条件: k k k 个人能够打开门上数字为 d d d 的一扇数字门,当且仅当这 k k k 个人的腕表数字之和的数字根恰好为 d d d。 一个数字的数字根是指:将该数字各数位上的数字相加得到一个新的数,直到得到的数字小于 10 10 10。
输入 n n n 和 n n n 个数 { a n {a_n} an} ,求可以打开 1 − 9 1-9 1−9 号门的人物组合有多少种。
首先我们需要知道一个结论:一个数的数字根等于这个数对9取模的结果(特殊的情况是取模结果为0时数字根为9)。那我们要求的就是 { a n {a_n} an} 中选择一些数字组成 1 − 9 1-9 1−9 的方案数。
该问题可以转化为一个01背包问题,dp即可。定义 d p [ i ] [ j ] dp[i][j] dp[i][j] 数组 ,表示从前个数中选择一些数字,使得数字和的数字根为j的方案数(即求和对9取模得的方案数)。 d p [ i ] [ j ] = d p [ i − 1 ] [ j ] + d p [ i − 1 ] [ j − a [ i ] ] + d p [ i − 1 ] [ j + 9 − a [ i ] ] dp[i][j] = dp[i - 1][j]+dp[i - 1][j - a[i]]+dp[i - 1][j + 9 - a[i]] dp[i][j]=dp[i−1][j]+dp[i−1][j−a[i]]+dp[i−1][j+9−a[i]],当然,这涉及到下标是否合法问题。
/*
* @Author: Retr0.Wu
* @Date: 2022-01-25 00:23:59
* @Last Modified by: Retr0.Wu
* @Last Modified time: 2022-02-02 02:46:04
*/
#include
using namespace std;
const int mod = 998244353;
typedef long long ll;
ll dp[100010][10]; // 从前个数中选择一些数字,使得数字和的数字根为j的方案数(即求和对9取模得的方案数)
void solveA()
{
int n;
cin >> n;
vector a(n + 1);
for (int i = 1; i <= n; i++)
{
cin >> a[i];
a[i] %= 9; // 一个数的数字根等于这个数对9取模的结果(取模得0则表示数字根为9,这里先不做处理,最后输出时做处理即可)
dp[i][a[i]] = 1; // 只用a[i]可以使dp[i][a[i]]至少有1种方案
}
//dp[1][a[1]] = 1;
for (int i = 2; i <= n; i++)
{
for (int j = 0; j <= 8; j++)
{
// 至少有dp[i][j]+dp[i-1][j]种(只用a[i]和用a[1...i-1])
ll tmp = (dp[i - 1][j] + dp[i][j]);
// 无非有俩种情况使得原数字根加上a[i]后的数字根为j
if (j - a[i] >= 0)
{
tmp += (dp[i - 1][j - a[i]]);
}
if (j + 9 - a[i] >= 0 && j + 9 - a[i] <= 8)
{
tmp += (dp[i - 1][j + 9 - a[i]]);
}
dp[i][j] = tmp;
dp[i][j] %= mod; // 特别注意:需要此时才能mod,防止大值被模后反而小于小值从而影响前面的max结果
}
}
for (int i = 1; i <= 8; i++)
{
cout << dp[n][i] << " ";
}
cout << dp[n][0] << endl;
}
链接:https://ac.nowcoder.com/acm/contest/23106/C
来源:牛客网
在CPU流水线式的架构中,有一个先写后读相关问题:一条语句A写入寄存器的数据若想被语句B读到,则语句B和A之间至少要间隔三条语句,如果不够三条就做插入空语句处理。
输入第一行包括一个整数 n ( 3 ≤ n ≤ 100 ) n(3≤n≤100) n(3≤n≤100) 程序原有语句总数。接下来有 n n n 行,第iii行描述了第 i i i 条程序语句,每行有三个数字。第 i i i 行第 j j j 个数字 a i , j ∈ 0 , 1 a_{i,j}∈{0,1} ai,j∈0,1 表示第 i i i 句与第 i − j i-j i−j 句间是否发生了先写后读相关,为 1 1 1 表示有发生先写后读相关(即第 i − j i-j i−j 句写入了某一寄存器,而第 i i i 句又要读取同一寄存器),为 0 0 0 表示没有。
求为了完全消除先写后读相关至少需加入多少条空语句。
有点贪心的感觉,每个语句尽量直接接上一个语句,如果出现先写后读相关问题,就差多少补多少空语句,循环下一个语句,实现时简单模拟即可,我们开一个 p o s [ i ] pos[i] pos[i] 数组去表示第 i i i 个语句所在的位置,取初始值 p o s [ i ] = p o s [ i − 1 ] + 1 pos[i]=pos[i - 1]+1 pos[i]=pos[i−1]+1 。
然后判断:若第 $ i$ 个语句比放寄存器的语句 + 4 +4 +4 还要大或者相等,说明不会有先写后读相关问题,保持原值。 否则就要插入空语句。至少到 i − j i-j i−j 语句后面第 4 4 4 个位置,取值 pos[i-j]+4。最后得到最终的总语句数,减去非空语句就是答案了。
void solveC()
{
int n;
cin >> n;
int a[110][5] = {0};
for (int i = 1; i <= n; i++)
{
for (int j = 1; j <= 3; j++)
cin >> a[i][j];
}
int cnt = 0;
vector pos(110);
pos[1] = 1;
for (int i = 2; i <= n; i++)
{
pos[i] = pos[i - 1] + 1; // 第i个命令所在的位置,取初始贪心值pos[i - 1]+1
for (int j = 1; j <= 3; j++)
{
if (a[i][j] == 1) // i-j 放 , i 取
{
// 贪心
// 若第i个命令比放寄存器的命令+4还要大或者相等,说明不会有先写后读相关问题,保持原贪心值;
// 否则就要插入空命令至少到i-j命令后面第4个位置,取贪心值 pos[i-j]+4。
pos[i] = max(pos[i], pos[i - j] + 4);
}
}
}
cout << pos[n] - n << endl; // 本来有n个命令,现在最后一个命令至pos[n]位,既有pos[n]-n个空命令
}
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-P0ApW6Y5-1644255504122)(winter2022_1/封面.png)]
链接:https://ac.nowcoder.com/acm/contest/23106/D
来源:牛客网
这道题需要我们对素数的性质够敏感,直接说结论吧:
问题一:根据自然顺序取前 k k k 个素数的累乘,答案就是最大的且不超过n的那一个。
问题二:问题范围内最大的素数。(从大到小暴力判读是否为素数找到第一个即可,因为1e9内每一个素数间间隔最大也不会超过300,不会爆时间)
bool isprime(int x)
{
for (int i = 2; i * i <= x; i++)
{
if (x % i == 0)
{
return false;
}
}
return true;
}
void solveD()
{
vector primes;
ll tp = 1;
for (int i = 2; i <= 2000; i++)
{
if (i * tp <= 1000000000 && isprime(i))
{
tp *= i; // i: 2 3 5 7 11 13 17 19 23
primes.push_back(tp);
}
}
int T;
cin >> T;
while (T--)
{
int n;
cin >> n;
if (n == 1)
{
cout << -1 << endl;
continue;
}
for (int i = 0; i < primes.size(); i++)
{
if (primes[i] > n)
{
cout << primes[i - 1] << " ";
break;
}
}
if (primes[primes.size() - 1] <= n)
cout << primes[primes.size() - 1] << " ";
for (int i = n; i >= 2; i--)
{
if (isprime(i))
{
cout << i << endl;
break;
}
}
}
}
链接:https://ac.nowcoder.com/acm/contest/23106/E
来源:牛客网
签到题。
void solveE()
{
int T;
cin>>T;
while(T--){
int n,m;
cin>>n>>m;
if(n==1){
cout<<1<
链接:https://ac.nowcoder.com/acm/contest/23106/F
来源:牛客网
给定一个长为 n n n 的数组 { a a a} 和一个整数 m m m,你需要将其切成连续的若干段,使得每一段的中位数都大于等于 m m m ,求最多可以划分成多少段。(偶数个数的中位数为中间两个数中较小的那一个)
这题如果不去严谨地证明,就是一道思维题,我们可以这么贪心地去思考:对于每一段,我们都取大于等于m的数量比小于m的多一个即可。这样的话答案就是大于等于m的数量减去小于m的数量。
void solveF()
{
ios::sync_with_stdio(false);
int T;
cin >> T;
while (T--)
{
int n, m;
cin >> n >> m;
int cnt1 = 0, cnt2 = 0;
vector a(n + 1);
for (int i = 0; i < n; i++)
{
cin >> a[i];
if (a[i] < m)
cnt1++;
else
cnt2++;
}
if (cnt1 >= cnt2)
cout << -1 << endl; // 注意相等时由于中位数偏小依旧不可行
else
cout << cnt2 - cnt1 << endl;
}
}
链接:https://ac.nowcoder.com/acm/contest/23106/H
来源:牛客网
定义式子 Σ i = 1 n Σ j = i n ∣ a i + a j − 1000 ∣ Σ_{i=1}^nΣ_{j=i}^{n}∣ai+aj−1000∣ Σi=1nΣj=in∣ai+aj−1000∣ ,给出整数序列 { a a a} : a i ( 0 ≤ a i ≤ 1000 ) a_i(0≤a_i≤1000) ai(0≤ai≤1000)求这个式子的值。
方法一:这个数值范围很小,可以保存 c n t [ ] cnt[] cnt[i] 表示 i 出现的次数,枚举(,)对。
方法二:当然这个方法仅限这种数值范围小的情况。我们也可以利用绝对值的性质对其求解:我们先保存一个后缀数组 l a s t [ N ] last[N] last[N],然后对整数序列 { a a a} 进行 $i、j $ 的嵌套循环,对于每一个 i i i , 我们都要求 Σ j = i n ∣ a i + a j − 1000 ∣ Σ_{j=i}^{n}∣ai+aj−1000∣ Σj=in∣ai+aj−1000∣, 那对{ a a a} 序列进行一个排序,在这个{ a a a} 的顺序序列中,总有一个值是 a i + a j − 1000 ai+aj−1000 ai+aj−1000 正负性的分界数(除非都大于等于0或者都小于0,那就更简单了),然后对这个分界前段和后段分类讨论即可,这个分界数我们通过二分查找找到,code的时候注意边界条件啥的不然很容易出错,思路还是简单的。(当然这题用方法一最合适)
void solveH()
{
int N;
cin >> N;
vector a(N);
for (int i = 0; i < N; i++)
cin >> a[i];
vector last(N + 1, 0);
sort(a.begin(), a.end());
last[N] = 0;
last[N - 1] = a[N - 1];
for (int i = N - 2; i >= 0; i--)
{
last[i] = last[i + 1] + a[i];
}
int pos = 0;
ll ans = 0;
//cout<<"pos="< 0)
{
tmp += abs(a[i] * (pos + 1 - i) + last[i] - last[pos + 1] - 1000 * (pos + 1 - i));
//cout<
链接:https://ac.nowcoder.com/acm/contest/23106/I
来源:牛客网
概率期望题,为了方便考虑,最好的方法是:对于n个up主,每个up主每句都以一定概率p选择唱或者不唱。那么对于每一句,唱失败的概率是 p n + ( 1 − p ) n p^n + (1-p)^n pn+(1−p)n ,为了最小化这个概率 p p p 取 1 2 \frac{1}{2} 21(不会求的话可以猜出来),那么就知道了每一句唱成功的概率,求期望的话乘句子数 m m m 即可: m × 2 n − n 2 n m \times \frac{2^n - n}{2^n} m×2n2n−n ,需要用逆元防止模出错。
const ll modI = 1e9 + 7;
//利用快速幂可以用来快速求a^(p-2)
ll qpow(ll base, ll power, ll mod)
{
ll ans = 1;
base %= mod;
while (power > 0)
{
if (power & 1)
{ //指数为奇数,先乘
ans = ans * base % mod;
}
power >>= 1; //指数取半
base = (base * base) % mod; //基数平方
}
return ans;
}
//求a%p的逆元
ll calInv(ll a, ll p)
{
return qpow(a, p - 2, p);
}
void solveI()
{
int T;
cin >> T;
while (T--)
{
int n, m;
cin >> n >> m;
// m * (1 - 2 * 2^n) = m * (2^n - 2) / 2^n
cout << m * (qpow(2, n, modI) - 2) % modI * calInv(qpow(2, n, modI), modI) % modI << endl;
}
}
链接:https://ac.nowcoder.com/acm/contest/23106/J
来源:牛客网
签到题,闹腾小朋友最多选一半。
bool cmp(int a, int b) { return a > b; }
void solveJ()
{
int T;
cin >> T;
while (T--)
{
int A, B, n;
cin >> A >> B >> n;
vector va(A);
vector vb(B);
for (int i = 0; i < A; i++)
cin >> va[i];
for (int i = 0; i < B; i++)
cin >> vb[i];
sort(va.begin(), va.end(), cmp);
sort(vb.begin(), vb.end(), cmp);
if (2 * A < n)
{
cout << -1 << endl;
continue;
}
int a, b;
a = 0;
b = 0;
ll ans = 0;
for (int i = 0; i < n; i++)
{
if (a < A && (va[a] >= vb[b] || b == B))
{
//cout<<"1numa++ ans+="<
链接:https://ac.nowcoder.com/acm/contest/23106/L
来源:牛客网
第一道签到题
double dist(int x1, int x2, int y1, int y2)
{
return sqrt(pow(x1 - x2, 2) + pow(y1 - y2, 2));
}
void solveL()
{
int T;
cin >> T;
while (T--)
{
int n;
cin >> n;
int x0 = 0;
int y0 = 0;
double ans = 0.0f;
for (int i = 0; i < n; i++)
{
char c;
cin >> c;
if (c == 'L')
{
x0--;
}
else if (c == 'R')
{
x0++;
}
else if (c == 'U')
{
y0++;
}
else if (c == 'D')
{
y0--;
}
ans = max(ans, dist(0, x0, 0, y0));
}
printf("%.10f\n", ans);
}
}