【比赛】2021牛客寒假算法基础集训营2 题解

题目提交处


点击此处查看题目

duang~

A 牛牛与牛妹的RMQ


这个题目看题解看了好久才看懂,听说已经达到了区域银牌的水平,那我肯定不会放过这个题目的, hhh~
给定一个排列数组a,选定k 个下标,每次可以选取两个进行RMQ运算,问所取到值的概率。

首先,我们分母可以确定下来,两个数可以取一样的、有先后顺序,所以就有 k 2 k^2 k2中结果。

接下来去处理分子的情况:
分子是RMQ的结果,由区间去确定RMQ的结果太被动了, 所以换一种思路,用结果去确定区间。
通过RMQ的性质我们可以发现:RMQ(a, b) 取的是区间(a, b)的最大值, 也就是整个区间的极大值,所以先要预处理每一 个极大值的作用域,要确定它的作用域的话,我们就可以想到单调栈算法可以得出来,(如果单调栈不知道的话, 可以去看看官方题解的讲解,我是把我看官方题解没看懂的地方和大家分享发一下。) 还有一个预处理就是建造线段树,去维护区间中的最大值。

分析每一组值的情况:
先将我们可以取的下标排序idx[], i d x 1 、 i d x 2 、 i d x 3 … … i d x n idx_1、idx_2、idx_3……idx_n idx1idx2idx3idxn, 会生成答案的情况有 :
①RMQ值为 a [ i d x i ] a[idx_i] a[idxi]的个数。(单调栈预处理作用)
R M Q ( i d x i − 1 , i d x i ) RMQ(idx_{i -1}, idx_i) RMQ(idxi1,idxi)的值,因为可能会出现可选下标代表值之外的的值。(线段树预处理作用)

那么如何去确定它出现了多少次呢?
pos代表当前的坐标位置, L p o s L_{pos} Lpos代表最左边小于 a [ p o s ] a[pos] a[pos]的坐标, R p o s R_{pos} Rpos代表最右边小于 a [ p o s ] a[pos] a[pos]的坐标, [ L p o s , p o s ] [L_{pos}, pos] [Lpos,pos]中取一个数 , [ p o s , R p o s ] [pos, R_{pos}] [pos,Rpos] 中取一个数。区间中取到的数必须是可选下标,所以要用树状数组记录一下区间值的情况(比前缀和快一些)。所以结果就是 g e t s u m ( L [ p o s ] , p o s ) ∗ g e t s u m ( p o s , R [ p o s ] ) ∗ 2 getsum(L[pos], pos) * getsum(pos, R[pos]) * 2 getsum(L[pos],pos)getsum(pos,R[pos])2 因为中间取了两次,所以要减掉一次,如果pos这个下标不是可用下标就不用减了。

最后约一下分,结果就出来了。

AC 代码


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

#define _for(i, a, b) for (int i = (a); i < (b); ++i)
#define _rep(i, a, b) for (int i = (a); i <= (b); ++i)
#define For(i, a, b) for (int i = (a); i >= (b); --i)
typedef long long ll;
typedef vector<int> vi;
typedef pair<int, int> pii;

const int N = 100000 + 10, INF = 0x3f3f3f3f;
int n, m, k, tt;
pii stk[N];
int L[N], R[N], a[N], c[N];
vi alls;
map<int, ll> ansMap;
bool vis[N];

struct Node
{
	int l, r, id, v;

	Node(int l = 0, int r = 0, int id = 0, int v = 0) : l (l), r(r), id(id), v(v) {}
	Node operator + (const Node& t) const {
		int pos, val;
		v >= t.v ? (pos = id, val = v) : (pos = t.id, val = t.v);
		return { l, t.r, pos, val };
	}
}tr[N << 2];

void push(int u)
{
	tr[u] = tr[u << 1] + tr[u << 1 | 1];
}

void build(int u, int l, int r)
{
	if (l == r) tr[u] = { l,  r, l, a[l] };
	else
	{
		int mid = l + r >> 1;
		build(u << 1, l, mid), build(u << 1 | 1, mid + 1, r);
		push(u);
	}
}

Node query(int u, int l, int r)
{
	if (tr[u].l == l && tr[u].r == r) return tr[u];
	Node& t = tr[u];
	int mid = t.l + t.r >> 1;
	if (r <= mid) return query(u << 1, l, r);
	if (l > mid) return query(u << 1 | 1, l, r);
	return query(u << 1, l, mid) + query(u << 1 | 1, mid + 1, r);
}

void add(int x, int k)
{
	for (; x <= n; x += x & -x) c[x] += k;
}

ll ask(int x)
{
	ll ans = 0;
	for (; x; x -= x & -x) ans += c[x];
	return ans;
}

ll get_sum(int l, int r)
{
	return ask(r) - ask(l - 1);
}

void cal(int pos)
{
	if (ansMap.count(a[pos])) return;
	ansMap[a[pos]] = get_sum(L[pos], pos) * get_sum(pos, R[pos]) * 2 - vis[pos];
}

ll gcd(ll a, ll b)
{
	return b ? gcd(b, a % b) : a;
}

int main()
{
#ifdef LOCAL
	freopen("data.in", "r", stdin);
#endif

	ios::sync_with_stdio(false);
	cin.tie(0), cout.tie(0);

	cin >> n;
	_rep(i, 1, n) cin >> a[i];
	build(1, 1, n);
	a[0] = INF, a[n + 1] = INF - 1;
	
	_rep(i, 0, n + 1)
	{
		while (tt && stk[tt].first <= a[i])
		{
			L[stk[tt].second] = stk[tt - 1].second + 1;
			R[stk[tt].second] = i - 1;
			--tt;
		}
		stk[++tt] = make_pair(a[i], i);
	}

	cin >> m;
	while (m--)
	{
		alls.clear(), ansMap.clear();
		
		cin >> k;
		_rep(i, 1, k)
		{
			int x;
			cin >> x;
			alls.push_back(x);
			add(x, 1);
			vis[x] = true;
		}
		sort(alls.begin(), alls.end());
		int sz = alls.size();
		_for(i, 0, sz)
		{
			cal(alls[i]);
			if (i) cal(query(1, alls[i - 1], alls[i]).id);
		}

		_for (i, 0, sz)
		{
			add(alls[i], -1);
			vis[alls[i]] = false;
		}
		
		for (const auto& p : ansMap)
		{
			ll son = p.second, mom = 1ll * k * k;
			ll d = gcd(son, mom);
			cout << p.first << " " << son / d << "/" << mom / d << endl;
		}
	}
	return 0;
}

C 牛牛与字符串border


找出最小循环节,然后根据字母的众数去构造字符串即可。

AC代码


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

#define _for(i, a, b) for (int i = (a); i < (b); ++i)
#define _rep(i, a, b) for (int i = (a); i <= (b); ++i)
#define For(i, a, b) for (int i = (a); i >= (b); --i)
#define mod(x) (x) % 1000000007
#define debug(a) cout << #a << " = " << a << endl;
typedef long long ll;
typedef pair<int, int> pii;
typedef vector<int> vi;

const int N = 100000 + 10;
char s[N];

int gcd(int a, int b)
{
	return b ? gcd(b, a % b) : a;
}

int main()
{
#ifdef LOCAL
	freopen("data.in", "r", stdin);
#endif
	ios::sync_with_stdio(false);
	cout.tie(0);
	cin.tie(0);

	int T;
	cin >> T;
	while (T--)
	{
		int n, l, d = 0;
		cin >> n >> l >> s;
		if (2 * l > n && l != n) d = n - l;
		else d = gcd(n, l);

		string ans;
		_for(i, 0, d)
		{
			int cnt[26] = { 0 };
			for (int j = i; j < n; j += d) cnt[s[j] - 'a']++;
			int mx = 0, alp = 0;
			_for(j, 0, 26) if (cnt[j] > mx) mx = cnt[j], alp = j;
			ans.push_back('a' + alp);
		}

		_for(i, 0, n / d) cout << ans;
		_for(i, 0, n % d) cout << ans[i];
		cout << endl;
	}
	
	return 0;
}

D 牛牛与整除分块


打表找规律题,不要小看题目给你的任意一个条件,说不定就是突破口。
比如集合S的大小总是严格小于 2 N 2 \sqrt N 2N , 就要去尝试一下,尝试之后你会发现前 ⌊ N ⌋ \lfloor \sqrt N \rfloor N 个数的整除之后的结果是不一样的,后面 n − ⌊ N ⌋ n - \lfloor \sqrt N \rfloor nN 乘出来的结果有些会有重复结果的出现,但是结果都是连续的。

所以综上所述:
a n s = { x x ≤ ⌊ N ⌋ 2 ∗ a − n / x + ( a ! = n / a ) ) x > ⌊ N ⌋ ( a = ⌊ N ⌋ ) ans = \begin{cases} x & x \leq \lfloor \sqrt N \rfloor\\ 2 * a - n / x + (a != n / a))& x > \lfloor \sqrt N \rfloor \end{cases} (a = \lfloor \sqrt N \rfloor) ans={x2an/x+(a!=n/a))xN x>N (a=N )

⌊ N / x ⌋ \lfloor N/x \rfloor N/x得出来的结果就是它的倒数位置。
需要注意的是当 a ! = n / a a != n / a a!=n/a时,有 2 ∗ a + 1 2 * a + 1 2a+1种结果。样例有提醒。

AC代码


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

#define _for(i, a, b) for (int i = (a); i < (b); ++i)
#define _rep(i, a, b) for (int i = (a); i <= (b); ++i)
#define For(i, a, b) for (int i = (a); i >= (b); --i)
#define mod(x) (x) % MOD
#define ENDL "\n"
#define debug(a) cout << #a << " = " << a << endl;
typedef long long ll;
typedef pair<int, int> pii;
typedef vector<int> vi;

int main()
{
    #ifdef LOCAL
    freopen("data.in", "r", stdin);
    #endif
    ios::sync_with_stdio(false);
    cout.tie(0);
    cin.tie(0);

    int T;
    cin >> T;
    while (T--)
    {
        int n, x;
        cin >> n >> x;

        int a = sqrt(n + 0.5);
        if (x <= a) cout << x << ENDL;
        else cout << (2 * a - n / x + (a != n / a)) << ENDL;
    }

    return 0;
}

F 牛牛与交换排序


首先可以确定k的值,例如:5 2 1 4 3, 因为1肯定是第一个回到起始点的,所以k就确定了为3。
然后根据k依次检查即可得到答案,使用暴力翻转是会超时的,每次只翻转k个数,对k个数进行翻转操作可以想到使用懒标记的双向队列,虽然STL中有,但是STL会慢,所以是使用数组模拟双向队列的写法。

AC代码


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

#define _for(i, a, b) for (int i = (a); i < (b); ++i)
#define _rep(i, a, b) for (int i = (a); i <= (b); ++i)
#define For(i, a, b) for (int i = (a); i >= (b); --i)
#define mod(x) (x) % 1000000007
#define debug(a) cout << #a << " = " << a << endl;
typedef long long ll;
typedef pair<int, int> pii;
typedef vector<int> vi;

const int N = 100000 + 10;
int n;
int a[N], pos[N];

struct deQue
{
	int dq[N << 1], hh = N, tt = N - 1;
	bool rev;
	
	bool empty()
	{
		return hh > tt;
	}

	int size()
	{
		return tt - hh + 1;
	}

	int front()
	{
		return rev ? dq[tt] : dq[hh];
	}

	int back()
	{
		return rev ? dq[hh] : dq[tt];
	}

	void push_front(int x)
	{
		rev ? dq[++tt] = x : dq[--hh] = x;
	}

	void push_back(int x)
	{
		rev ? dq[--hh] = x : dq[++tt] = x;
	}

	void pop_front()
	{
		rev ? --tt : ++hh;
	}

	void pop_back()
	{
		rev ? ++hh : --tt;
	}

	void reverse()
	{
		rev ^= 1;
	}
	
}q;

int main()
{
#ifdef LOCAL
	freopen("data.in", "r", stdin);
#endif
	ios::sync_with_stdio(false);
	cout.tie(0);
	cin.tie(0);

	cin >> n;
	int s = 0;
	_rep(i, 1, n)
	{
		cin >> a[i];
		pos[a[i]] = i;
		if (!s && a[i] != i) s = i;
	}

	if (!s) return puts("yes\n1"), 0;

	int k = pos[s] - s + 1;
	_rep(i, 1, k) q.push_back(a[i]);

	_rep(i, 1, n)
	{
		if (q.front() != i && q.size() == k) q.reverse();
		if (q.front() != i) return puts("no"), 0;

		q.pop_front();
		if (i + k <= n) q.push_back(a[i + k]);
	}
	printf("yes\n%d\n", k);
	return 0;
}

G 牛牛与比赛颁奖


这个题目模拟的话会超时,因为n的数据点有 1 e 9 1e9 1e9这么大。
所以的话,需要想到的目标就是从题目数入手。

我做这个题目的时候思考方向不对,我的想法是只要确定了每个点被覆盖了几次就行,这是不对的,应该哪一段被覆盖了相同的次数,具体是哪些点不重要,我们要的只是区间长度,然后然每一次的覆盖次数的数组加上区间长度(不要局限于一维,想出多维之间的联系)。

具体处理就是使用差分的思想去维护覆盖次数。

AC代码


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

#define _for(i, a, b) for (int i = (a); i < (b); ++i)
#define _rep(i, a, b) for (int i = (a); i <= (b); ++i)
#define For(i, a, b) for (int i = (a); i >= (b); --i)
#define mod(x) (x) % MOD
#define ENDL "\n"
#define debug(a) cout << #a << " = " << a << endl;
typedef long long ll;
typedef pair<int, int> pii;
typedef vector<int> vi;

const int N = 100000 + 10;
int n, m, all[N];
pii seg[N << 1];

int main()
{
#ifdef LOCAL
	freopen("data.in", "r", stdin);
#endif
	ios::sync_with_stdio(false);
	cout.tie(0);
	cin.tie(0);
	
	cin >> n >> m;
	int Au = (n + 9) / 10, Ag = (n + 3) / 4, Cu = (n + 1) / 2;
	int sz = 0;
	_for (i, 0, m)
	{
		int l, r;
		cin >> l >> r;
		seg[sz++] = make_pair(l, 1);
		seg[sz++] = make_pair(r + 1, -1);
	}

	sort(seg, seg + sz);
	int cnt = 0;
	_for(i, 0, sz)
	{
		cnt += seg[i].second;
		if (i + 1 < sz) all[cnt] += seg[i + 1].first - seg[i].first;
	}

	int g, s, c, sum = 0;
	For(i, m, 1)
	{
		if (sum < Au) g = i;
		if (sum < Ag) s = i;
		if (sum < Cu) c = i;
		sum += all[i];
		all[i] += all[i + 1];
	}

	cout << all[g] << " " << all[s] - all[g] << " " << all[c] - all[s] << ENDL;
	
	return 0;
}

H 牛牛与棋盘


签到题

AC代码


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

#define _for(i, a, b) for (int i = (a); i < (b); ++i)
#define _rep(i, a, b) for (int i = (a); i <= (b); ++i)
#define For(i, a, b) for (int i = (a); i >= (b); --i)
#define debug(a) cout << #a << " = " << a << endl;
typedef long long ll;
typedef pair<int, int> pii;
typedef vector<int> vi;

const int N = 100000 + 10;

int main()
{
#ifdef LOCAL
	freopen("data.in", "r", stdin);
#endif
	ios::sync_with_stdio(false);
	cout.tie(0);
	cin.tie(0);

	int n;
	cin >> n;
	int x = 1;
	_for(i, 0, n)
	{
		_for(j, 0, n) cout << (x = !x);
		x = !x;
		cout << endl;
	}
	
	return 0;
}

I 牛牛的“质因数”


数字都是按照顺序去排列组合的,所以只要取大于等于前一个数的质数构造一个 F ( i ) F(i) F(i),再判断加上这个质数之后是否小于等于n就可以了。
要注意:

  • 末尾加质数的时候要注意他的位数。
  • 乘结果要不断取模,防止溢出

代码其实很简单,就是线性筛质数+dfs。

AC代码


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

#define _for(i, a, b) for (int i = (a); i < (b); ++i)
#define _rep(i, a, b) for (int i = (a); i <= (b); ++i)
#define For(i, a, b) for (int i = (a); i >= (b); --i)
#define mod(x) (x) % 1000000007
#define debug(a) cout << #a << " = " << a << endl;
typedef long long ll;
typedef pair<int, int> pii;
typedef vector<int> vi;

const int N = 4000000 + 10;
ll ans = 0;
int primes[N], cnt, n;
bool st[N];

void get_primes(int n)
{
	for (int i = 2; i <= n; ++i)
	{
		if (!st[i]) primes[cnt++] = i;
		for (int j = 0; i * primes[j] <= n; ++j)
		{
			st[i * primes[j]] = true;
			if (i % primes[j] == 0) break;
		}
	}
}

inline ll len(ll x)
{
	ll res = 1;
	while (x)
	{
		x /= 10;
		res *= 10;
	}
	return res;
}

void dfs(int cur, ll s, ll sum)
{
	if (sum <= n) ans = mod(ans + s);
    
	_for(i, cur, cnt)
	{
		if (sum * primes[i] > n) return;
		dfs(i, mod(mod(s * len(primes[i])) + primes[i]), sum * primes[i]);
	}
}

int main()
{
#ifdef LOCAL
	freopen("data.in", "r", stdin);
#endif
	ios::sync_with_stdio(false);
	cout.tie(0);
	cin.tie(0);

	cin >> n;
	get_primes(n);
	_for(i, 0, cnt) dfs(i, primes[i] * 1ll, primes[i] * 1ll);
	cout << ans << endl;	
	return 0;
}

J 牛牛想要成为hacker


这题也算是里面比较简单的题目了,当时没有仔细去做这个题,后面补题的时候发现好简单,这算是比赛策略的问题了,如何选择一个题目进行深究也是一个问题,所以选择一个合适排行榜目标很重要(解决办法:查看自己学校的或者自己省份的学校进行比较)。

这个题目想让这份代码TLE,那么最简单的就是构造出一组任意三个无法组成三角形的数据,所以可以想到斐波拉契,但是会超过 10 e 9 10e9 10e9,所以超过的就都用1代替。

AC 代码


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

#define _for(i, a, b) for (int i = (a); i < (b); ++i)
#define _rep(i, a, b) for (int i = (a); i <= (b); ++i)
#define For(i, a, b) for (int i = (a); i >= (b); --i)
typedef long long ll;
typedef vector<int> vi;
typedef pair<int, int> pii;

const int N = 40 + 10, INF = 0x3f3f3f3f;
int n, a[N];

int main()
{
#ifdef LOCAL
	freopen("data.in", "r", stdin);
#endif

	ios::sync_with_stdio(false);
	cin.tie(0), cout.tie(0);

	cin >> n;
	a[0] = 1, a[1] = 2;
	_rep(i, 2, 40) a[i] = a[i - 1] + a[i - 2];
	_rep(i, 1, n)
	{
		if (i <= 40) cout << a[i] << " ";
		else cout << 1 << " ";
	}
	cout << endl;
	return 0;
}

总结


  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值