AtCoder Beginner Contest 221 A~E題解


發揮比較好的一場,就來搓篇題解。

F 沒時間寫了,最后只是口胡了一下(還不是很對,改完后放上)。

比賽傳送

A - Seismic magnitude scales

給你 \(A,B\),表示兩場地震的強度。已知強度每 \(+1\) 所產生的能量就是原來的 \(32\) 倍。求 \(A\) 地震產生的能量是 \(B\) 地震產生能量的多少倍。數據保證 \(A \ge B\)

輸出 \(32^{A-B}\) 即可。

int Pow(int x, int p) {
    int res = 1;
    while(p) {
        if(p & 1) res = res * x;
        x = x * x, p >>= 1;
    }
    return res;
}

signed main()
{
	int A = read(), B = read();
	int x = A - B;
	cout<<Pow(32, x);
    return 0;
}

B - typo

給你兩個相同長度的字符串 \(S,T\),問 \(S\) 串交換一次或不交換能否得到 \(T\) 串。

找到第一個不相同的位置交換一下,然后重新判斷整串是否相同即可。

int main()
{
	cin >> s + 1 >> t + 1;
	int len1 = strlen(s + 1);
	int len2 = strlen(t + 1);
	int cnt = 0;
	for(int i = 1; i < len1; ++i) {
	    if(s[i] != t[i] && s[i + 1] != t[i + 1]) {
	        swap(s[i], s[i + 1]);
	        break;
        }
    }
    int flag = false;
    for(int i = 1; i <= len1; ++i) {
        if(s[i] != t[i]) {
            flag = true;
        }
    }
    if(flag) puts("No");
    else puts("Yes"); 
    return 0;
}

C - Select Mul

給你一個數 \(n\)\(n\) 的每位數可以隨便排列,然后可以將 \(n\) 分成兩部分。求這兩部分乘積的最大值。注意兩部分都不能有前導零。 \(n \le 10^9\)

模擬即可。

\(n\) 拆位,枚舉 \(n\) 所有位數的全排列,對於每個排列暴力分成兩部分求乘積最大值。

總復雜度大概是 \(\mathcal O( s ! s^2)\),其中 \(s\) 表示 \(n\) 的位數。反正隨便過嘍。

void Calc() {
    for(int i = sc; i >= 2; --i) {
        int flag = false;
        for(int j = sc; j >= i; --j) {
            if(!stc[j]) flag = true;
            if(stc[j]) break;
        }
        if(flag) continue;
        flag = false;
        for(int j = i - 1; j >= 1; --j) {
            if(!stc[j]) flag = true;
            if(stc[j]) break;
        }
        if(flag) continue;
        int x = 0, y = 0;
        for(int j = sc; j >= i; --j) x = x * 10 + stc[j];
        for(int j = i - 1; j >= 1; --j) y = y * 10 + stc[j];
        ans = max(ans, x * y);
    }
}

signed main()
{
	n = read();
	while(n) {
	    stc[++sc] = n % 10;
	    n /= 10;
    }
    sort(stc + 1, stc + sc + 1);
    do {
        Calc();
    }while(next_permutation(stc + 1, stc + sc +1));
    printf("%lld\n", ans);
    return 0;
}

D - Online games

給你 \(n\) 條線段,每條線段有一個左端點 \(a_i\) 和長度 \(b_i\)。設 \(d_k\) 表示有 \(d_k\) 個點被覆蓋了 \(k\) 次,求出所有 \(d_k (k \in [1,n])\) 並輸出。
\(n \le 2 \times 10^5\)\(1 \le a_i,b_i \le 10^9\)

這個問題好像非常經典。

考慮差分統計貢獻。

需要對 +1 貢獻的 左端點 和有 -1 貢獻的 右端點+1 進行離散化。

設離散化后一共有 \(Cnt\) 個點,實際上這 \(Cnt\) 個點構成了 \(Cnt - 1\) 個左開右閉的區間,那么

\(i\) 個點用來統計 \([date_{i},date_{i+1})\) 這段區間的貢獻,直接用一個 \(cnt_i\) 來記錄。(顯然這段區間中沒個點被覆蓋次數都是一樣的吧)

最后枚舉所有表示的區間統計答案即可。

signed main()
{
	n = read();
	for(int i = 1; i <= n; ++i) {
        a[i] = read(), b[i] = a[i] + read();
        date[++date_num] = a[i];
        date[++date_num] = b[i];
    }
    sort(date + 1, date + date_num + 1); date[0] = -INF;
    for(int i = 1; i <= date_num; ++i) if(date[i] != date[i - 1]) date[++Cnt] = date[i];
    for(int i = 1; i <= n; ++i) {
        a[i] = lower_bound(date + 1, date + Cnt + 1, a[i]) - date;
        b[i] = lower_bound(date + 1, date + Cnt + 1, b[i]) - date;
        cnt[a[i]]++, cnt[b[i]]--;
    }
    date[0] = 0;
    for(int i = 1; i <= Cnt; ++i) {
        cnt[i] += cnt[i - 1];
    }
//    for(int i = 1; i <= Cnt; ++i) cout<<cnt[i]<<" "; puts("");
//    for(int i = 1; i <= Cnt; ++i) cout<<date[i]<<" "; puts("");
    for(int i = 2; i <= Cnt; ++i) {
        ans[cnt[i - 1]] += date[i] - date[i - 1];
    }
    for(int i= 1; i <= n; ++i) {
        printf("%lld ", ans[i]);
    }
    return 0;
}

E - LEQ

給你一個長度為 \(n\) 的序列 \(a\),統計有多少長度 \(k \ge 2\) 的子序列 \(a_{b_1},a_{b_2},...,a_{b_k}\) 滿足 \(a_{b_1} \le a_{b_k}\),答案對 \(998244353\) 取模。
\(n \le 3 \times 10^5, 1 \le a_i \le 10^9\)

一開始你很想當然的以為統計的是長度 \(\ge 2\) 的子串,然后你感覺這個題簡單的一匹,離散化后想當然的敲了一個樹狀數組維護出現次數。

你發現第一個樣例不對,然后你發現它題目寫的的是子序列。。。

沒錯就是我。

那好,正文開始。

我們延續上面的做法,想想怎么魔改一下。

不難發現所有貢獻只和序列首元素和序列尾元素之間的大小關系有關。

然后你考慮找出所有滿足 \(a_j \le a_i\)\((j,i)\) 數對,然后 \(j,i\) 之間的數可以隨便選,假設有 \(s = i - j - 1\) 個,那么 \((j,i)\) 的貢獻就是 \(2^s\)

暴力找所有合法數對是 \(\mathcal O(n^2)\) 的,考慮用上面那個錯解的方法優化。

假設現在有三個點 \(j,k,i\),滿足 \(j,k < i\)\(j = k-1\)\(a_j \le a_i\)\(a_k \le a_i\)

不難發現 \((j,i)\) 的貢獻是 \((k,i)\) 的貢獻的 \(2^1\) 倍。

那我們對 \(j\) 這個點在樹狀數組上統計貢獻時可以用 \(2^{n-j}\),這樣每個點的貢獻都是 \(2\)\(2\) 倍的縮小。

然后考慮枚舉右端點 \(i\),並且再次之前 \([1,i-1]\) 中的點的全部被統計進樹狀數組了。

假設 \(j_1,j_2,...,j_k\) 的點都滿足 \(a_{j_x} \le a_i\)

此時在樹狀數組上查詢的和其實就是 \(\displaystyle \sum_{x=1}^{k} 2^{n-j_x}\)

顯然算多了,那么算多了多少?

假設 \(j_k = i - 1\),但 \(j_k\) 在樹狀數組中的貢獻為 \(2^{n-i+1}\),它與 \(i\) 的貢獻為 \(2^0 = 1\),所以應該除以這個數 \(2^{n-i+1}\),然后你發現與前面所有的有貢獻的點都需要除以 \(2^{n-i+1}\)

所以答案為

\[\frac{\displaystyle \sum_{x=1}^{k} 2^{n-j_x}}{2^{n-i+1}} \]

統計所有 \(i\) 的貢獻求和即為最終答案。

總時間復雜度為 \(\mathcal O(n \log^2 n)\),如果你預處理 \(2\) 的次冪,可以做到 \(\mathcal O(n \log n)\)

namespace Bit {
    int sum[MAXN];
    int lb(int x) { return x & -x; }
    void Modify(int x, int k) { for(; x <= Cnt; x += lb(x)) sum[x] = (sum[x] + k) % mod; }
    int Query(int x) { int res = 0; for(; x; x -= lb(x)) res = (res + sum[x]) % mod; return res; }
}

int Pow(int x, int p) {
    int res = 1;
    while(p) {
        if(p & 1) res = res * x % mod;
        x = x * x % mod;
        p >>= 1;
    }
    return res;
}

signed main()
{
	n = read();
	for(int i = 1; i <= n; ++i) {
	    a[i] = read();
	    date[i] = a[i];
    }
    sort(date + 1, date + n + 1), date[0] = -INF;
    for(int i = 1; i <= n; ++i) if(date[i] != date[i - 1]) date[++Cnt] = date[i];
    for(int i = 1; i <= n; ++i) a[i] = lower_bound(date + 1, date + Cnt + 1, a[i]) - date;
    for(int i = 2; i <= n; ++i) {
        Bit::Modify(a[i - 1], Pow(2, n - i));
        int res = Bit::Query(a[i]);
//        cout<<res<<" "<<sum<<"\n";
        ans = (ans + res * Pow(Pow(2, (n - i)), mod - 2) % mod) % mod; 
    }
    printf("%lld\n", ans);
    return 0;
}


免責聲明!

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



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