ICode9

精准搜索请尝试: 精确搜索
首页 > 其他分享> 文章详细

2021牛客暑期多校训练营1

2021-08-11 23:00:53  阅读:147  来源: 互联网

标签:int scanf ++ 多校 牛客 maxn 2021 fail dp


比赛地址

A(博弈+思维)

题目链接
⭐⭐⭐

题目:
Alice与Bob玩游戏,有两个石子堆石子个数分别为\(n,m\)。Alice先走,每人可以从一堆中拿走\(k(k>0)\)个,并且从另一堆拿走\(s\times k(s\ge0)\)个,不能执行操作的人为负。

解析:
引理:每一堆确定数量的石子都与唯一确定数量的另一堆石子构成必败点
证明:假定有两个必败点\((a,b_1)(a,b_2)\ b_1<b_2\),则存在\((a,b_1)\rightarrow(a,b_2)\),则一定有一个点不是必败点,故反证成立
因此可以维护一个\(fail\)数组代表必败点组合,每次可以从之前的状态\(fail[j]\)进行转移,而\(i-j\)既可以作为某个数的倍数,也可以作为一个基数,对于第一种情况,预处理好范围内的所有数的因数即可

#include<bits/stdc++.h>

using namespace std;

const int maxn = 5e3 + 1;

vector<int> fac[maxn];
int fail[maxn];
bool vis[maxn];

int main() {
	for (int i = 1; i < maxn; ++i)
		for (int j = i; j < maxn; j += i)
			fac[j].push_back(i);
	fail[0] = 0;
	for (int i = 1; i < maxn; ++i) {
		if (fail[i]) continue;
		memset(vis, 0, sizeof(vis));
		for (int j = 0; j < i; ++j) {
			if (!fail[j] && j) continue;
			for (int k = fail[j]; k < maxn; k += i - j)
				vis[k] = true;
			for (auto& i : fac[i - j]) {
				if (fail[j] + i >= maxn) break;
				vis[fail[j] + i] = true;
			}
		}
		for (int j = i + 1; j < maxn; ++j)
			if (!vis[j]) {
				fail[i] = j;
				fail[j] = i;
				break;
			}
	}
	int T;
	scanf("%d", &T);
	int a, b;
	while (T--) {
		scanf("%d%d", &a, &b);
		printf("%s\n", fail[a] == b ? "Bob" : "Alice");
	}
}

B(计算几何)

题目链接

题目:
如给定图,求出圆心距离底部的距离

解析:
平面几何题目,构造一个相似三角形即可

#include<bits/stdc++.h>
 
using namespace std;
 
double r, a, b, h;
 
int main() {
    scanf("%lf%lf%lf%lf", &r, &a, &b, &h);
    if (2 * r < b) printf("Drop");
    else {
        printf("Stuck\n");
        printf("%.10f", (sqrt(((a - b) / 2) * ((a - b) / 2) + h * h) * r / h - b / 2) * h / ((a - b) / 2));
    }
}

D(水题)

题目链接

题目:
给出一个\(n\times n\)的01矩阵,找出\(1\times m\)值均为 \(0\) 的子矩阵个数

解析:
统计每行长度超过\(m\)的连续 \(0\) 的个数的和

#include<bits/stdc++.h>
using namespace std;
 
char t[2005][2005];
 
int main() {
    int n, m;
    scanf("%d%d", &n, &m);
    for (int i = 0; i < n; ++i) {
        scanf("%s", t[i]);
    }
    scanf("%*s");
    int ans = 0, cnt;
    for (int i = 0; i < n; ++i) {
        int j = 0;
        while (j < n) {
            cnt = 0;
            while (t[i][j] == '0' && j < n) ++j, ++cnt;
            if (t[i][j] == '1') ++j;
            ans += max(cnt - m + 1, 0);
        }
    }
    printf("%d", ans);
}

F(思维)

题目链接

题目:
如果一个数,如果对应字符串的含有连续子串所对应的数是\(3\)的倍数,则认为这个数是\(3-friendly\)的数,现在给定区间\([L, R]\),求出区间内\(3-friendly\)的数的个数

解析:
定义\(f(x)\)代表小于等于\(x\)中\(3-friendly\)的数的个数,那么答案即为\(f(R)-f(L-1)\)。
对于任意大于三位的数而言,组成他们的数位模3后只有\(0,1,2\)三种可能,则必定含有子串满足\(3-friendly\)(根据三的倍数必定满足各位相加之和仍然位三的倍数)
那么只需要对小于等于100的数进行特判处理即可

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

bool check(int x) {
	int t[3] = { 0 };
	while (x) {
		++t[x % 10 % 3];
		x /= 10;
	}
	if (t[1] || t[2]) return (t[1] > 0 && t[2] > 0) || t[0] > 0;
	return true;
}

int t[100];

int main() {
	long long L, R;
	int T;
	for (int i = 1; i < 100; ++i)
		t[i] = t[i - 1] + check(i);
	scanf("%d", &T);
	while (T--) {
		scanf("%lld%lld", &L, &R);
		--L;
		if (L < 100) L = t[L];
		else L = L - 99 + t[99];
		if (R < 100) R = t[R];
		else R = R - 99 + t[99];
		printf("%lld\n", R - L);
	}
}

G(贪心+思维+绝对值)

题目链接
⭐⭐⭐⭐

题目:
给出长度为\(N\)的\(A,B\)数组,现在可以任意更换\(A\)数组中元素的位置,求\(\sum_{i=1}^n|A_i-B_i|\)的最大值

解析:
可以把绝对值拆开,这样就变成了\(\sum_{i=1}^n (\pm A_i)+(\pm B_i)\),即A中正号出现次数要等与B中负号出现次数,反之亦然。假设可以交换任意次,只要挑选\(A,B\)中最大前\(n\)个加正号
那么现在如果只能交换\(k\)次,考虑最大利益的贪心交换。不难发现对于两队值\((a_1,b_1)(a_2,b_2)\),如果是\(\max(a_1,b_1)<\min(a_2,b_2)\),即两个线段没有交集,此时的贡献是\(|b_2-a_2|+|b_1-a_1|\),倘若交换\(a_1,a_2\)位置,则此时贡献为\(|b_2-a_1|+|a_2-b_1|\),(画出线段图很容易发现)相比于前者多了\(2(\max(a_1,b_1)-\min(a_2,b_2))\)
那么就可以找出每一对的最大值和最小值,并将他们的最大值升序排列,最小值降序排序,找出最大的前\(k\)对,对应的操作自然也是将最大值与最小值对应的原元素进行交换

#include<bits/stdc++.h>
 
using namespace std;
typedef long long ll;
const int maxn = 5e5 + 5;
ll a[maxn], b[maxn];
ll mn[maxn], mx[maxn];
 
int main() {
    int n, k;
    scanf("%d%d", &n, &k);
    for (int i = 0; i < n; ++i)
        scanf("%lld", a + i);
    for (int i = 0; i < n; ++i)
        scanf("%lld", b + i);
 
    ll ans = 0;
    if (n == 2) {
        if (k & 1) swap(a[0], a[1]);
        ans += abs(a[0] - b[0]) + abs(a[1] - b[1]);
    }
    else {
        k = min(k, n);
        for (int i = 0; i < n; ++i)
            ans += abs(a[i] - b[i]), mx[i] = max(a[i], b[i]), mn[i] = min(a[i], b[i]);
        sort(mx, mx + n), sort(mn, mn + n, greater<ll>());
        for (int i = 0; i < k && mn[i] - mx[i]>0; ++i)
            ans += 2 * (mn[i] - mx[i]);
    }
    printf("%lld", ans);
}

I(dp+前缀和优化)

题目链接
⭐⭐⭐⭐

题目:
Alice和Bob玩游戏,每个人可以轮流从P数组中选取元素(P数组为\(1\sim N\)的排列),但必须要满足以下规则

  1. 每个人取的数都必须大于之前二人取过的所有数
  2. 每个人取的数的下标都得大于之前自己取过的所有下标

如果有多种选择,则概率均匀分布,有一方无法进行操作时游戏结束
询问二人总操作数的期望(答案模取\(998244353\))

解析:
首先暴力状态转移,一定是一个\(O(n^3)\)的做法,所以考虑用前缀和进行优化降维
考虑设置\(dp[i][j]\),代表Alice取下标为\(i\)的元素时,Bob取下表为\(j\)元素时的期望,那么对于当前这个状态,需要判断\(P[i]\)与\(P[j]\)的大小关系,谁大证明谁刚操作过,当前轮到另一方操作,那么可以得到下列状态转移方程

\[dp[i][j]=1+\frac{sum[i/j]}{cnt[i/j]} \]

\(sum,cnt\)分别代表当前状态可以达到状态的\(dp\)前缀和,以及状态的数量,由于是概率均匀分布所以可以采用前缀和进行优化,他们也不难利用倒序迭代去维护

注意:
除了最后一次\((0,0)\)的状态,其他\(i=j\)的状态均不合法。

#include <bits/stdc++.h>

constexpr int maxn = 5e3 + 5;
constexpr int mod = 998244353;
int p[maxn];
int inv[maxn], cnt[maxn], dp[maxn][maxn], sum[maxn];

int main() {
    int n;
    scanf("%d", &n);
    for (int i = 1; i <= n; ++i)
        scanf("%d", &p[i]);
    inv[1] = 1;
    for (int i = 2; i <= n; ++i) 
        inv[i] = 1ll * (mod - mod / i) * inv[mod % i] % mod;
    for (int i = n; i >= 0; --i) {
        int cnt2 = 0, sum2 = 0;
        for (int j = n; j >= 0; --j) {
            if (i != 0 && i == j) continue;
            if (p[i] > p[j]) {
                if (!cnt2) dp[i][j] = 0;
                else dp[i][j] = (1 + 1ll * inv[cnt2] * sum2) % mod;
                cnt[j] += 1;
                (sum[j] += dp[i][j]) %= mod;
            }
            else {
                if (!cnt[j]) dp[i][j] = 0;
                else dp[i][j] = (1 + 1ll * inv[cnt[j]] * sum[j]) % mod;
                cnt2 += 1;
                (sum2 += dp[i][j]) %= mod;
            }
        }
    }
    printf("%d", dp[0][0]);
}

标签:int,scanf,++,多校,牛客,maxn,2021,fail,dp
来源: https://www.cnblogs.com/DreamW1ngs/p/15130647.html

本站声明: 1. iCode9 技术分享网(下文简称本站)提供的所有内容,仅供技术学习、探讨和分享;
2. 关于本站的所有留言、评论、转载及引用,纯属内容发起人的个人观点,与本站观点和立场无关;
3. 关于本站的所有言论和文字,纯属内容发起人的个人观点,与本站观点和立场无关;
4. 本站文章均是网友提供,不完全保证技术分享内容的完整性、准确性、时效性、风险性和版权归属;如您发现该文章侵犯了您的权益,可联系我们第一时间进行删除;
5. 本站为非盈利性的个人网站,所有内容不会用来进行牟利,也不会利用任何形式的广告来间接获益,纯粹是为了广大技术爱好者提供技术内容和技术思想的分享性交流网站。

专注分享技术,共同学习,共同进步。侵权联系[81616952@qq.com]

Copyright (C)ICode9.com, All Rights Reserved.

ICode9版权所有