牛客竞赛_ACM/NOI/CSP/CCPC/ICPC算法编程高难度练习赛_牛客竞赛OJ (nowcoder.com)
注:本题解只涉及赛时思路和赛后补题收获,人菜佬勿喷
赛时七题【ACGHILM】补题【EFJKB】
这场...特判很多,非常需要动脑子。
A.mutsumi的质数合数
A-mutsumi的质数合数_2024牛客寒假算法基础集训营5 (nowcoder.com)
分析:
签到题,只需要特判 1 这个数,因为 1 既不是质数也不是合数
代码:
void solve(){
int n;
cin >> n;
vector<int>a(n);
int sum = 0;
for(int i = 0; i < n; i++){
cin >> a[i];
if(a[i] == 1)
sum++;
}
cout << n - sum << endl;
}
C.anon的私货
C-anon的私货_2024牛客寒假算法基础集训营5 (nowcoder.cc
分析:
分析题意可以发现,每两个数为一组的话最多可以插入较小数 −1 个 0 ,但是,在这两个数插入 0 之后又会对下两个数产生影响,所以只需要用一个变量 m 来记录上两个数的较小值 −1 ,然后判断是否会对当前这两个数产生影响,如果不影响,再取第一个数减去上一组 0 的个数和第二个数的较小值 −1 即可,如果影响那么这组数不能加。
除此之外还要特判 1 这个数, n 等于 1 时直接输出第一个数 −1 即可
注意数据范围,记得开 longlong
代码:
void solve(){
ll n;
cin >> n;
vector<ll>a(n + 2), b(n + 2);
ll sum = 0, num = 0;
for(ll i = 1; i <= n; i++){
cin >> a[i];
}
if(n == 1){
cout << a[1] - 1 << endl;
return;
}
ll ans = a[1] - 1;
b[0] = ans;
for(int i = 1; i <= n - 1; i++){
ll m = min(a[i], a[i + 1]) - 1;
if(a[i] - 1 > b[i - 1])
b[i] = min(a[i] - 1 - b[i - 1], m);
else
b[i] = 0;
ans += b[i];
}
if(a[n] - 1 > b[n - 1]){
cout << ans + a[n] - 1 - b[n - 1] << endl;
return;
}
cout << ans << endl;
}
G.sakiko的排列构造(easy)
G-sakiko的排列构造(easy)_2024牛客寒假算法基础集训营5 (nowcoder.com)
分析:
这道题很有意思,搓一下样例可以发现答案必定是一段一段连续的数。
比如:1 2 3 4 5 6
可以先找大于6的质数,找到后倒序排列:6 5 4 3 2 1 可以发现 都是7
但发现 1 2 3 4 5 6 7 8这组样例,大于8的最小质数是11,所以需要把8放在 i=4 的位置上倒序排列,那前三个该如何凑到11,再看题目给的样例:1 2 3可以排列成1 3 2,这个问题便解决了。
所以这道题的解题思路就是:先用欧拉筛筛出质数,然后凑出倒序的部分,再填补前半部分即可。
代码:
const int N = 5e6 + 10;
int p[N],cnt;
bool vis[N];
bool prime[N];
void init(int n){
for(int i=2;i<=n;++i){
if(vis[i]==0){
p[cnt]=i;
cnt++;
prime[i]=true;
for(int j=i+i;j<=n;j+=i) vis[j]=1;
}
}
}
void solve(){
init(2e6 + 5);
int n;
cin >> n;
int l = 1;
vector<int>a;
int pos[N];
for(int i = n; i >= 1; i--){
while(prime[l+i] == 0 || pos[l] == 1){
l++;
if(l > n)
l = 1;
}
a.push_back(l);
pos[l] = 1;
l++;
if(l > n)
l = 1;
}
for(int i = a.size() - 1; i >= 0; i--)
cout << a[i] << " ";
cout << endl;
}
H.sakiko的排列构造(hard)
H-sakiko的排列构造(hard)_2024牛客寒假算法基础集训营5 (nowcoder.com)
分析:
hard版本与easy版本不同的是数据范围不同,采用埃氏筛筛出质数后复杂度可以通过此题。
思路同G
代码:
同G
I.rikki的最短路
I-rikki的最短路_2024牛客寒假算法基础集训营5 (nowcoder.com)
分析:
签到题。根据题意,需要先去T再去A。所以先判断A与T是否在同一侧,如果在同一侧,判断A与T的大小,A的绝对值大那么答案就是A的绝对值+A到T的距离,否则即为T。如果不在同一侧,判断A与K的大小关系,K大于A那么只能是先去A再去T,答案是2*A+T,否则只能先去T再去找A,答案是3*T+2*A
注意数据范围,记得开 longlong
代码:
void solve(){
ll t, a, k;
cin >> t >> a >> k;
if(t < 0){
if(a < 0){
if(t > a){
if(t - a > k)
cout << -t + 2 * (t - a);
else
cout << -a + (t - a);
}
else
cout << -t;
}
else{
if(a > k){
cout << 2 * a + 3 * (-t);
}
else
cout << 2 * a + (-t);
}
}
else{
if(a > 0){
if(t < a){
if(a - t > k)
cout << t + 2 * (a - t);
else
cout << a + (a - t);
}
else
cout << t;
}
else{
if(-a > k){
cout << 2 * (-a) + 3 * t;
}
else
cout << 2 * (-a) + t;
}
}
}
L.anon的星星
L-anon的星星_2024牛客寒假算法基础集训营5 (nowcoder.com)
分析:
签到题。根据题意模拟即可。(赛时直接用数学式子交了一发,不知道哪写错了,喜提一发wa,老老实实暴力枚举去了
代码:
void solve(){
int n, x;
cin >> n >> x;
for(int i = 0; i <= n; i++)
for(int j = 0; j <= n; j++){
if(i - j == x && i + j == n){
cout << i << " " << j << endl;
return;
}
}
cout << -1 << endl;
}
M.mutsumi的排列连通
M-mutsumi的排列连通_2024牛客寒假算法基础集训营5 (nowcoder.com)
分析:
这道题跟第一场的关鸡很像,关鸡分鸡(?
之所以说跟关鸡很像是因为这两道题需要判断的条件是类似的。(都是找同一列是否有相同的数或者相邻列不同行有没有相同的数字)如果不明白这个条件,传送门(第一场题解:2024牛客寒假算法基础训练营1 - 知乎 (zhihu.com))
分析可以发现,最多删两个即可将矩阵分成连通块,如果有满足条件的则只需要操作一次。
然后特判n为1 和 n为2的时候,n为1是一定不能完成的,n等于2的时候如果出现同一列的数字相同也是不能完成的,否则只需操作一次
代码:
void solve(){
int n;
cin >> n;
vector<int>a(n + 2), b(n + 2);
for(int i = 1; i <= n; i++){
cin >> a[i];
}
for(int i = 1; i <= n; i++)
cin >> b[i];
if(n == 1){
cout << -1 << endl;
return;
}
if(n == 2){
if(a[1] == b[1] || a[2] == b[2])
cout << -1 << endl;
else
cout << 1 << endl;
return;
}
for(int i = 1; i <= n; i++){
if((a[i] == b[i] && i != 1 && i != n) || a[i] == b[i - 1] || a[i] == b[i + 1]){
cout << 1 << endl;
return;
}
}
cout << 2 << endl;
}
E.soyorin的数组操作(easy)
E-soyorin的数组操作(easy)_2024牛客寒假算法基础集训营5 (nowcoder.com)
分析:
不难发现当n为偶数的时候是一定可以完成的。所以我们只需要讨论n为奇数的情况
直接将数组从后往前遍历,每个数的操作的次数是可以根据当前的下标计算出来的
直接根据题意模拟一下 ,如果出现操作后还为降序的情况直接输出NO即可
注意数据范围记得开 longlong
代码:
void solve(){
ll n;
cin >> n;
vector<ll>a(n + 1);
for(ll i = 1; i <= n; i++)
cin >> a[i];
if(n % 2 == 0){
cout << "YES\n";
return;
}
ll pos = 0;
for(ll i = n - 1; i > 1; i -= 2){
a[i] += pos * i;
a[i - 1] += pos * (i - 1);
if(a[i] > a[i + 1]){
cout << "NO\n";
return;
}
ll t = (a[i + 1] - a[i]) / i;
a[i] += t * i;
a[i - 1] += t * (i - 1);
pos += t;
}
if(is_sorted(a.begin(), a.end())){
cout << "YES\n";
return;
}
cout << "NO\n";
}
F.soyorin的数组操作(hard)
F-soyorin的数组操作(hard)_2024牛客寒假算法基础集训营5 (nowcoder.com)
分析:
基本思路跟E题是一样的,n为偶数是一定能完成的,n为奇数时从后往前遍历数组并记录所需要的次数即可
代码:
void solve(){
ll n;
cin >> n;
vector<ll>a(n + 1);
for(ll i = 1; i <= n; i++)
cin >> a[i];
if(n & 1){
ll cnt = 0;
ll t = 0;
auto b = a;
{
ll cnt = 0;
for(ll i = n - 1; i >= 2; i -= 2){
b[i] += cnt * i;
b[i - 1] += cnt * (i - 1);
if(b[i] > b[i + 1]){
cout << -1 << endl;
return;
}
ll t = (b[i + 1] - b[i]) / i;
b[i] += t * i;
b[i - 1] += t * (i - 1);
cnt += t;
}
if(!is_sorted(b.begin(), b.end())){
cout << -1 << endl;
return;
}
}
for(ll i = n - 1; i >= 1; i -= 2){
a[i] += i * t;
a[i - 1] += (i - 1) * t;
if(a[i + 1] < a[i]){
ll x = a[i] - a[i + 1];
cnt -= x;
t += x;
a[i - 1] += x * (i - 1);
a[i] += x * i;
a[i + 1] += x * (i + 1);
a[i + 2] += x * (i + 2);
}
else
cnt += (b[i + 1] - a[i]) / i;
ll x = a[i - 1] - a[i];
if(x > 0){
cnt -= x;
t += x;
a[i] += x * i;
a[i - 1] += x * (i - 1);
}
if(cnt < 0){
cout << -1 << endl;
return;
}
}
cout << t << endl;
}
else{
ll cnt = 0;
for(ll i = n ; i >= 1; i--){
ll l = a[i - 1] + (i - 1) * cnt;
ll r = a[i] + i * cnt;
if(r < l)
cnt += l - r;
}
cout << cnt << endl;
}
}
J.rikki的数组陡峭值
J-rikki的数组陡峭值_2024牛客寒假算法基础集训营5 (nowcoder.com)
分析:
赛时读错题,读成 在
和
中选一个数,再求最小的陡峭值,那便是一个很简单的线性dp,
用二维数组 dp[i][0] 和 dp[i][1] 来分别表示第 i个数选第一个和第 i 个数选第二个
很容易得到转移方程:
dp[i][1]=min(dp[i−1][0]+,dp[i−1][1]+
)
dp[i][0]=min(dp[i−1][0]+,dp[i−1][1]+
)
但是这个题是在区间中选择一个数,分析陡峭值的定义,发现每两个数要想陡峭值越小,就需要取这两个区间的交集,所以我们用两个变量 l,r 来维护区间的交集,当出现相邻区间交集为空时就更新维护交集,当把交集维护到一个数组中时,不难发现,这个题就变成了读错题的dp形状。
代码:
void solve(){
ll n;
cin >> n;
vector<pair<ll, ll>>a(n);
for(int i = 0; i < n; i++)
cin >> a[i].first >> a[i].second;
ll r = 1e9, l = -1;
vector<vector<ll>>v;
for(int i = 0; i < n; i++){
if(r < a[i].first || l > a[i].second){
v.push_back({l, r});
l = a[i].first;
r = a[i].second;
}
l = max(l, a[i].first);
r = min(r, a[i].second);
}
v.push_back({l, r});
vector dp(v.size(), vector(2, 0LL));
for(int i = 1; i < v.size(); i++){
dp[i][1] = min(dp[i - 1][0] + abs(v[i][1] - v[i - 1][0]), dp[i - 1][1] + abs(v[i][1] - v[i - 1][1]));
dp[i][0] = min(dp[i - 1][0] + abs(v[i][0] - v[i - 1][0]), dp[i - 1][1] + abs(v[i][0] - v[i - 1][1]));
}
cout << min(dp.back()[0], dp.back()[1]) << endl;
}
K.soyorin的通知
K-soyorin的通知_2024牛客寒假算法基础集训营5 (nowcoder.com)
分析:
赛时没看,赛后发现是个很经典的完全背包问题,用 dp[i] 表示传播给 i 个人花费的代价
状态转移方程与普通完全背包类似
=min(
,
−
+
)
这个状态转移方程是对花费 传播给
个人的
题目还有种可能就是花费 p 传给1个人,用一个循环枚举这种方式的个数,最后取最小值即可
代码:
void solve(){
ll n, p;
cin >> n >> p;
vector<ll>dp(n + 1, 1e9), a(n + 1), b(n + 1);
ll ans = 1e18;
dp[0] = 0;
for(ll i = 0; i < n; i++){
cin >> a[i] >> b[i];
if(b[i] >= n)
b[i] = n - 1;
}
for(ll i = 0; i < n; i++){
for(ll j = 0; j <= n; j++){
dp[j] = min(dp[j], dp[max(0LL, j - b[i])] + a[i]);//花费ai的情况
}
}
for(ll i = 0; i < n; i++){//枚举花费p传给1个人的个数时花费的代价
ans = min(ans, dp[i] + (n - i) * p);
}
cout << ans << endl;
}
B.omorin的字符串迷茫值
B-tomorin的字符串迷茫值_2024牛客寒假算法基础集训营5 (nowcoder.com)
分析:
"mygo"字串能够在固定在删除的方式下形成只有这八种:
∗ 可以代表任意字符,这八种只能通过删除 ∗ 来得到,所以方案数是1,考虑每种字串只需要计算该字串前后有多少种删除方式即可。
摸一下题意可以发现:删除长度为1的字符方案数是1,删除长度为2的字符方案数是2,删除长度为3的字符方案数是3,删除长度为4的字符方案数为5,删除长度为5的方案数为8......
可以发现是一个斐波那契数列,所以这个题只需要枚举字符串每个点,检查这个点后的字符串能否形成八种字串中的一种,如果能的话只需将答案加上该字串的前后任意删除方案数之一即可。
代码:
void solve(){
string s;
cin >> s;
int n = s.size();
vector<string>a = {"mygo", "m*ygo", "my*go", "myg*o", "m*y*g*o", "m*y*go", "m*yg*o", "my*g*o"};
vector<ll>dp(n + 1);
dp[0] = 1;
dp[1] = 2;
dp[2] = 3;
int ans = 0;
for(int i = 3; i <= n; i++)
dp[i] = (dp[i - 1] + dp[i - 2]) % mod;
for(int i = 0; i < n; i++){
for(int j = 0; j < 8; j++){
int flag = 1;
for(int k = 0; k < a[j].size(); k++){
if(a[j][k] == '*')
continue;
if(s[i + k] != a[j][k]){
flag = 0;
break;
}
}
if(flag)
ans = (ans + (dp[i] * dp[n - i - a[j].size()]) % mod) % mod;
}
}
cout << ans << endl;
}