2021牛客暑期多校訓練營1

比賽地址c++

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\)既能夠做爲某個數的倍數,也能夠做爲一個基數,對於第一種狀況,預處理好範圍內的全部數的因數便可spa

#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(計算幾何)

題目連接
code

題目:
如給定圖,求出圓心距離底部的距離排序

解析:
平面幾何題目,構造一個類似三角形便可遊戲

#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\) 的子矩陣個數get

解析:
統計每行長度超過\(m\)的連續 \(0\) 的個數的和it

#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]);
}
相關文章
相關標籤/搜索