2021牛客寒假算法基础集训营1 题解
比赛地址:https://ac.nowcoder.com/acm/contest/9981
官方题解:https://ac.nowcoder.com/discuss/593200
A-串
第一种:我们用 f [ i ] f[i] f[i]来表示长度为 i i i有 u s us us的串的数量,对于当前位置:
-
如果之间有完整的 u s us us了,那么随便填,有 26 26 26种情况。
f [ i ] + = f [ i − 1 ] ∗ 26 f[i] += f[i-1] * 26 f[i]+=f[i−1]∗26
-
如果之前没有完整的 u s us us,但是有一个 u u u,那么就必须在这个位置上添加一个 s s s。具体算法: 所 有 的 情 况 − 有 u s 的 情 况 − 没 有 u 的 情 况 = 有 一 个 u 的 情 况 所有的情况-有us的情况-没有u的情况=有一个u的情况 所有的情况−有us的情况−没有u的情况=有一个u的情况
也就是 2 6 i − 1 − f [ i − 1 ] − 2 5 i − 1 26^{i-1} - f[i-1] - 25^{i-1} 26i−1−f[i−1]−25i−1
整合就是: f [ i ] = f [ i − 1 ] ∗ 25 + 2 6 i − 1 − 2 5 i − 1 f[i] = f[i-1] * 25 + 26^{i-1} - 25^{i-1} f[i]=f[i−1]∗25+26i−1−25i−1
#include <bits/stdc++.h>
using namespace std;
#define ll long long
#define IOS ios_base::sync_with_stdio(0); cin.tie(0);cout.tie(0);
const int maxn = 1e6 + 10;
const ll mod = 1e9 + 7;
ll f[maxn];
ll qsm(ll a, ll b){
ll res = 1;
while(b){
if(b & 1) res = (res * a) % mod;
a = (a * a) % mod;
b >>= 1;
}
return res % mod;
}
void solve(){
int n; cin >> n;
f[0] = 0; f[1] = 0;
for(int i = 2; i <= n; i++){
f[i] = ((qsm(26, i-1)-qsm(25, i-1) + mod) % mod + f[i-1] * 25 % mod) % mod;
}
ll ans = 0;
for(int i = 0; i <= n; i++) ans = (ans + f[i]) % mod;
cout << ans << endl;
}
int main(){
IOS; int t = 1;
while(t--){
solve();
}
return 0;
}
第二种:动态规划
我们用 f [ i ] [ 0 / 1 / 2 ] f[i][0/1/2] f[i][0/1/2]表示长度为 i i i没有 u u u的串/有 u u u没 s s s的串/有 u s us us的串。
状态转移方程:
f [ i ] [ 0 ] = f [ i − 1 ] [ 0 ] ∗ 25 ; f[i][0] = f[i-1][0] * 25; f[i][0]=f[i−1][0]∗25;
f [ i ] [ 1 ] = f [ i − 1 ] [ 0 ] + f [ i − 1 ] [ 1 ] ∗ 25 ; f[i][1] = f[i-1][0] + f[i-1][1] * 25; f[i][1]=f[i−1][0]+f[i−1][1]∗25;
f [ i ] [ 2 ] = f [ i − 1 ] [ 1 ] + f [ i − 1 ] [ 2 ] ∗ 26 ; f[i][2] = f[i-1][1] + f[i-1][2] * 26; f[i][2]=f[i−1][1]+f[i−1][2]∗26;
#include <bits/stdc++.h>
using namespace std;
#define ll long long
#define IOS ios_base::sync_with_stdio(0); cin.tie(0);cout.tie(0);
const int maxn = 1e6 + 10;
const ll mod = 1e9 + 7;
ll f[maxn];
ll qsm(ll a, ll b){
ll res = 1;
while(b){
if(b & 1) res = (res * a) % mod;
a = (a * a) % mod;
b >>= 1;
}
return res % mod;
}
/*
void solve(){
int n; cin >> n;
f[0] = 0; f[1] = 0;
for(int i = 2; i <= n; i++){
f[i] = ((qsm(26, i-1)-qsm(25, i-1) + mod) % mod + f[i-1] * 25 % mod) % mod;
}
ll ans = 0;
for(int i = 0; i <= n; i++) ans = (ans + f[i]) % mod;
cout << ans << endl;
}
*/
ll a[maxn][3];
void solve(){
int n; cin >> n;
a[0][0] = a[0][1] = a[0][2] = 0;
a[1][0] = 25; a[1][1] = 1; a[1][2] = 0;
for(int i = 2; i <= n; i++){
a[i][0] = a[i-1][0] * 25 % mod;
a[i][1] = (a[i-1][0] + a[i-1][1] * 25 % mod) % mod;
a[i][2] = (a[i-1][1] + a[i-1][2] * 26 % mod) % mod;
}
ll ans = 0;
for(int i = 1; i <= n; i++) ans = (ans + a[i][2]) % mod;
cout << ans << endl;
}
int main(){
IOS; int t = 1;
while(t--){
solve();
}
return 0;
}
B-括号
可以这样去构造:
找到个数最少的满足 x ∗ y + z x*y+z x∗y+z的括号数,那么构造左括号为 x x x个,右括号为 y y y个,剩余 z z z个括号插入合适的位置即可。
#include <bits/stdc++.h>
using namespace std;
#define ll long long
#define IOS ios_base::sync_with_stdio(0); cin.tie(0);cout.tie(0);
const int maxn = 2e5 + 10;
const ll mod = 1e9 + 7;
void solve(){
ll k; cin >> k;
if(k == 0){
cout << "(" << endl;
return;
}
ll a = 1e9, b = 1e9, p = 1e9;
for(ll i = 1; i * i <= k; i++){
ll x = i, y = k / i, z = k - (i * (k / i));
// cout << x << " " << y << " " << z << endl;
if(x + y + z < a + b + p){
a = x, b = y, p = z;
}
}
string ans = "(";
for(int i = 0; i < p; i++) ans += ")";
for(int i = 0; i < a - 1; i++) ans += "(";
for(int i = 0; i < b; i++) ans += ")";
cout << ans << endl;
}
int main(){
IOS; int t = 1;
while(t--){
solve();
}
return 0;
}
C-红和蓝
思维:特殊到一般,简单到复杂。
叶子节点只能和它的父节点一个颜色,那么我们可以从叶子节点开始考虑, d f s dfs dfs将叶子节点和它的父亲节点化为一个颜色,过程中如果有叶子节点的父节点已经上色了,或者有个节点没有被上色,那么不满足要求直接输出-1。
接着再跑一遍 d f s dfs dfs染色。
#include <bits/stdc++.h>
using namespace std;
#define ll long long
#define IOS ios_base::sync_with_stdio(0); cin.tie(0);cout.tie(0);
const int maxn = 3e5 + 10;
const ll mod = 1e9 + 7;
int n, cnt = 0;
vector<int> G[maxn];
int col[maxn], vis[maxn];
bool flag = false;
void dfs(int u, int fa){
int count = 0;
for(auto v : G[u]){
if(v == fa) continue;
count ++;
dfs(v, u);
}
if(count == 0 || !vis[u]){
if(vis[fa]) {flag = true; return;}
vis[u] = vis[fa] = ++cnt;
}
}
void dfs2(int u, int fa){
for(auto v : G[u]){
if(v == fa) continue;
if(vis[v] == vis[u]) col[v] = col[u];
else col[v] = col[u] ^ 1;
dfs2(v, u);
}
}
void solve(){
cin >> n;
int x, y;
for(int i = 1; i < n; i++){
cin >> x >> y;
G[x].push_back(y);
G[y].push_back(x);
}
memset(vis, 0, sizeof vis);
dfs(1, 0);
if(flag || vis[0] != 0) {
cout << -1 << endl;
return;
}
//for(int i = 1; i <= n; i++) cout << vis[i] << " ";
//cout << endl;
col[1] = 1;
dfs2(1, 0);
for(int i = 1; i <= n; i++) cout << (col[i] == 1 ? 'R' : 'B');
cout << endl;
}
int main(){
IOS; int t = 1;
while(t--){
solve();
}
return 0;
}
D-点一成零
考虑并查集。
首先先考虑没有改变的情况总方案数是怎么算出来的: 总 方 案 数 = 连 通 块 的 个 数 ∗ 每 个 联 通 块 中 1 的 个 数 总方案数=连通块的个数*每个联通块中1的个数 总方案数=连通块的个数∗每个联通块中1的个数
接着考虑将0变为1的操作会改变什么?
可能会引起某个连通块变大,或者是增加连通块,或者将多个连通块合并为一块。
代码实现上,如果这个位置本来就是1,那么无需计算直接输出;若为0,可以先计算连通块增加后的总方案数,接着遍历4个方向,有等于1并且不在一个连通块里的,就计算方案数并合并。
计算公式为: 原 a n s / ( 修 改 后 的 c n t ∗ 两 个 连 通 块 的 个 数 ) ∗ 两 个 连 通 块 个 数 总 和 原ans / (修改后的cnt * 两个连通块的个数) * 两个连通块个数总和 原ans/(修改后的cnt∗两个连通块的个数)∗两个连通块个数总和
注意
- 并查集中二维结构要转换成一维的。
- 最后计算中除一个很大的数可以转换成乘它的逆元。
#include <bits/stdc++.h>
using namespace std;
#define ll long long
#define IOS ios_base::sync_with_stdio(0); cin.tie(0);cout.tie(0);
const int maxn = 5e5 + 10;
const ll mod = 1e9 + 7;
int n;
int f[maxn], sz[maxn];
int a[550][550];
string s[550];
int d[4][2] = {1, 0, -1, 0, 0, -1, 0, 1};
ll qsm(ll a, ll b){
ll res = 1;
while(b){
if(b & 1) res = res * a % mod;
a = a * a % mod;
b >>= 1;
}
return res % mod;
}
void init(){
for(int i = 0; i <= n * n; i++) f[i] = i, sz[i] = 1;
}
int Find(int x){
if(x == f[x]) return x;
return f[x] = Find(f[x]);
}
void Union(int x, int y){
x = Find(x), y = Find(y);
if(x == y) return;
f[x] = y;
sz[y] += sz[x];
}
void solve(){
cin >> n;
init();
for(int i = 0; i < n; i++){
cin >> s[i];
for(int j = 0; j < n; j++){
char c = s[i][j];
if(c == '0') continue;
if(j > 0 && s[i][j-1] == '1') Union(i * n + j, i * n + j - 1);
if(i > 0 && s[i-1][j] == '1') Union(i * n + j, (i-1) * n + j);
if(i < n-1 && s[i+1][j] == '1') Union(i * n + j, (i+1) * n + j);
if(j < n-1 && s[i][j+1] == '1') Union(i * n + j, i * n + j + 1);
}
}
int cnt = 0;
ll ans = 1;
for(int i = 0; i < n; i++){
for(int j = 0; j < n; j++){
if(f[i * n + j] == i * n + j && s[i][j] == '1') cnt++, ans = ans * sz[i *n + j] % mod;
}
}
for(int i = 1; i <= cnt; i++) ans = ans * i % mod;
int k; cin >> k;
for(int i = 0; i < k; i++){
int x, y; cin >> x >> y;
if(s[x][y] == '1'){
cout << ans << endl;
continue;
}
s[x][y] = '1';
cnt ++;
ans = ans * cnt % mod;
for(int i = 0; i < 4; i++){
int dx = x + d[i][0], dy = y + d[i][1];
if(dx < 0 || dy < 0 || dx >= n || dy >= n) continue;
if(s[dx][dy] == '1'){
int f1 = Find(dx * n + dy), f2 = Find(x * n + y);
if(f1 != f2){
ans = ans * qsm(cnt, mod - 2) % mod;
ans = ans * qsm(sz[f1], mod - 2) % mod;
ans = ans * qsm(sz[f2], mod - 2) % mod;
ans = ans * (sz[f1] + sz[f2]) % mod;
cnt--;
Union(f1, f2);
}
}
}
cout << ans << endl;
}
}
int main(){
IOS; int t = 1;
while(t--){
solve();
}
return 0;
}
I-限制不互素对的排列
构造、数学
注意题目中的条件
k
<
=
n
/
2
k <= n/2
k<=n/2
我们很容易可以发现相邻的两数都互为素数,相邻的两个奇数也都互为素数。两个偶数的最大公约数都大于1,所以我们可以考虑将相邻的偶数放在一起。这样当 n / 2 > k n/2 > k n/2>k, 是一定能够找到满足的排列。
当 n / 2 = = k n/2 == k n/2==k, 我们可以发现 6 6 6 和 3 3 3的最大公约数大于 1 1 1, 那么只需要将 6 6 6放在最后一个偶数位上后跟 3 3 3即可满足有 n / 2 n/2 n/2对gcd大于1的数对。
#include <bits/stdc++.h>
using namespace std;
#define ll long long
#define IOS ios_base::sync_with_stdio(0); cin.tie(0);cout.tie(0);
const int maxn = 2e5 + 10;
const ll mod = 1e9 + 7;
void solve(){
int n, k; cin >> n >> k;
if(n < 6 && n / 2 == k){ cout << -1 << endl; return;}
if(n /2 > k){
int cnt = -1;
for(int i = 2; i <= n; i += 2){
cout << i << " ";
cnt++;
if(cnt == k) break;
}
for(int i = 1; i <= n; i ++){
if((i & 1) || i > (cnt + 1) * 2) cout << i << " ";
}
}
else{
for(int i = 2; i <= n; i += 2) if(i != 6) cout << i << " ";
cout << "6 3 1 ";
for(int i = 5; i <= n; i += 2) cout << i << " ";
}
}
int main(){
IOS; int t = 1;
while(t--){
solve();
}
return 0;
}
J-一群小青蛙呱蹦呱蹦呱
数论
举例可以发现:
被划掉的数:
2 :
2
1
2
2
2
3
2
4
2^1 2^2 2^3 2^4
21222324
3 :
3
1
3
2
3
3
3
4
3^1 3^2 3^3 3^4
31323334
5 :
5
1
5
2
5
3
5
4
5^1 5^2 5^3 5^4
51525354
观察到:没有被划掉的数是具有两种或者两种以上的类型不同的质因子, 比如: 6 12 6 \ 12 6 12 。
那么最后的结果可以表示为 p 1 n 1 p 2 n 2 p 3 n 3 p 4 n 4 p 5 n 5 . . . p_1^{n1}p_2^{n2}p_3^{n3}p_4^{n4}p_5^{n5}... p1n1p2n2p3n3p4n4p5n5...
对于两个这样的数: p 1 n 1 p 2 n 2 p 3 n 3 p 4 n 4 p 5 n 5 . . . p_1^{n1}p_2^{n2}p_3^{n3}p_4^{n4}p_5^{n5}... p1n1p2n2p3n3p4n4p5n5...和 p 1 m 1 p 2 m 2 p 3 m 3 p 4 m 4 p 5 m 5 . . . p_1^{m1}p_2^{m2}p_3^{m3}p_4^{m4}p_5^{m5}... p1m1p2m2p3m3p4m4p5m5...求lcm为 p 1 m a x ( m 1 , n 1 ) p 2 m a x ( m 2 , n 2 ) p 3 m a x ( m 3 , n 3 ) p 4 m a x ( m 4 , n 4 ) p 5 m a x ( m 5 , n 5 ) . . . p_1^{max(m1, n1)}p_2^{max(m2, n2)}p_3^{max(m3, n3)}p_4^{max(m4, n4)}p_5^{max(m5, n5)}... p1max(m1,n1)p2max(m2,n2)p3max(m3,n3)p4max(m4,n4)p5max(m5,n5)...,
那么问题转化为对于每一个质因子 p p p, 我们求它的最高次数即可。有两种情况:
-
p = 2 p = 2 p=2时, 2 2 2的最高次数 x x x一定为 2 x ∗ 3 < = n 2^x*3<=n 2x∗3<=n,那么 x = f l o o r ( l o g ( n / 3 ) / l o g ( 2 ) ) x = floor(log(n/3) / log(2)) x=floor(log(n/3)/log(2))
-
p ! = 2 p\ !=\ 2 p != 2时, p p p的最高次数 x x x一定为 p x ∗ 2 < = n p^x*2<=n px∗2<=n,那么 x = f l o o r ( l o g ( n / 2 ) / l o g ( p ) ) x = floor(log(n/2) / log(p)) x=floor(log(n/2)/log(p))
最后求积即可。
素数筛不可以用埃氏筛,会TLE,线性筛即可。
#include <bits/stdc++.h>
using namespace std;
#define ll long long
#define IOS ios_base::sync_with_stdio(0); cin.tie(0);cout.tie(0);
const int maxn = 8e7 + 10;
const ll mod = 1e9 + 7;
ll p[maxn];
bool v[maxn];
int n;
ll qsm(ll a, ll b){
ll res = 1;
while(b){
if(b & 1) res = (res * a) % mod;
a = (a * a) % mod;
b >>= 1;
}
return res % mod;
}
ll calc(int i){
if(i == 2) return qsm(2, log(n / 3) / log(2));
return qsm(i, log(n / 2) / log(i));
}
void solve(){
cin >> n;
memset(v, false, sizeof v);
int cnt = 0;
ll ans = 1;
for(int i = 2; i <= n / 2; i++){
if(!v[i]){
p[cnt++] = i;
ans = (ans * calc(i)) % mod;
}
for(int j = 0; j < cnt && i * p[j] <= n / 2; j++){
v[i * p[j]] = 1;
if(i % p[j] == 0) break;
}
}
if(ans == 1) cout << "empty" << endl;
else cout << ans << endl;
}
int main(){
IOS; int t = 1;
while(t--){
solve();
}
return 0;
}