NewCoder Weekly Contest 55
NewCoder Weekly Contest 55
A 小红的字符串
Problem Statement
小红拿到了一个长度为 \(3\) 且仅由小写字母组成的字符串,她每次操作可以修改任意一个字符,小红希望最终所有字符都相同。
你能帮小红求出最小的操作次数吗?
Constraints
Input
第一行输入一个长度为 \(3\) 且仅由小写字母组成的字符串 \(s\)。
Output
在一行上输出一个整数,表示最小的操作次数。
Sample Input 1
aba
Sample Output 1
1
Solution
字符串长度固定为3,暴力罗列情况即可
Code
#include<bits/stdc++.h>
#define int long long
using namespace std;
string s;
void solve()
{
cin >> s;
if (s[0] == s[1] && s[1] == s[2]) cout << 0 << endl;
else if (s[0] == s[1] && s[1] != s[2]) cout << 1 << endl;
else if (s[0] != s[1] && s[1] == s[2]) cout << 1 << endl;
else if (s[0] != s[1] && s[1] != s[2] && s[0] == s[2]) cout << 1 << endl;
else cout << 2 << endl;
}
signed main()
{
ios::sync_with_stdio(0);
cin.tie(0);
cout.tie(0);
solve();
return 0;
}
B 小红的序列乘积
Problem Statement
小红有一个长度为 \(n\) 的整数序列 \(\{a_1,a_2,\dots,a_n\}\),定义 \(f_i = a_1 \times a_2 \times \cdots \times a_i\) ,即前 \(i\) 项的乘积。现在小红想知道 \(f_1, f_2, \dots, f_n\) 中,有多少个数的个位数是 \(6\) 。
Constraints
Input
第一行输入一个整数 \(n \left(1 \leq n \leq 10^5\right)\) 代表序列中元素的数量。
第二行输入 \(n\) 个整数 \(a_1, a_2, \cdots, a_n \left(1 \leq a_i \leq 10^9\right)\) 代表序列元素。
Output
在一行上输出一个整数,代表 \(f_1, f_2, \cdots, f_n\) 中,个位数是 \(6\) 的数的个数。
Sample Input 1
5
1 2 3 4 4
Sample Output 1
2
Solution
你想直到前i项的乘积\(f_i\)个位为6的项有多少个,怎么把个位拎出来,%10即可,这样还能避免溢出,每次相乘mod10即可,结果==6就++
Code
#include<bits/stdc++.h>
#define int long long
using namespace std;
const int N = 1e5 + 10;
int n, cnt;
int a[N];
void solve()
{
cin >> n;
for (int i = 0; i < n; i ++ ) cin >> a[i];
int ans = 1;
for (int i = 0; i < n; i ++ )
{
ans = ans * a[i] % 10;
if (ans == 6) cnt ++ ;
}
cout << cnt << endl;
}
signed main()
{
ios::sync_with_stdio(0);
cin.tie(0);
cout.tie(0);
solve();
return 0;
}
C 小红的数组重排
Problem Statement
给出一个数字 \(n\) 和一个长度为 \(n\) 的数组 \([a_1,a_2,\dots,a_n]\) 。现在你可以以任意方式重新排列数组 \(a\),问是否存在重排后的数组 \(a\) 满足:\(a_1 \times a_2 < a_2 \times a_3 < \cdots < a_{n-1} \times a_n\)。
Constraints
Input
第一行输入一个整数 \(n\left( 3 \leq n \leq 5 \times 10^5\right)\) 代表数组中的元素数量。
第二行输入 \(n\) 个整数 \(a_1, a_2, \cdots, a_n\left(0 \leq a_i \leq 10^9 \right)\) 代表数组元素。
Output
若存在符合题意的排列后的数组 \(a\),在一行上输出 \(\rm YES\) ,随后在第二行上输出这个数组;否则,直接输出 \(\rm NO\) 。
如果存在多个解决方案,您可以输出任意一个。
Sample Input 1
4
7 2 5 1
Sample Output 1
YES
1 5 2 7
Solution
题目要求是若a[i]* a[i+1]恒小于a[i+1]* a[i+2],则当前数组满足要求
我们不妨将数组排好序,这样大多数元素将满足a[i]* a[i+1] < a[i+1]* a[i+2]的要求,之后再判断若有三个连续元素相等的话则不满足要求。
本来以为这样就能AC了,不然,我们还有一个特殊条件没考虑到,若是碰到0怎么办?
不难发现,数组0若是位于数组中部,一个0就能让前后两个乘积结果相等,若0位于开头,两个0会让乘积结果相等,特判一下0即可。
Code
#include<bits/stdc++.h>
#define int long long
using namespace std;
const int N = 5e5 + 10;
int n;
int a[N];
void solve()
{
cin >> n;
for (int i = 0; i < n; i ++ ) cin >> a[i];
sort(a, a + n);
if (a[0] == 0 && a[1] == 0)
{
cout << "NO" << endl;
return;
}
bool flag = true;
for (int i = 2; i < n; i ++ )
{
if ((a[i] == a[i - 1] && a[i] == a[i - 2]) || (a[i] == 0))
{
flag = false;
break;
}
}
if (flag) cout << "YES" << endl;
else
{
cout << "NO" << endl;
return;
}
for (int i = 0; i < n - 1; i ++ ) cout << a[i] << " ";
cout << a[n - 1] << endl;
}
signed main()
{
ios::sync_with_stdio(0);
cin.tie(0);
cout.tie(0);
solve();
return 0;
}
D 虫洞操纵者
Problem Statement
你需要在一个可以上下左右移动的 \(n\times n\) 棋盘上解开一个迷宫:棋盘四周都是墙;每个方格要么是可以通过的空方格 \(\sf '0'\) ,要么是不可通过的墙方格 \(\sf '1'\) ;你可以沿着空方格通行;你已经被传送到了迷宫的左上角(第 \(1\) 行第 \(1\) 列的位置),你知道终点位于迷宫右下角(第 \(n\) 行第 \(n\) 列的位置)。
别人都不知道的是,你是一个虫洞大师,当你所在的位置能同时看到左右两侧或上下两侧最近的那两面相对的墙时,你可以在这两面墙上开启虫洞,靠近后从一侧进入,穿越它到达另一侧。
现在,你准备好以最短的步数离开迷宫了吗!
Constraints
\(n\left(2\le n \le 10^3\right)\)
\(a_{i,1},a_{i,2},\dots,a_{i,n} \left(0\le a_{i,j}\le 1\right)\)
Input
第一行输入一个整数 \(n\left(2\le n \le 10^3\right)\) 代表迷宫的大小。
此后 \(n\) 行,第 \(i\) 行输入 \(n\) 个整数 \(a_{i,1},a_{i,2},\dots,a_{i,n} \left(0\le a_{i,j}\le 1\right)\) 代表迷宫中第 \(i\) 行第 \(j\) 列这个格子中的情况,其中 \(0\) 代表空方格,\(1\) 代表墙方格。保证起点和终点不为墙壁。
Output
在一行上输出一个整数,代表离开迷宫需要的最短步数,如果无论如何都无法离开迷宫,则直接输出 \(-1\) 。
Sample Input 1
5
0 0 0 1 0
0 0 0 0 0
0 0 0 0 0
0 0 0 1 0
1 0 0 0 0
Sample Output 1
3
说明
该样例示意图如下图所示。全部墙壁的位置都使用粗黑线标注。先从 \((1,1)\) 向右移动一步走到 \((1,2)\) ,此时可以开启一个虫洞(使用紫色箭头标注),向上移动一步到达 \((5,2)\) ;此时可以开启第二个虫洞(使用红色箭头标注),向左移动一步到达 \((5,5)\) 。
\(\,\,\,\,\,\,\,\,\,\,\)注意,在该样例中,不能直接从 \((1,1)\) 开启虫洞(使用黑色箭头标注)到达 \((1,5)\) ,因为 \((1,4)\) 存在两面墙遮挡了视线;不能直接从 \((4,3)\) 开启虫洞(使用绿色箭头标注)到达 \((1,3)\) ,因为这两面墙不是相对的。
Solution
这道题是最短路+一些针对题目自身的改动,从左上角走到右下角,如果碰到墙可以穿越到当前墙的对面墙那一侧。
最短路优先使用BFS,每走一步我们对四个方向进行处理,分为碰到墙和没有碰到墙壁的情况
如果碰到了墙壁,利用循环穿到对应墙的一侧,若新坐标没有访问过,将更新后的坐标入队
这里有个非常需要注意的点,每次找到对应墙后,无论新点是否被访问过,都应该break掉退出循环,否则很有可能穿越到边界的墙,举个例子,上图中从1,1向左穿应该穿到1,3 ,若此时1,3已经访问过了,没有退出循环的话,循环继续进行会找到1,6这个边界墙,并将坐标1,5入队,这样就出现了错误,是不可能穿越到1,5的。所以记得break。
Code
#include<bits/stdc++.h>
#define int long long
using namespace std;
const int INF = 0x3f3f3f3f;
typedef pair<int, int> PII;
const int N = 1e3 + 10;
int dx[] = {0, 0, 1, -1}, dy[] = {1, -1, 0, 0};
int n;
void solve()
{
cin >> n;
vector<vector<int> > g(n + 2, vector<int>(n + 2, 1)); //二维数组初始化的好方法
for (int i = 1; i <= n; i ++ )
for (int j = 1; j <= n; j ++ )
{
cin >> g[i][j];
}
vector<vector<int> > dist(n + 5, vector<int>(n + 5, -1));
dist[1][1] = 0;
queue<PII> q;
q.push({1, 1});
while (q.size())
{
auto t = q.front();
q.pop();
//if (t.first == n && t.second == n) break; //由于初始化所有位置都是墙壁,不会出现无限循环的问题
//cout << endl << t.first << t.second << endl;
for (int i = 0; i < 4; i ++ )
{
int a = t.first + dx[i], b = t.second + dy[i];
if (dist[a][b] != -1) continue; //如果该位置已经处理,跳过,最短路的关键就是不走回头路
//cout << a << b << endl;
if (g[a][b] == 1)
{
//如果移动到的新位置是墙,在该移动方向进行穿越
if (i == 0) //右穿
{
for (int j = t.second; j >= 0; j -- )
{
if (g[a][j] == 1)
{
if (dist[a][j + 1] == -1)
{
//cout << 1 << a << j + 1 << endl;
q.push({a, j + 1});
dist[a][j + 1] = dist[t.first][t.second] + 1;
}
break; //之前把break放进if (dist语句内导致MLE,处理了很久,根本原因是没有理解到最短路的核心思路,如果碰到墙壁不break的话,就会越过墙壁穿越,导致结果错误
}
}
}
else if (i == 1) //左穿
{
for (int j = t.second; j <= n + 1; j ++ )
{
if (g[a][j] == 1)
{
if (dist[a][j - 1] == -1)
{
//cout << 2 << a << j - 1<< endl;
q.push({a, j - 1});
dist[a][j - 1] = dist[t.first][t.second] + 1;
}
break;
}
}
}
else if (i == 2) //下穿
{
for (int j = t.first; j >= 0; j -- )
{
if (g[j][b] == 1)
{
if (dist[j + 1][b] == -1)
{
//cout << 3 << j + 1<< b << endl;
q.push({j + 1, b});
dist[j + 1][b] = dist[t.first][t.second] + 1;
}
break;
}
}
}
else if (i == 3) //上穿
{
for (int j = t.first; j <= n + 1; j ++ )
{
if (g[j][b] == 1)
{
if (dist[j - 1][b] == -1)
{
//cout << j - 1 << b << endl;
q.push({j - 1, b});
dist[j - 1][b] = dist[t.first][t.second] + 1;
}
break;
}
}
}
}
else //若该位置不是墙,将其入队
{
q.push({a, b});
dist[a][b] = dist[t.first][t.second] + 1;
}
}
}
cout << dist[n][n] << endl;
}
signed main()
{
ios::sync_with_stdio(0);
cin.tie(0);
cout.tie(0);
solve();
return 0;
}
E 小红的序列乘积2.0
Problem Statement
对于一个长度为 \(m\) 的整数序列 \(\{b_1,b_2,\dots,b_m\}\),定义 \(f_i = b_1 \times b_2 \times \cdots \times b_i\) ,即前 \(i\) 项的乘积。这个序列的权值即为 \(f_1, f_2, \dots, f_m\) 中个位数是 \(6\) 的数字个数。
小红有一个长度为 \(n\) 的整数序列 \(\{a_1,a_2,\dots,a_n\}\),她想知道这个序列全部 \(2^n-1\) 个非空子序列的权值和是多少。
如果序列 \(a\) 可以通过删除序列 \(b\) 中的若干(可能为零或全部)元素得到,则序列 \(a\) 是序列 \(b\) 的子序列。
Constraints
Input
第一行输入一个整数 \(n \left(1 \leq n \leq 10^5\right)\) 代表序列中元素的数量。
第二行输入 \(n\) 个整数 \(a_1, a_2, \cdots, a_n \left(1 \leq a_i \leq 10^9\right)\) 代表序列元素。
Output
在一行上输出一个整数,输出一个整数,表示全部子序列的权值和。由于答案可能很大,请将答案对 \((10^9+7)\) 取模后输出。
Sample Input 1
3
4 4 6
Sample Output 1
4
对于子序列 \([4]\) ,没有贡献;
对于子序列 \([6]\) ,\(f=\{6\}\) 贡献为 \(1\) ;
对于子序列 \([4,4]\) ,\(f=\{4,16\}\) 贡献为 \(1\) ;
对于子序列 \([4,6]\) ,\(f=\{4,24\}\) ,没有贡献;
对于子序列 \([4,4,6]\) ,\(f=\{4,16,96\}\) 贡献为 \(2\) 。
Solution
这道题是让我们求出所有子序列的权值后相加,这里权值的求法是在每个子序列内算前i项的乘积和找出低位为6的项数即为该子序列的权值,我们对每个点独立分析
dp[i][j]表示前i个数的乘积为j的方案数
,例如对于序列1 2 3,前i个数乘积为2的方案数为2,(1, 2;2)dp[3][2]=2
对于前i项的乘积底数在1~9都有可能,取决于前i-1项的底数j * a[i]的结果,所以我们枚举前i-1项底数结果1~9(当乘积底数结果为0时对答案没有贡献)
所以状态转移方程 dp[i][j * a[i] % 10] += dp[i - 1][j]
即当前底数为j * a[i] % 10的方案由dp[i - 1] [j]方案贡献。还要记得将dp[i][j] += dp[i - 1][j]
也一并转移。
当 j * a[i] % 10 == 6时,当前方案就是我们需要的方案,我们就把这种方案的方案数加到答案里,还要注意当我们找到答案后还需要对当前元素之后的子序列进行组合数处理
举个例子,对于序列1 2 3 4 5,当我们枚举到 1 2 3 时,此时i = 3,当j==2时此时的dp[3][2 * 3 % 10] = dp[3][6]
符合答案要求需要记录,首先dp[3 - 1][2]
我们肯定需要记录,我们还要对于i=3之后的元素进行处理,4 5无论怎么排列都可以,也就是说对于当前这种情况,1 2 3,1 2 3 4, 1 2 3 5,1 2 3 4 5,都是符合要求的 也就是有 \(2^{n-i}\)种可能,再乘上前面j == 2的两种方案,所以当前情况下
ans += dp[i - 1][j] * qmi(2, n - i);
我认为这里有一个很细节的点,为什么ans = (ans + dp [i - 1] [j] * qmi(2, n - i) % mod) % mod;
只利用前i-1个数结尾为j的方案数就把当前结尾为6的方案数处理好了?那如果a[i]=6,那不是忽略了自身这种方案了吗?
目前我是这么想的,对于当前元素值为6时,在循环到j=1时,ans就已经加上了自身这种方案,即dp[i-1] [1]* qmi(2, n - i)。
对于其他的数,例如4 就只需要看前面结尾为4和9的方案数即可
Code
#include<bits/stdc++.h>
#define int long long
using namespace std;
const int INF = 0x3f3f3f3f;
const int mod = 1e9 + 7;
const int N = 1e5 + 10;
int n, ans;
int a[N];
int dp[N][15]; //dp[i][j]表示前i个数中以数字j结尾的方案个数
int qmi(int a, int b)
{
int res = 1 % mod;
while (b)
{
if (b & 1)
res = res * a % mod;
a = a * a % mod;
b >>= 1;
}
return res;
}
void solve()
{
cin >> n;
for (int i = 1; i <= n; i ++ )
{
cin >> a[i];
}
dp[0][1] = 1; //关键点,让1号元素的前一个位置上结尾为1的方案数为1,从而可以利用 dp[i][j * a[i] % 10] = (dp[i][j * a[i] % 10] + dp[i - 1][j]) % mod; 顺利地让之后的dp[i][a[i]]=1
//就可以实现每一个位置将自身作为一个子序列的方案数得以记录
for (int i = 1; i <= n; i ++ )
{
for (int j = 1; j <= 9; j ++ ) //对每一个位置,枚举所有数字结尾 0没用
{
if (j * a[i] % 10 == 6)
{
ans = (ans + dp[i - 1][j] * qmi(2, n - i) % mod) % mod; //如果找到了符合题目要求的以数字6结尾的方案
}
dp[i][j] = (dp[i][j] + dp[i - 1][j]) % mod;
dp[i][j * a[i] % 10] = (dp[i][j * a[i] % 10] + dp[i - 1][j]) % mod;
}
}
cout << ans << endl;
}
signed main()
{
ios::sync_with_stdio(0);
cin.tie(0);
cout.tie(0);
solve();
return 0;
}