2020百度之星程序設計大賽復賽


A. Battle for Wosneth (Hdu 6838)

題目大意

初始\(Alice\)有無限血,\(Bob\)\(m\)滴血。\(Alice\)\(p\%\)命中\(Bob\),並使\(Bob\)減少一滴血,自身回復一滴血。\(Bob\)\(q\%\)概率命中\(Alice\),並使\(Alice\)減少一滴血,但自身血不變。問當\(Bob\)血量減少為\(0\)時,\(Alice\)的期望血量變化值是多少。結果對\(998244353\)取模。

解題思路

\(Bob\)血量大於\(1\)時,設\(Alice\)命中一次\(Bob\),自身血量變化的期望值為\(x\),則(此處\(p,q\)為小數)

\[x = p \times (1 - q) + ( 1 - p ) \times ( - q + x ) \]

解得

\[x = 1 - \frac{q}{p} \]

所以Bob從\(m\)滴血扣到\(1\)滴血時,Alice的血量變化期望值為

\[(m-1)\times x = (m - 1) ( 1 - \frac{q}{p}) \]

\(Bob\)剩下一滴血時,由於如果\(Alice\)命中他,則Bob不會反擊,這是與上方的區別所在,設\(Alice\)命中Bob,自身血量變化值為\(y\),則

\[y = p \times 1 + (1 - p) \times (-q + y) \]

解得

\[y = 1 - \frac{q}{p} + q \]

所以最終答案

\[ans = (m-1) \times x + y = (1 - \frac{q}{p}) \times m + q \]

這可以理解為先假設\(m\)輪,\(Bob\)都會反擊,造成變化期望值為\((1 - \dfrac{q}{p}) \times m\),再減去最后一次\(Bob\)實際反擊的變化(扣血)期望\(-\dfrac{p}{1 - (1 - p)} \times q = -q\)

即為

\[(1 - \frac{q}{p}) \times m - (-q) = (1 - \frac{q}{p}) \times m + q \]

神奇的代碼
#include <bits/stdc++.h>
using namespace std;
typedef long long LL;

const LL mo = 998244353;

LL qpower(LL a, LL b)
{
    LL qwq = 1;
    while (b)
    {
        if (b & 1)
            qwq = qwq * a % mo;
        b >>= 1;
        a = a * a % mo;
    }
    return qwq;
}

LL inv(LL x)
{
    return qpower(x, mo - 2);
}

int main(void)
{
    ios::sync_with_stdio(false);
    cin.tie(0);
    cout.tie(0);
    int kase;
    cin >> kase;
    for (int ii = 1; ii <= kase; ii++)
    {
        LL m, q, p;
        cin >> m >> p >> q;
        p = p * inv(100) % mo;
        q = q * inv(100) % mo;
        LL ans = ((1 - q * inv(p) % mo + mo) % mo * m % mo + q) % mo;
        cout << ans << endl;
    }
    return 0;
}


B. Binary Addition (Hdu 6839)

題目大意

給你一串無限長的\(01\)\(S、T\),其中第\(n+1\)位及以后都是\(0\)。現你有兩種操作作用於\(S\)串:

  • 將某一位與\(1\)異或
  • 將其視為一個數,對它加一。其中最低位在最左邊

求最小的操作次數,使得\(S\)串變成\(T\)串。

解題思路

這種看似麻煩的題要去想想特別之處

可以證明猜測操作二要執行則僅可能執行一次

操作二有什么用?

如果第一位是\(0\),操作二與操作一沒區別。

如果第一位是\(1\),操作二就能夠將前面一連串的\(1\)變成\(0\),在這之后的\(0\)變成\(1\)

如果我們會執行兩次操作二,由於執行了第一次操作二,前面的數變成了\(0\),我們要重新變成\(1\),才能再執行操作二。而這最終的結果也只是把某一位變成\(1\),而這一結局采用操作一可以一步到位。

所以我們就得到了個重要性質:操作二只能執行一次或者不執行

所以,我們就枚舉操作二的執行效果,即枚舉\(i\),把前\(i\)個數變成\(1\),並把第\(i+1\)個數變成\(0\),然后執行一次操作二,剩下的全部執行操作一即可。

\(num_0[i]\)表示\(S\)串的前\(i\)個數中\(0\)的個數,\(num_1[i]\)表示\(T\)串的前\(i\)個數中\(1\)的個數,\(cnt[i]\)表示\(S\)\(T\)串的\([i..n]\)中不同的數的個數。

則此時的次數就為\(num_0[i] + (s[i+1] == 1) + 1 + num_1[i] + (t[i+1] == 0) + cnt[i+2]\)

對所有\(i\)以及\(cnt[1]\)取最小值即是答案。

神奇的代碼
#include <bits/stdc++.h>
using namespace std;
typedef long long LL;

template <typename T>
void read(T &x)
{
    int s = 0, c = getchar();
    x = 0;
    while (isspace(c))
        c = getchar();
    if (c == 45)
        s = 1, c = getchar();
    while (isdigit(c))
        x = (x << 3) + (x << 1) + (c ^ 48), c = getchar();
    if (s)
        x = -x;
}

template <typename T>
void write(T x, char c = ' ')
{
    int b[40], l = 0;
    if (x < 0)
        putchar(45), x = -x;
    while (x > 0)
        b[l++] = x % 10, x /= 10;
    if (!l)
        putchar(48);
    while (l)
        putchar(b[--l] | 48);
    putchar(c);
}

const int N = 1e5 + 8;

char s[N], t[N];

int n;

int cnt[N];

int num0[N];

int num1[N];

int qwq(int pos)
{
    return num0[pos] + (s[pos + 1] == 1) + 1 + num1[pos] + (t[pos + 1] == 0) + cnt[pos + 2];
}

int main(void)
{
    int kase;
    read(kase);
    for (int ii = 1; ii <= kase; ii++)
    {
        read(n);
        scanf("%s", s + 1);
        scanf("%s", t + 1);
        num0[0] = num1[0] = 0;
        for (int i = 1; i <= n; ++i)
        {
            s[i] -= '0';
            t[i] -= '0';
            num0[i] = num0[i - 1] + (s[i] == 0);
            num1[i] = num1[i - 1] + (t[i] == 1);
        }
        cnt[n + 1] = 0;
        cnt[n + 2] = 0;
        bool sign = false;
        int cur = n;
        for (int i = n; i >= 1; --i)
        {
            cnt[i] = cnt[i + 1] + (s[i] ^ t[i]);
        }
        int ans = cnt[1];
        for (int i = 1; i <= n; ++i)
        {
            ans = min(ans, qwq(i));
        }
        write(ans, '\n');
    }
    return 0;
}


C. Range k-th Maximum Query (Hdu 6840)

題目大意

給定一個\(n\)個數的序列,以及正整數\(k,l\),要求對它重新排序,使得所有長度為\(l\)的子區間的第\(k\)大的數和和最大和最小。求最大值和最小值。

解題思路

我們將數列從大到小排列。前\(k-1\)大的數不會對答案有貢獻。

要讓和最大,我們期望大的數對答案的貢獻盡可能多。

於是我們可以構造這樣的序列,它是由若干個長度為\(l\)的區間構成。

每個這樣的區間,前\(l - k\)個位置標記為紅,后\(k\)個位置標記為藍。

我們將這個排好序的序列,從左到右,按順序填充藍的位置,放完藍的,然后再從右到左,按順序填充紅的位置。

最后一個長度不足\(l\)的區間(如果有的話),前\(l - k\)個位置標記為紅,后\(k\)個位置標記為藍(如果有的話)。

這樣就是最大值的構造。

最小值,將數列從小到大排列,前\(k\)大的數也就是說前\(l - k + 1\)小的數,再按照上面構造就可以了。

既然構造出來了,答案自然也就能求出來了。

最大值的情況,設\(d = \lfloor \dfrac{n}{l} \rfloor , r = n \% l\)

排好序的數列里,\(k 到 d * k - 1\)的數都對答案有\(1\)次的貢獻,其中位置是\(k\)的倍數的還有額外的\((l - k)\)次的貢獻。

最后第\(d * k\)位的貢獻次數跟\(r\)有關

如果\(r > (l - k)\),則第\(d * k + 1\)位到第\(d * k + r - (l - k)\)的數對答案也有\(1\)次的貢獻。

神奇的代碼
#include <bits/stdc++.h>
using namespace std;
typedef long long LL;

template <typename T>
void read(T &x)
{
    int s = 0, c = getchar();
    x = 0;
    while (isspace(c))
        c = getchar();
    if (c == 45)
        s = 1, c = getchar();
    while (isdigit(c))
        x = (x << 3) + (x << 1) + (c ^ 48), c = getchar();
    if (s)
        x = -x;
}

template <typename T>
void write(T x, char c = ' ')
{
    int b[40], l = 0;
    if (x < 0)
        putchar(45), x = -x;
    while (x > 0)
        b[l++] = x % 10, x /= 10;
    if (!l)
        putchar(48);
    while (l)
        putchar(b[--l] | 48);
    putchar(c);
}

const int N = 1e5 + 8;

int n, k, l, d, r;

LL a[N];

LL solve(LL a[], int k)
{
    LL ans = 0;
    for (int i = k; i < d * k; ++i)
    {
        ans += a[i] * (1 + (l - k) * (i % k == 0));
    }
    ans += a[d * k] * min(r + 1, l - k + 1);
    if (r > l - k)
    {
        for (int i = d * k + 1, yu = r - l + k; yu; ++i, --yu)
        {
            ans += a[i];
        }
    }
    return ans;
}
int main(void)
{
    int kase;
    read(kase);
    for (int ii = 1; ii <= kase; ii++)
    {
        read(n);
        read(l);
        read(k);
        for (int i = 1; i <= n; ++i)
        {
            read(a[i]);
        }
        sort(a + 1, a + 1 + n, greater<int>());
        d = n / l;
        r = n % l;
        LL ans1 = solve(a, k);
        sort(a + 1, a + 1 + n);
        k = l - k + 1;
        LL ans2 = solve(a, k);
        printf("%lld %lld\n", ans1, ans2);
    }
    return 0;
}


題目大意

qwq

解題思路

qwq

神奇的代碼
qwq


E. Battle for Wosneth2 (Hdu 6842)

題目大意

初始\(Alice\)\(n\)滴血,\(Bob\)\(m\)滴血。\(Alice\)\(p\%\)命中\(Bob\),並使\(Bob\)減少一滴血,但自身血不變;\(Bob\)\(q\%\)概率命中\(Alice\),並使\(Alice\)減少一滴血,但自身血不變。當一方血量減為\(0\)時,對方獲勝。問\(Alice\)獲勝的概率。答案對\(998244353\)取模。

解題思路

我們抽象成一個二維平面圖,左下角\((0,0)\),初始位於\((n,m)\),然后有三個移動方向,問移動到\((r,0)\)的概率是多少(\(r\)是任意一個不大於\(n\)的數)。

當前位置為\((i,j)\)

  • 移動到\((i-1, j-1)\)的概率\(a = \dfrac{pq}{1 - (1 - p)(1 - q)}\)

  • 移動到\((i, j - 1)\)的概率\(b = \dfrac{p(1-q)}{1 - (1 - p)(1 - q)}\)

  • 移動到\((i - 1, j)\)的概率\(c = \dfrac{q(1-p)}{1 - (1 - p)(1 - q)}\)

值得注意的是,從\((i,1)\)移動到\((i,0)\)的概率是\(d = \dfrac{p}{1 - (1 - p)(1 - q)}\)

所以我們先計算移動到\((r,1)\)的概率,最后再計算移動到\((r,0)\)的概率。

這里有兩個自由變量。

如果我們假設移動到\((r,1)\),或者說,水平方向進行了\(i = n - r\)次移動,還要假設,我們進行了\(x\)次情況一的移動,則情況二進行了\(m - 1 -x\)次,情況三進行了\(i - x\)次移動。

則移動到\(m = 1\)的概率為

\[\sum\limits_{i = 0}^{n-1}\sum\limits_{x = 0}^{\min(i,m - 1)} C_{m - 1 + i -x}^{i - x} C_{m - 1} ^{x}a ^ {x} b ^ {m - 1 - x} c ^{i - x} \]

很顯然這式子整不動。通常處理方法就是交換求和順序。我們從實際意義來說明。

我們先假設進行了\(x\)次情況一的移動,則情況二進行了\(m - 1 -x\)次,情況三進行了\(i\)次移動,其中\(0 \leq i \leq n - 1 - x\)

則移動到\(m=1\)的概率為

\[\begin{aligned} &\sum\limits_{x = 0}^{\min(m-1,n-1)}\sum\limits_{i = 0}^{n - 1 - x} C_{m - 1 + i}^{i}C_{m - 1}^{x} a^{x}b^{m - 1 -x}c^{i} \\ &= \sum\limits_{x = 0}^{\min(m-1,n-1)}C_{m - 1}^{x} a^{x}b^{m - 1 -x}\sum\limits_{i = 0}^{n - 1 - x} C_{m - 1 + i}^{i}c^{i} \end{aligned} \]

而后面這一項可以事先預處理一個前綴和\(S(r) = \sum\limits_{i = 0}^{r} C_{m - 1 + i}^{i}c^{i}\)

這樣,最終的答案就是

\[ans = d \times \sum\limits_{x = 0}^{\min(m-1,n-1)}C_{m - 1}^{x} a^{x}b^{m - 1 -x}S(n - 1 - x) \]

值得注意的是在計算\(b^{m - 1 - x}\)時,萬萬不可算出\(b^{m-1}\)然后除以\(b\)除以\(b\)(取模意義上)。

因為當\(b=0\)的時候,這樣算的話\(0^{0} = 0\)

而實際上我們應該認為\(0^{0} = 1\),也就是說應當采用快速冪計算 (雖然會多個log)

神奇的代碼
#include <bits/stdc++.h>
using namespace std;
typedef long long LL;

template <typename T>
void read(T &x)
{
    int s = 0, c = getchar();
    x = 0;
    while (isspace(c))
        c = getchar();
    if (c == 45)
        s = 1, c = getchar();
    while (isdigit(c))
        x = (x << 3) + (x << 1) + (c ^ 48), c = getchar();
    if (s)
        x = -x;
}

template <typename T>
void write(T x, char c = ' ')
{
    int b[40], l = 0;
    if (x < 0)
        putchar(45), x = -x;
    while (x > 0)
        b[l++] = x % 10, x /= 10;
    if (!l)
        putchar(48);
    while (l)
        putchar(b[--l] | 48);
    putchar(c);
}

const LL mo = 998244353;

const int N = 2e5 + 8;

int n, m;

LL jie[N], invjie[N];

LL sum[N];

LL p, q;

LL a, b, c, d;

LL inv100 = 828542813;

LL ans;

LL qpower(LL a, LL b)
{
    LL qwq = 1;
    while (b)
    {
        if (b & 1)
            qwq = qwq * a % mo;
        b >>= 1;
        a = a * a % mo;
    }
    return qwq;
}

LL inv(LL x)
{
    return qpower(x, mo - 2);
}

LL C(int n, int m)
{
    if (n < m)
        return 0;
    return jie[n] * invjie[m] % mo * invjie[n - m] % mo;
}

int main(void)
{
    int kase;
    read(kase);
    jie[0] = invjie[0] = 1;
    for (int i = 1; i < N; ++i)
    {
        jie[i] = jie[i - 1] * i % mo;
        invjie[i] = inv(jie[i]);
    }
    for (int ii = 1; ii <= kase; ii++)
    {
        read(n);
        read(m);
        read(p);
        read(q);
        p = p * inv100 % mo;
        q = q * inv100 % mo;
        d = inv((1 - (1 - p) * (1 - q) % mo + mo) % mo);
        a = p * q % mo * d % mo;
        b = p * ((1 - q + mo) % mo) % mo * d % mo;
        c = q * ((1 - p + mo) % mo) % mo * d % mo;
        sum[0] = 1;
        LL tmp = c;
        for (int i = 1; i < n; ++i)
        {
            sum[i] = (sum[i - 1] + tmp * C(m - 1 + i, i) % mo) % mo;
            tmp = tmp * c % mo;
        }
        ans = 0;
        LL qaq = 1;
        // LL qbq = qpower(b, m - 1);
        // LL invb = inv(b);
        int up = min(m - 1, n - 1);
        for (int i = 0; i <= up; ++i)
        {
            ans = (ans + qaq * qpower(b, m - i - 1) % mo * C(m - 1, i) % mo * sum[n - i - 1] % mo) % mo;
            // ans = (ans + qaq * qbq % mo * C(m - 1, i) % mo * sum[n - i - 1] % mo) % mo;   // 0^0 = 1
            qaq = qaq * a % mo;
            // qbq = qbq * invb % mo;
        }
        ans = ans * p % mo * d % mo;
        write(ans, '\n');
    }
    return 0;
}


F. Query on the Tree (Hdu 6843)

題目大意

qwq

解題思路

qwq

神奇的代碼
qwq



免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM