2021牛客寒假基础训练营4题解

A、九峰与签到题

题意:
给定 n n n道题,以及所有的 m m m条提交记录,一道题是签到题当且仅当在任意一个时间段内其通过率都不小于 50 % 50\% 50%,输出所有的签到题。当一道题没有提交记录时,一定不是签到题。
数据范围: 1 ≤ n ≤ 20 , 1 ≤ m ≤ 1 0 5 1\leq n\leq 20, 1\leq m\leq 10^5 1n20,1m105

题解:
暴力统计即可,注意当存在一个时刻一道题的通过率低于 50 % 50\% 50%时,其就不可为签到题。

代码:

#include<bits/stdc++.h>
using namespace std;

#define sz(x) (int)x.size()

typedef long long ll;

const int N = 30, M = 1e5 + 10;
int all[N];
int cnt[N];
int n, m;
char op[100];
int num;
int ans[N], g;

struct Node {
	int p, v;	
};

vector<Node> a[N];

void solve() {
	scanf("%d%d", &m, &n);
	for(int i = 1; i <= m; ++i) {
		scanf("%d%s", &num, op);
		a[num].push_back({i, *op == 'A'});
	}
	
	for(int i = 1; i <= n; ++i) {
		if(sz(a[i]) == 0) continue;
		
		int sum = 0, f = 1;
		for(int j = 0; j < sz(a[i]); ++j) {
			sum += a[i][j].v;
			if(sum * 2 < (j + 1)) {
				f = 0;
				break;
			}
		}
		if(f) ans[++g] = i;
	}
	
	if(g == 0) puts("-1");
	else for(int i = 1; i <= g; ++i) printf("%d%c", ans[i], " \n"[i == g]);
}

int main()
{
	int T = 1;
	//scanf("%d", &T);
	for(int i = 1; i <= T; ++i) solve();

	return 0;
}

B、武辰延的字符串

题意:
给定串 s s s t t t,找出满足 s s s的一个前缀 s i s_i si和另一个前缀 s j s_j sj,有 s i + s j = t i + j s_i+s_j=t_{i+j} si+sj=ti+j
其中 i i i可以等于 j j j,前缀 ( s i , s j ) (s_i,s_j) (si,sj) ( s i ′ , s j ′ ) (s_{i'},s_{j'}) (si,sj)中, i ≠ i ′ , j ≠ j ′ i\neq i',j\neq j' i=i,j=j,输出满足条件的前缀对数。
数据范围: 1 ≤ ∣ s ∣ , ∣ t ∣ ≤ 1 0 5 1\leq |s|,|t|\leq 10^5 1s,t105

题解:
解法一:
考虑由于需要满足一个前缀 s i = s_i= si=前缀 t i t_i ti,所以考虑二分前缀 s j s_j sj t [ i + 1 , i + j ] t_{[i+1,i+j]} t[i+1,i+j],枚举最长的 j j j,那么前缀 s i s_i si可以构成的方案数就是 j j j

解法二:
exkmp待补

代码:
解法一:

#include<bits/stdc++.h>
using namespace std;

typedef unsigned long long ull;
typedef long long ll;

const int N = 4e5 + 10;
char s[N], t[N];
int n, m;
const int P = 131;
ull spre[N], tpre[N], p[N];

bool check(ull A[], int l, int r, ull B[], int L, int R) {
	int len = r - l + 1;
	return A[r] - A[l - 1] * p[len] == B[R] - B[L - 1] * p[len];
}
int main()
{
	scanf("%s", s + 1);	n = strlen(s + 1);
	scanf("%s", t + 1);	m = strlen(t + 1);
	int k = max(n, m);
	p[0] = 1;
	for(int i = 1; i <= k; ++i) p[i] = p[i - 1] * P;
	for(int i = 1; i <= n; ++i) spre[i] = spre[i - 1] * P + s[i];
	for(int i = 1; i <= m; ++i) tpre[i] = tpre[i - 1] * P + t[i];
	
	ll res = 0;
	for(int i = 1; i <= min(n, m); ++i) {
		if(s[i] != t[i]) break;
		int l = 0, r = m - i;
		while(l < r) {
			int len = l + r + 1 >> 1;
			if(check(spre, 1, len, tpre, i + 1, i + len)) l = len;
			else r = len - 1;
		}
		res += r;
	}
	printf("%lld\n", res);
	return 0;
}

C、九峰与CFOP

大模拟直接跳过,这辈子也不会补的。

D、温澈滢的狗狗

题意:
给定 n n n只狗以及每只狗的颜色 c i c_i ci,如果编号为 i i i的狗和编号为 j j j的狗的颜色不同,那么他们有亲密关系,且亲密度为: ∣ i − j ∣ |i-j| ij
把所有的亲密关系按照第一关键字为亲密度大小,第二关键字为编号较小的狗狗,第三关键字为编号较大的狗狗,均按照升序排序,问第 k k k对亲密关系是哪两只狗狗,输出两只狗狗的编号,小的在前,大的在后。
数据范围: 1 ≤ n ≤ 1 0 5 , 1 ≤ k ≤ n × ( n − 1 ) 2 , 1 ≤ c i ≤ n 1\leq n\leq 10^5, 1\leq k\leq \frac{n\times(n-1)}{2},1\leq c_i\leq n 1n105,1k2n×(n1),1cin

题解:
考虑二分第 k k k对亲密关系的亲密度 x x x
那么亲密度为 x x x的亲密关系=任意编号差绝对值不大于 x x x的狗狗关系减去所有同色之间编号差绝对值不大于 x x x的狗狗关系。
这样再可以最多 O ( n ) O(n) O(n)枚举到第 k k k对关系即可。

代码:

#include<bits/stdc++.h>
using namespace std;

#define sz(x) (int)x.size()
typedef long long ll;
const int N = 1e5 + 10;
int c[N], n;
ll k;
vector<int> g[N];

ll count(int mid) {
	ll sum = 0;
	for(int i = 1; i <= n; ++i) sum += min(mid, n - i);
	for(int i = 1; i <= n; ++i) 
		if(sz(g[i]))
			for(int y = 0, x = 0; y < sz(g[i]); ++y) {
				while(g[i][y] - g[i][x] > mid) ++x;
				sum -= (y - x);
			}
	return sum;
}

int main()
{
	scanf("%d%lld", &n, &k);
	for(int i = 1; i <= n; ++i)
		scanf("%d", &c[i]), g[c[i]].emplace_back(i);
		
	int l = 1, r = n;
	while(l < r) {
		int mid = l + r >> 1;
		if(count(mid) >= k) r = mid;
		else l = mid + 1;
	}
	if(l == n) puts("-1");
	else {
		k -= count(l - 1);
		for(int i = 1; i + l <= n; ++i)
			if(c[i] != c[i + l]) 
				if(--k == 0) {
					printf("%d %d\n", i, i + l);
					break;
				}
	}
	return 0;
}
E、九峰与子序列

题意:
给定一个字符串 s s s s s s n n n个字符串序列,第 i i i个为 t i t_i ti,问有多少种选择使得子序列可以组成为 s s s
数据范围: 1 ≤ ∣ s ∣ ≤ 5 × 1 0 6 , ∑ i = 1 n ∣ t i ∣ ≤ 5 × 1 0 6 1\leq |s|\leq 5\times 10^6,\sum_{i=1}^n |t_i|\leq 5\times10^6 1s5×106i=1nti5×106

题解:
题目中已然提示了使用字符串hash,可我一贯没有读题~
本题用字符串hash来找当前的 t i t_i ti可以和 s s s匹配的位置。
这里可以将可以匹配的位置看做为 01 01 01背包问题中的物品装背包。
总体复杂度为 2 e 8 2e8 2e8,常数较小加上跑的很猛的评测机可以通过。

#include<bits/stdc++.h>
using namespace std;

typedef long long ll;
typedef unsigned long long ull;

const int M = 5e6 + 10;
const int P = 131;
int n; 
ull h1[M], p[M];
char s[M];
ll f[M];

ull get(int l, int r) {
	return h1[r] - h1[l - 1] * p[r - l + 1];
}

int main()
{
	scanf("%d", &n);
	scanf("%s", s + 1);
	int len = strlen(s + 1);
	
	p[0] = 1;
	for(int i = 1; i <= len; ++i) {
		h1[i] = h1[i - 1] * P + s[i];
		p[i] = p[i - 1] * P;
	}
	
	f[0] = 1;
	for(int i = 1; i <= n; ++i) {
		scanf("%s", s + 1);
		int lens = strlen(s + 1);
		ull hv = 0;
		for(int j = 1; j <= lens; ++j) hv = hv * P + s[j];
		for(int j = len; j >= lens; --j)
			if(hv == get(j - lens + 1, j)) 
				f[j] += f[j - lens];
	}
	
	printf("%lld\n", f[len]);
	
	return 0;
}

F、魏迟燕的自走棋

题意:
n n n个人, m m m件装备,第 i i i件装备的战力值为 w i w_i wi,第 i i i件装备可以给指定 k i k_i ki个人中的一个,分别为 p 1 , p 2 , . . . , p k i p_1,p_2,...,p_{k_i} p1,p2,...,pki
求所有人分到装备的战力值和的最大值。
数据范围: 1 ≤ n , m ≤ 1 0 5 , 1 ≤ w i ≤ 1 0 9 , 1 ≤ p i ≤ n , 1 ≤ k i ≤ 2 1\leq n,m\leq 10^5,1\leq w_i\leq 10^9,1\leq p_i\leq n,1\leq k_i\leq 2 1n,m105,1wi109,1pin,1ki2

题解:
考虑贪心拿取,有点类似最小生成树的生成过程。
先按装备战力值从大到小排序然后贪心拿取,用并查集来维护是否该装备还可拿取。
具体做法为:
一个装备可以被拿是看 x , y x,y x,y两个人中是否至少有一个可以拿。拿完这个装备后, x , y x,y x,y这两个人最多可以再拿一个与 x x x y y y有关的装备,否则没人可以拿。因此一个做法就是将 x x x y y y合并到一个集合中,如果两者之前至少有一个没有拿装备,那这个集合可以拿一个与之有关的装备,否则就不行。特别的,当一个装备只有一个人可以拿且这个人所在集合还可以拿时,其拿下这个装备。

#include<bits/stdc++.h>
using namespace std;

const int N = 1e5 + 10;
struct Node {
	int x, y, z;
	bool operator < (const Node &A) const {
		return z > A.z;
	} 
}a[N];
int n, m;
int p[N], f[N];

int find(int x) {
	if(x != p[x]) p[x] = find(p[x]);
	return p[x];
}


int main()
{
	scanf("%d%d", &n, &m);
	for(int i = 1; i <= n; ++i) p[i] = i, f[i] = 0;
	for(int i = 1; i <= m; ++i) {
		int cnt; scanf("%d", &cnt);
		if(cnt == 1) scanf("%d%d", &a[i].x, &a[i].z), a[i].y = a[i].x;
		else scanf("%d%d%d", &a[i].x, &a[i].y, &a[i].z);
	}
	
	sort(a + 1, a + m + 1);
	
	long long res = 0;
	for(int i = 1; i <= m; ++i) {
		int x = find(a[i].x), y = find(a[i].y), v = a[i].z;
		if(x != y && (f[x] == 0 || f[y] == 0)) {
			res += v;
			p[x] = y;
			f[y] = (f[x] || f[y]);
		}
		else if(f[x] == 0){
			res += v;
			f[x] = 1;
		}
	}
	
	printf("%lld\n", res);
	return 0;
}

G、九峰与蛇形填数

待补

H、吴楚月的表达式

题意:
给定一棵 n n n n − 1 n-1 n1条边的树,每个点有一个点权 w i w_i wi,每条边有一个操作符, 1 1 1号点为根。从根到每个点会产生一个表达式,请你按照加减乘除的优先级正确计算这个表达式的值。由于结果可能很大,所以答案对 1 e 9 + 7 1e9+7 1e9+7取模。
数据范围: 1 ≤ n ≤ 1 0 5 , 1 ≤ w i ≤ 1 0 9 , 1 ≤ f a i ≤ i , o p i ∈ { + , − , ∗ , / } 1\leq n\leq 10^5, 1\leq w_i\leq 10^9, 1\leq fa_i\leq i, op_i\in \{+,-,*,/\} 1n105,1wi109,1faii,opi{+,,,/}

题解:
为了方便处理,我们将所有的减法预处理转换为加法,除法预处理转换为乘法。
那么我们只需要去考虑加号和乘号的优先级即可。
1 1 1号点不进行计算,表达式的值即为 w 1 w_1 w1

  • 考虑当前是乘法,那么优先级已然是最高,所以只要看当前数的个数,如果两个,乘到第二个上,否则乘到第一个上即可
  • 考虑当前是加法,那么后面可能会出现乘法,所以看当前数的个数,如果一个,那么将该数放置到第二个数即可,否则先将两个数相加合并,再把当前数放置到第二个数。

最后只需要第一个数和第二个数(如果第二个数存在的话)相加即每个点的答案。

代码:

#include<bits/stdc++.h>
using namespace std;

typedef long long ll;

const int N = 1e5 + 10, M = N;
const int mod = 1e9 + 7;
int h[N], w[M], e[M], ne[M], idx;
int a[N], n;
int fa[N];
char s[N];
int res[N];

struct Ans {
	int a, b;
	Ans() {b = -1;} 
}ans[N];

void add(int a, int b, int c) {
	e[idx] = b, w[idx] = c, ne[idx] = h[a], h[a] = idx++; 
}

int qp(int a, int b) {
	int ans = 1;
	while(b) {
		if(b & 1) ans = (ll)ans * a % mod;
		a = (ll)a * a % mod;
		b >>= 1; 
	}
	return ans;
}

int id(char ch) {
	if(ch == '+') return 0;
	else if(ch == '-') return 1;
	else if(ch == '*') return 2;
	return 3;
}

int cal(int a, int b, int c) {
	int res;
	if(c == 0) res = (a + b) % mod;
	else if(c == 1) res = (ll)a * b % mod;
	return res;
}

void dfs(int u) {
	for(int i = h[u]; i != -1; i = ne[i]) { 
		int v = e[i];
		ans[v] = ans[u]; 

		//multiple  
		if(w[i] == 1) {
			if(ans[u].b == -1) ans[v].a = cal(ans[v].a, a[v], w[i]);
			else ans[v].b = cal(ans[v].b, a[v], w[i]);
		}
		//addition 
		else {
			if(ans[u].b != -1) ans[v].a = cal(ans[v].a, ans[v].b, 0);
			ans[v].b = a[v];
 		}
		dfs(v);
	} 
}

int main()
{
	scanf("%d", &n);
	for(int i = 1; i <= n; ++i) scanf("%d", &a[i]);
	for(int i = 2; i <= n; ++i) scanf("%d", &fa[i]);
	scanf("%s", s + 2);
	
	memset(h, -1, n + 1 << 2);
	for(int i = 2; i <= n; ++i) {
		int sig = id(s[i]);
		if(sig == 3) a[i] = qp(a[i], mod - 2);
		else if(sig == 1) a[i] = ((-a[i]) % mod + mod) % mod;
		add(fa[i], i, sig >> 1);
	} 
	
	ans[1].a = a[1];
	dfs(1);
	
	for(int i = 1; i <= n; ++i) {
		int c = ans[i].a;
		if(~ans[i].b) c = (c + ans[i].b) % mod;
		printf("%d%c", c, " \n"[i == n]);
	}
	return 0;
}

I、九峰与分割序列

待补

J、邬澄瑶的公约数

题意:
给定 n n n个数以及每个数的幂,问这些数的幂的最大公约数是多少。答案对 1 e 9 + 7 1e9+7 1e9+7取模。特别地, g c d ( x ) = x gcd(x)=x gcd(x)=x
数据范围: 1 ≤ n , x i , p i ≤ 10000 1\leq n,x_i,p_i\leq 10000 1n,xi,pi10000

题解:
分解每个数的质因数,最终的最大公约数中的质因数最大幂为给定 n n n个数的质因数最小幂。

代码:

#include<bits/stdc++.h>
using namespace std;

typedef long long ll;

const int N = 1e4 + 10;
const int INF = 0x3f3f3f3f;
const int mod = 1e9 + 7;
int pri[N], cnt;
bool st[N];
int n;
int act[N];

struct Node {
	int x, p;
	bool operator < (const Node &A) const {
		return x < A.x;
	}
}a[N];

void xs(int n) {
	st[0] = st[1] = true;
	for(int i = 2; i <= n; ++i) {
		if(!st[i]) pri[++cnt] = i;
		for(int j = 1; j <= cnt && i <= n / pri[j]; ++j) {
			st[i * pri[j]] = true;
			if(i % pri[j] == 0) break;
		}
	}
}

int qp(int a, int b) {
	int ans = 1;
	while(b) {
		if(b & 1) ans = (ll)ans * a % mod;
		a = (ll)a * a % mod;
		b >>= 1;
	}
	return ans;
}

int main()
{
	ll res = 1; 
	scanf("%d", &n);
	int mx = 0;
	for(int i = 1; i <= n; ++i) scanf("%d", &a[i].x), mx = max(mx, a[i].x);
	for(int i = 1; i <= n; ++i) scanf("%d", &a[i].p);
	
	xs(mx);
	sort(a + 1, a + n + 1);
	
	memset(act, 0x3f, sizeof act);
	for(int i = 1; i <= n; ++i) {
		for(int j = 1; j <= cnt; j++) {
			if(act[pri[j]] == 0) continue;
			if(a[i].x % pri[j]) act[pri[j]] = 0;
			else {
				int c = 0;
				int xx = a[i].x;
				while(xx % pri[j] == 0) ++c, xx /= pri[j];
				act[pri[j]] = min(act[pri[j]], c * a[i].p);
			}
		}
	}
	
	for(int i = 1; i <= cnt; ++i) res = res * qp(pri[i], act[pri[i]]) % mod;
	printf("%lld\n", res);
	
	return 0;
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值