前言
第二次选拔赛,,,,看这个成绩,,,,战况糟糕,,,只做出来了简单题,,,
秉持着什么比赛尽量复现一遍,,,,故今晚就干这个了。。。 ——2021.3.20
题解参考:https://ac.nowcoder.com/acm/discuss/blogs?tagId=140529
题目地址:https://ac.nowcoder.com/acm/contest/12478
A 暗号1
wuwuwu,,,看着还行,,,就写出来容易超时,,,
思路
将字符串按照第一次出现的顺序替换【使得本质相同的字符串一样】
利用map对字符串进行映射,<string,int>
,使得键对应字符串,值对应出现的次数
将map对应string的下标存入数组,最后的查询在区间内的下标个数即可。
unordered_map
内部实现哈希表(散列表),将关键码值映射到hash表中的一个位置来访问
查找的时间复杂度位O(1)
元素的排列顺序是无序的。
1 2 3 #include <unordered_map> //头文件 unordered_map <string ,int > id; id[s] = ++tot;
upper_bound和lower_bound 功能:利用二分查找
的方法在一个排好序的数组
中进行查找的。
特点:
从begin到end-1的位置开始查找
二分查找,有序数组
返回找到数字的下标
1 2 3 4 lower_bound(begin ,end ,num); upper_bound(begin ,end ,num);
万能头文件
包括了基本所有的头文件,,,方便使用。
具体查看里面都有什么头文件请查看
memset 功能:初始化函数,将某一块内存中的内容全部设置为指定的值
1 2 #include <bits/stdc++.h>//emm memset (vis,-1 ,sizeof (vis));
第一个参数:初始化数组
第二个参数:赋的值
第三个参数:大小
exp 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 #include <iostream> #include <bits/stdc++.h> #include <unordered_map> #include <vector> using namespace std ;int vis[30 ];unordered_map <string ,int > id;int tot;const int N = 1e5 ;vector <int > e[N];string tran (string s) { memset (vis,-1 ,sizeof (vis)); int len = s.size (); int cnt = -1 ; for (int i = 0 ; i < len; ++i) { if (vis[s[i] - 'a' ] == -1 ){ vis[s[i] - 'a' ] = ++cnt; } s[i] = vis[s[i] - 'a' ] + 'a' ; } return s; } int get (int id,int l,int r) { return upper_bound(e[id].begin (),e[id].end (),r) - lower_bound(e[id].begin (),e[id].end (),l); } int main () { int n,q; cin >>n>>q; for (int i = 1 ; i <= n ; ++i) { string s; cin >>s; s = tran(s); if (!id[s]){ id[s] = ++tot; } e[id[s]].push_back(i); } while (q--){ string s; cin >>s; int l,r; cin >>l>>r; s = tran(s); if (!id[s]){ cout <<0 <<endl ; }else { cout <<get (id[s],l,r)<<endl ; } } return 0 ; }
B 暗号2
当时看第二题,,,一看对其做hash,感觉还好,,,好吧,我对什么感觉都还好,,但是不会做TATATAT
思路 使用动态规划dp来做,,,【只能说我看懂了, 能不能想到就难说了】
dp[i][j]
表示长度为i,bash值为j
的字符串有几个【第一个长度是len,21即可;第二个长度是p的最大值,100001即可】 初值:dp[0][0] = 1
对于长度为i的字符串,它是长度为i-1的字符串(令hash值为j【因为hash值对p求余,故hash值小于p】)加上一个字符k∈[0,26)
得到的,故得到最终的hash值是(j*base+k)%p
长度为i的hash值如上,它的值是:dp[i][v] = dp[i][v] + dp[i-1][j];
,
最后,输出的结果是dp[n][hashsum]
增强for循环
auto 功能:根据后面的值,来自己推测前面的类型是什么。
exp 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 #include <bits/stdc++.h> using namespace std ;long long dp[21 ][100001 ]; const long long mod = 1e9 + 7 ;int main () { long long base,p,n; string s; cin >>base>>p>>n>>s; long long hashsum=0 ; for (auto i:s) hashsum = (hashsum*base+i-'a' )%p; dp[0 ][0 ] = 1 ; for (int i = 1 ; i <= n; ++i) { for (int j = 0 ; j < p; ++j) { for (int k = 0 ; k < 26 ; ++k) { int v = (j * base + k) % p; dp[i][v] += dp[i-1 ][j]; dp[i][v] %= mod; } } } cout <<dp[n][hashsum]<<endl ; }
C 3.6数对
题目是比较简单的,,,,比赛的时候也做出来了。。。但是题解更有意思,,,,提供一种思路
我的exp 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 #include <iostream> #include <vector> using namespace std ;void check (vector <int > &v) { int time=0 ; for (int i = 0 ; i < v.size ()-1 ; ++i) { for (int j = i+1 ; j < v.size (); ++j) { if (v[i] == 2 *v[j] || v[i]*2 == v[j]){ time++; } } } cout <<time<<endl ; } int main () { int n; cin >>n; for (int i = 0 ; i < n; ++i) { int n1; cin >>n1; vector <int > v (n1,0 ) ; for (int j = 0 ; j < n1; ++j) { cin >>v[j]; }check(v); } return 0 ; }
题解【桶排】 思路
使用数组下标表示值,因为最大值才100,开辟一个101的数组空间即可
输入一个数x,让a[x]++,让数组里面存储下标对应的个数
最后ans是用循环+a[i] * a[i*2]
得到的,因为多个值的话,其实就是乘积
高级,,,这种方法又称作桶排。
exp 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 #include <bits/stdc++.h> using namespace std ;int a[101 ];int main () { int T; cin >>T; while (T--){ int n; cin >>n; memset (a,0 ,sizeof (a)); for (int i = 1 ; i <= n; ++i) { int x; cin >>x; a[x]++; } int ans = 0 ; for (int i = 1 ; i <= 50 ; ++i) { ans += a[i] * a[i*2 ]; } cout <<ans<<endl ; } return 0 ; }
D 圆的交点
思维题,,哭哭,好嘛,我也想过,只不过没想明白 orz~~
思路 分为三种情况:
相邻的交点
两两相交,交点有两个
组合方式计算:
a.一排横着两两相交:a种
b.共有b+1排
故此情况为a*(b+1)
同理,纵向两两相交:b种,共有a+1排
故此情况共有交点:2*[a*(b+1)+b*(a+1)]
2.斜对面
这种情况,交点都在圆心处,,
相切
交点也都在圆心处,,,
所以,所有的圆心都是交点,也就是(a+1)*(b+1),也就是所有圆的个数
综上:所以最终答案为2*[a*(b+1)+b*(a+1)]+(a+1)*(b+1)
exp 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 #include <bits/stdc++.h> using namespace std ;const long long mod = 1e9 +7 ;int main () { int T; cin >>T; while (T--){ long long a,b; cin >>a>>b; cout <<(2 *( a*(b+1 ) + b*(a+1 ) ) + (a+1 )*(b+1 ))%mod<<endl ; } return 0 ; }
E 孪生质数
emm,这道题,,,,是真的搞不出来,虽然答案能算,但是超时,惨兮兮,涉及知识点盲区
设计了筛法计算质数,快速幂还有逆元(就是a的逆,,,),一个个来说说。
筛法求质数
参考:
简介 普通的求质数方法:两层循环,通过判断该数能否被从1开始的任意数整除,如果可以的话,为合数;都不可的话,为质数;
改进的普通筛法:已知2是质数,则令它与2,3,4…相乘得到的数都是合数,它的复杂度为O(NloglogN)
欧拉筛法:又称线性筛,它的时间复杂度为O(n),普通筛法有问题是,同样的一个数,如24,他既会被2筛,也会被3、4、6、8、12筛掉,不好,所以它改进为一个合数只被它最小的质因数筛掉。
代码 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 const int N = 1e7 +1e3 ;bool ok[N];int prim[N];int pos=0 ;void init () { for (int i = 2 ; i < N;++i) { if (ok[i] == 0 ){ prim[pos++] = i; } for (int j = 0 ; j < pos; ++j) { if (i*prim[j] > N) break ; ok[i*prim[j]] = 1 ; if (i%prim[j] == 0 ) break ; } } }
快速幂 思路 求解a^b(以2^10为例),步骤如下:
a = 2,b = 10(1010b【二进制表示】)
2^10 = 2^(2^0*0 + 2^1*1 + 2^2*0 + 2^3*1
下面的代码正是基于上面的思路:
若a为2,b = 10(1010b)
初始 a = 2,b = 1010b,ans = 1;
第一次循环,因为b最低位不为1,故a = a ^ 2,ans = 1;
b右移,b = 101,继续循环
第二次循环,因为b = 101,最低位为1,故ans = a ^ 2,a = a^4
b右移,b = 10,继续循环
第三次循环,因为b = 10,最低位不为1,故ans = a^2,a = a^8
b右移,b = 1,继续循环
第四次循环,因为b = 1,最低位为1,故ans = a^2 * a^8 = a^10 , a=a^16
b继续右移,b=0,循环结束
代码 1 2 3 4 5 6 7 8 9 10 11 int qmod (int a,int b) { int ans = 1 ; while (b){ if (b&1 ) ans = ans*a%mod; a = a*a%mod; b>>=1 ; } return ans; }
本题求解 思路
首先根据筛法求质数,将所有的质数保存在prim数组中
循环prim数组,找到所有的孪生质数,保存在a数组(向量)中
因为抽中的概率就是
k表示的是孪生质数的对数
化简为
根据vector,容易得到k
剩余1/(n*n-1),要求mod,,,需要快速幂,然后按着公式直接计算即可得到结果
exp 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 #include <bits/stdc++.h> using namespace std ;#define int long long const int N = 1e7 +1e3 ;const int mod = 1e9 +7 ;bool ok[N];int prim[N];int pos=0 ;vector <int >a ;void init () { for (int i = 2 ; i < N;++i) { if (ok[i] == 0 ){ prim[pos++] = i; } for (int j = 0 ; j < pos; ++j) { if (i*prim[j] > N) break ; ok[i*prim[j]] = 1 ; if (i%prim[j] == 0 ) break ; } } for (int i = 0 ; i < pos; ++i) { if (prim[i+1 ] == prim[i]+2 ){ a.push_back(prim[i]); } } } int qmod (int a,int b) { int ans = 1 ; while (b){ if (b&1 ) ans = ans*a%mod; a = a*a%mod; b>>=1 ; } return ans; } signed main () { init(); int t; cin >>t; while (t--){ int n; cin >>n; int sum = (upper_bound(a.begin (),a.end (),n-2 ) - a.begin ()) * 2 ; int ans = sum % mod * qmod(n*(n-1 ) % mod,mod-2 ) % mod; cout <<ans<<endl ; } return 0 ; }
F 数字金字塔
考试成功做出来了的简单题
规律很好找,主要是大数麻烦,当时用java的BigInteger做出来的,,,
思路巧妙地进行化简以及dp的思路
思路
最后这步的化简是根据分数的取模得到的
i层的和 = i-1层的和 + i*(2*i-1)
exp 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 #include <bits/stdc++.h> #define int long long using namespace std ;const int N = 1e6 +10 ;const int mod = 998244353 ;int a[N];signed main () { int t; cin >>t; for (int i = 1 ; i <= 1e6 ; ++i) { a[i] = (a[i-1 ] + i*(2 *i-1 ))%mod; } while (t--){ int n; cin >>n; cout <<a[n]<<endl ; } return 0 ; }
G 小凡做蛋糕