• 2021牛客寒假算法基础集训营1 题解


    2021牛客寒假算法基础集训营1 题解

    比赛地址https://ac.nowcoder.com/acm/contest/9981

    官方题解https://ac.nowcoder.com/discuss/593200

    A-串

    第一种:我们用(f[i])来表示长度为(i)(us)的串的数量,对于当前位置:

    • 如果之间有完整的(us)了,那么随便填,有(26)种情况。

      (f[i] += f[i-1] * 26)

    • 如果之前没有完整的(us),但是有一个(u),那么就必须在这个位置上添加一个(s)。具体算法:(所有的情况-有us的情况-没有u的情况=有一个u的情况)

      也就是(26^{i-1} - f[i-1] - 25^{i-1})

    整合就是: (f[i] = f[i-1] * 25 + 26^{i-1} - 25^{i-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])表示长度为(i)没有(u)的串/有(u)(s)的串/有(us)的串。

    状态转移方程:

    (f[i][0] = f[i-1][0] * 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;)

    #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)个括号插入合适的位置即可。

    #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-红和蓝

    思维:特殊到一般,简单到复杂。

    叶子节点只能和它的父节点一个颜色,那么我们可以从叶子节点开始考虑,(dfs)将叶子节点和它的父亲节点化为一个颜色,过程中如果有叶子节点的父节点已经上色了,或者有个节点没有被上色,那么不满足要求直接输出-1。

    接着再跑一遍(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的个数)

    接着考虑将0变为1的操作会改变什么?

    可能会引起某个连通块变大,或者是增加连通块,或者将多个连通块合并为一块。

    代码实现上,如果这个位置本来就是1,那么无需计算直接输出;若为0,可以先计算连通块增加后的总方案数,接着遍历4个方向,有等于1并且不在一个连通块里的,就计算方案数并合并。

    计算公式为:(原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)

    我们很容易可以发现相邻的两数都互为素数,相邻的两个奇数也都互为素数。两个偶数的最大公约数都大于1,所以我们可以考虑将相邻的偶数放在一起。这样当(n/2 > k), 是一定能够找到满足的排列。

    (n/2 == k), 我们可以发现(6)(3)的最大公约数大于(1), 那么只需要将(6)放在最后一个偶数位上后跟(3)即可满足有(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)
    3 : (3^1 3^2 3^3 3^4)
    ​ 5 : (5^1 5^2 5^3 5^4)

    观察到:没有被划掉的数是具有两种或者两种以上的类型不同的质因子, 比如:(6 12)

    那么最后的结果可以表示为 (p_1^{n1}p_2^{n2}p_3^{n3}p_4^{n4}p_5^{n5}...)

    对于两个这样的数:(p_1^{n1}p_2^{n2}p_3^{n3}p_4^{n4}p_5^{n5}...)(p_1^{m1}p_2^{m2}p_3^{m3}p_4^{m4}p_5^{m5}...)求lcm为(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)}...),

    那么问题转化为对于每一个质因子(p), 我们求它的最高次数即可。有两种情况:

    • (p = 2)时,(2)的最高次数(x)一定为(2^x*3<=n),那么(x = floor(log(n/3) / log(2)))

    • (p != 2)时,(p)的最高次数(x)一定为(p^x*2<=n),那么(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;
    }
    
  • 相关阅读:
    解析python数据后用html输出
    python 自动化测试HTTP接口
    python 自动化对比返回结果
    Java 基础知识 练习
    Java 菜鸟学习之 script脚本语句
    java语言的认识
    Script 语言的简单练习题 乘法口诀
    Script 简单语句的练习题
    Java菜鸟培训第二天
    36个号码随机摇奖编码
  • 原文地址:https://www.cnblogs.com/fans-fan/p/14507526.html
Copyright © 2020-2023  润新知