Codeforces Round #747 (Div. 2) A - F

https://codeforces.com/contest/1594

A

我们显然可以构造出一个 [ − n + 1 , n ] [-n+1,n] [n+1,n]的区间和满足条件

#include <bits/stdc++.h>

using namespace std;

int main(){
    ios::sync_with_stdio(false);
    cin.tie(0);
    int t;
    cin >> t;
    while(t--){
        long long n;
        cin >> n;
        cout << -n + 1 << ' ' << n << '\n';
    }
    return 0;
}

B

此题是NOIP原题
https://ac.nowcoder.com/acm/problem/16668

  • 这个问题是让你找 n 0 , n 1 , n 2 . . . n^0,n^1,n^2... n0,n1,n2...的第 k k k小是谁,我们可以手推一下,第一小显然是 n 0 n^0 n0,第二小是 n 1 n^1 n1,第三小是 n 0 + n 1 n^0+n^1 n0+n1,第四小是 n 2 n^2 n2,以此类推,如果对二进制非常敏感的话能够发现每个数都是一个二进制数,且二进制数正好是按照次序来的,也就是第一小是 ( 1 ) 2 (1)_2 (1)2,第二小是 ( 10 ) 2 (10)_2 (10)2,第三小是 ( 11 ) 2 (11)_2 (11)2,那么第 k k k小就是 k k k所对应的二进制数,这样我们枚举 k k k所对应的二进制数的每一位,累加结果即可
  • 这道题很长时间之前第一次做的时候我确实没看出这一层,但是现在看到这个题就很简单了
#include <bits/stdc++.h>

using namespace std;

typedef long long ll;
const ll MOD = 1e9 + 7;
ll fastpow(ll base, ll power){
	ll ans = 1;
	while(power){
		if(power & 1) ans = ans * base % MOD;
		base = base * base % MOD;
		power >>= 1;
	}
	return ans;
}
int main(){
	ios::sync_with_stdio(false);
	cin.tie(0);
	int t;
	long long n, k;
	cin >> t;
	while(t--){
		cin >> n >> k;
		ll ans = 0;
		ll m = 0;
		while(k){
			if(k & 1){
				ans = (ans + fastpow(n, m)) % MOD;
			}
			m += 1;
			k >>= 1;
		}
		cout << ans << '\n';
	}
	return 0;
}

C

我们的目的是让这一个字符串里面的所有字符都变成目标字符,操作方式是这样的,选择一个数 x ∈ [ 1 , n ] x\in[1,n] x[1,n],对于字符串的每一个位置,如果位置下标不能被 x x x,整除,那么就用目标字符替代这个位置,现在问最小的操作数

  • 显然如果我们选择 n n n,那么除了 n n n以外的其他位置都会变成目标字符,之后我们再选择 n − 1 n-1 n1即可,所以最多操作次数就是 2 2 2;操作数为 0 0 0的情况是显然的;那么现在问题的关键就是操作数为 1 1 1时候该怎么办
  • 如果有一个数不是任何非目标字符的下标的因子,那么操作次数就是1,换句话说,如果一个因子能够达到的所有的下标都是目标字符,那么操作次数就是1, x x x就是这个因子,我们可以使用一个 O ( n n ) O(n\sqrt n) O(nn )的类埃式筛法来解决这个问题
#include <bits/stdc++.h>

using namespace std;

int main(){
	ios::sync_with_stdio(false);
	cin.tie(0);
	int t, m;
	string s;
	char c;
	cin >> t;
	while(t--){
		cin >> m >> c;
		cin >> s;
		int nm = 0;
		for(int i=0;i<m;i++){
			if(s[i] != c) nm += 1;
		}
		if(nm == 0){
			cout << 0 << '\n';
			continue;
		}
		bool found = false;
		for(int d=1;d<=m;d++){
			int cc = d;
			for(int i=d;i<=m;i+=d){
				if(s[i - 1] != c){
					cc = -1;
					break;
				}
			}
			if(cc != -1){
				found = true;
				cout << 1 << '\n';
				cout << cc << '\n';
				break;
			}
		}
		if(!found){
			cout << 2 << '\n';
			cout << m - 1 << ' ' << m << '\n';
		}
	}
	return 0;
}

D

imposter:骗子
crewmate:船员

给出了若干组关系,表示为 u u u v v v是骗子或者船员,现在问透过这些关系,最多有多少骗子,如果存在矛盾关系那么输出 − 1 -1 1

  • 这道题的 i d e a idea idea我们也许能够发现,如果 u u u v v v是骗子,那么 u u u v v v肯定属于不同的集合;如果 u u u v v v是船员,他们肯定属于同一个集合,这样我们就能够将这些人分成若干组,这是并查集的思想
  • 我们在上面分的组只是若干个划分,并没有偏见,所以接下来先让 u u u节点是骗子或者船员,在 D f s Dfs Dfs的过程中观察是否出现矛盾,也就是根据 u u u推导出的 v v v所在的组和它现在所在的组不一致
  • 如果没有矛盾,取较大的数累加到答案中,因为这两种情况是互补的
  • 从程序实现的角度,我们可以利用异或的性质使得程序变得简单,也就是如果两个人是同一组那么集合权值为 0 0 0,否则为 1 1 1
#include <bits/stdc++.h>

using namespace std;

int main(){
	ios::sync_with_stdio(false);
	cin.tie(0);
	int t, n, m, u, v;
	string s;
	cin >> t;
	while(t--){
		cin >> n >> m;
		vector<vector<pair<int, int> > > g(n);
		for(int i=0;i<m;i++){
			cin >> u >> v >> s;
			u -= 1;
			v -= 1;
			if(s[0] == 'i'){
				g[u].push_back(make_pair(v, 1));
				g[v].push_back(make_pair(u, 1));
			}else{
				g[u].push_back(make_pair(v, 0));
				g[v].push_back(make_pair(u, 0));
			}
		}
		vector<int> used(n);
		vector<int> now(n);
		vector<int> cnt(2);
		bool flag = true;
		int ans = 0;
		function<void(int)> Dfs = [&](int u){
			used[u] = 1;
			cnt[now[u]] += 1;
			for(auto i : g[u]){
				int v = i.first;
				if(!used[v]){
					now[v] = (now[u] ^ i.second);
					Dfs(v);
				}else{
					if(now[v] != (now[u] ^ i.second)){
						flag = false;
					}
				}
			}
		};
		for(int i=0;i<n;i++){
			if(!used[i]){
				cnt[0] = cnt[1] = 0;
				now[i] = 0;
				Dfs(i);
				ans += max(cnt[0], cnt[1]);
			}
		}
		cout << (flag ? ans : -1) << '\n';
	}
	return 0;
}

E1

问这颗二叉树可能的涂色方案

  • 根据简单排列组合的知识,显然根节点有六种方案,剩下的所有节点都有四种方案,如果二叉树层数为 k k k,那么答案为 6 × 4 2 k − 2 6\times4^{2^k-2} 6×42k2
#include <bits/stdc++.h>

using namespace std;

typedef long long ll;
const ll MOD = 1e9 + 7;
ll fastpow(ll base, ll power){
	ll ans = 1;
	while(power){
		if(power & 1) ans = ans * base % MOD;
		base = base * base % MOD;
		power >>= 1;
	}
	return ans;
}
int main(){
	ios::sync_with_stdio(false);
	cin.tie(0);
	ll k;
	cin >> k;
	cout << 6ll * fastpow(4, pow(2, k) - 2) % MOD;
	return 0;
}

E2

先pass

于2021.11.26更新

此题相对于简单版本增加了一些限制,给定了某些节点的颜色,问现在能够构造的满二叉树有多少种涂色方案

  • 我们把这些节点分成几类来考虑,首先考虑有多少节点有四种涂色方案,假设节点总数为 v e r s vers vers,现在已经确定颜色的节点个数为 n n n,因为是一颗满二叉树,所以其余的节点都至少有两种涂色方案,也就是当前统计答案为 a n s = 2 v e r s − n ans=2^{vers-n} ans=2versn;再考虑一下,因为每个节点基本不会影响之后的节点,唯一起着比较大的限制就是之前的节点,因为需要考虑左右孩子涂色的问题,所以我们把涂色的节点的所有祖先都标记上,存到一个map里面,那么现在需要统计的答案就是 2 n − m p . s i z e ( ) 2^{n-mp.size()} 2nmp.size(),也就是有哪些处于孩子节点位置上的节点不会受到他之后的节点的影响
  • 接下来需要处理的就是那些受到限制的节点了,考虑搜索,我们从根节点开始搜索,设一个数组 r e t [ 3 ] ret[3] ret[3]表示三类涂色方案得到的当前节点涂色方案的数量,那么这个方案数量应该来自它的两个孩子节点,也就是 2 u , 2 u + 1 2u,2u+1 2u,2u+1,这是这个节点没有预先涂色的情况;如果已经涂好了颜色,那么就只有一种涂色方案,也就是只保留一个 r e t ret ret
#include <bits/stdc++.h>

using namespace std;

typedef long long ll;
const ll MOD = 1e9 + 7;
ll fastpow(ll base, ll power, ll mod){
  ll ans = 1;
  while(power){
    if(power & 1) ans = ans * base % mod;
    base = base * base % mod;
    power >>= 1;
  }
  return ans;
}
int main(){
  ios::sync_with_stdio(false);
  cin.tie(0);
  ll k, n;
  cin >> k >> n;
  ll vers = (1ll << k) - 1;
  ll ans = fastpow(2ll, vers - n, MOD);
  map<ll, int> mp;
  for(int i=0;i<n;i++){
    int c;
    ll v;
    string s;
    cin >> v >> s;
    if(s[0] == 'w' || s[0] == 'y') c = 0;
    else if(s[0] == 'g' || s[0] == 'b') c = 1;
    else c = 2;
    mp[v] = c;
    while(v > 1){
      v >>= 1;
      if(mp.find(v) == mp.end()) mp[v] = -1;
    }
  }
  ans = ans * fastpow(2ll, vers - (ll)mp.size(), MOD) % MOD;
  function<vector<ll>(ll)> Dfs = [&](ll u){
    vector<ll> ret(3, 1);
    for(auto v : {2 * u, 2 * u + 1}){
      if(mp.find(v) != mp.end()){
        auto got = Dfs(v);
        ret[0] *= (got[1] + got[2]) % MOD;
        ret[1] *= (got[0] + got[2]) % MOD;
        ret[2] *= (got[0] + got[1]) % MOD;
        ret[0] %= MOD;
        ret[1] %= MOD;
        ret[2] %= MOD;
      }
    }
    if(mp[u] != -1){
      for(int i=0;i<3;i++){
        if(mp[u] != i) ret[i] = 0;
      }
    }
    return ret;
  };
  auto ret = Dfs(1);
  ans = ans * ((ret[0] + ret[1] + ret[2]) % MOD) % MOD;
  cout << ans;
  return 0;
}

F

s s s只动物和 n n n个围栏,现在说,如果你能够把这些动物放在这些围栏里面,且没有空的围栏,并且至少有一个连续段里面至少有 k k k只动物,那么这个农场就是 l u c k y lucky lucky农场;同时,如果任何的没有空围栏的分配方式都能让这个农场成为 l u c k y lucky lucky农场,那么这个农场就是一个 i d e a l ideal ideal农场

  • 现在给出农场的 s , n , k s,n,k s,n,k,问这个农场是不是 l u c k y lucky lucky农场
  • 构造题,首先,如果 s < n s<n s<n显然我们随意构造都是非法;如果 s = n s=n s=n,那么随意构造都是合法;如果 s > n s>n s>n,考虑一种最坏情况的非法构造如下 1 , 1 , 1 , 1... ⏟ k − 1 个 1 , k + 1 , 1 , 1 , 1 , 1 , . . . ⏟ k − 1 个 1 , k + 1 , 1 , 1... \underbrace{1,1,1,1...}_{k-1个1},k+1,\underbrace{1,1,1,1,...}_{k-1个1},k+1,1,1... k11 1,1,1,1...,k+1,k11 1,1,1,1,...,k+1,1,1...
  • 我们先给每一个围栏里面放上一个动物,这需要 n n n只动物,剩下 s − n s-n sn只动物,然后 k k k的倍数的位置需要多放上 k k k个,需要动物的数量就是 ⌊ s k ⌋ × k \lfloor\frac{s}{k}\rfloor\times k ks×k,所以构造出这种非法情况需要的条件是 s − n ≥ ⌊ s k ⌋ × k s-n\geq \lfloor\frac{s}{k}\rfloor\times k snks×k,这个时候输出NO
#include <bits/stdc++.h>

using namespace std;

typedef long long ll;
int main(){
	ios::sync_with_stdio(false);
	cin.tie(0);
	int t;
	cin >> t;
	while(t--){
		long long s, n, k;
		cin >> s >> n >> k;
		if(s == k) cout << "YES\n";
		else if(s < k) cout << "NO\n";
		else if(s - n < n / k * k) cout << "YES\n";
		else cout << "NO\n";
	}
	return 0;
}
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

Clarence Liu

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值