1. 程式人生 > 其它 >使用 postMessage 解決 iframe 跨域通訊問題

使用 postMessage 解決 iframe 跨域通訊問題

ABC 怎麼這麼難。賽後補的,\(\rm G\) 瞅了題解。\(\rm Ex\) 我知道,這題大毒瘤,就沒打算寫,是最小割和最小割計數,後者跟計算幾何有關係。

A - Shampoo

給出 \(v,a,b,c\),輪流給 \(v\) 減去 \(a,b,c\),求當 \(v\) 第一次為負數時,減去的數是哪個。(\(1\le v,a,b,c\le 10^5\))

可以直接模擬。不過也可以取餘數找到剩下的然後判斷。時間複雜度 \(\mathcal{O}(1)\)

#include <cstdio>
int main()
{
    int v, a, b, c; scanf("%d%d%d%d", &v, &a, &b, &c);
    int res = v % (a + b + c);
    if (res < a) puts("F");
    else 
    {
        res -= a;
        if (res < b) puts("M");
        else puts("T");
    }
    return 0;
}

B - Hit and Blow

給出兩個長為 \(n\) 的互不相同序列 \(A,B\),分別求滿足 \(i=j,A_i=B_j\)\(i\ne j,A_i=B_j\) 的有序數對 \((i,j)\) 的數量。(\(1\le n\le 10^3,1\le A_i,B_i\le 10^9\))

前者可以直接判,二者的和可以用 std::map 解決。後者作差即可得到。時間複雜度 \(\mathcal{O}(n\log n)\)

#include <map>
#include <cstdio>
const int N = 1e3 + 10; std::map<int, int> mp; int a[N];
int main()
{
    int n, cnt = 0, tot = 0; scanf("%d", &n);
    for (int i = 1; i <= n; ++i) scanf("%d", a + i), mp[a[i]] = 1;
    for (int i = 1, x; i <= n; ++i)
        scanf("%d", &x), cnt += (x == a[i]), tot += mp[x];
    printf("%d\n%d\n", cnt, tot - cnt); return 0; 
}

C - Collision 2

給出 \(n\) 個二維平面上的人,第 \(i\) 個人位於 \((x_i,y_i)\),座標互不相同,一開始面朝 \(s_i\in\{\mathtt{L,R}\}\),分別表示左和右。求如果所有人按照相同速度一直走下去會不會相撞。(\(2\le n\le 2\times 10^5,0\le x_i,y_i\le 10^9\))

注意到每種 \(y\) 座標是獨立的。所以考慮記錄每個 \(y\) 座標對應的,\(s_i=\tt R\) 的人,\(x_i\) 的最小值。這樣,所有 \(s_i=\tt L\) 的人,如果 \(x_i\) 座標大於 \(y_i\) 座標對應的最小值,則就會相撞,反之如果小於或 \(y_i\)

座標上沒有 \(\tt R\) 的人,就不會相撞。上述過程可以用 std::map 輕鬆實現,時間複雜度 \(\mathcal{O}(n\log n)\)

#include <map>
#include <cstdio>
const int N = 2e5 + 10; int x[N], y[N]; char s[N]; std::map<int, int> mp;
int main()
{
    int n; scanf("%d", &n);
    for (int i = 1; i <= n; ++i) scanf("%d%d", x + i, y + i);
    scanf("%s", s + 1);
    for (int i = 1; i <= n; ++i)
        if (s[i] == 'R')
        {
            if (!mp.count(y[i])) mp[y[i]] = x[i];
            else mp[y[i]] = std::min(mp[y[i]], x[i]);
        }
    for (int i = 1; i <= n; ++i)
        if (s[i] == 'L')
            if (mp.count(y[i]) && mp[y[i]] < x[i]) return puts("Yes"), 0;
    puts("No"); return 0;
}

D - Moves on Binary Tree

給出一棵結點可看做無窮多的滿二叉樹,\(i\) 號結點的左右兒子分別為 \(2i,2i+1\)。初始時在結點 \(x\),有長為 \(n\) 的操作序列 \(s\),其中 \(s_i\in\{\tt U,L,R\}\),分別表示向父親,左兒子,右兒子走。求最終位於哪個結點,保證答案 \(\le 10^{18}\)。(\(1\le n\le 10^6,1\le x\le 10^{18}\))

直接模擬肯定會爆 long long。但注意到題目的限制,所以我們只需要求出最終有用的操作序列,然後模擬就不會爆 long long 了。具體來講,維護一個棧,如果 \(s_i\in\{\tt L,R\}\),就把它壓到棧裡。如果 \(s_i=\tt U\),則如果棧為空,就令 \(x\leftarrow \lfloor\frac{x}{2}\rfloor\),否則彈出棧頂。將最終得到的操作序列施加到最終的 \(x\) 上即可,時間複雜度 \(\mathcal{O}(n)\)

#include <cstdio>
const int N = 1e6 + 10; typedef long long ll; char s[N]; int c[N], tp;
int main()
{
    int n; ll x; scanf("%d%lld%s", &n, &x, s + 1);
    for (int i = 1; i <= n; ++i)
        if (s[i] == 'U') { if (!tp) x /= 2; else --tp; }
        else if (s[i] == 'L') c[++tp] = 0;
        else c[++tp] = 1;
    if (tp < 0)
        for (int i = 1; i <= -tp; ++i) x /= 2;
    else 
        for (int i = 1; i <= tp; ++i)
            if (!c[i]) x *= 2; else x = x * 2 + 1;
    printf("%lld\n", x); return 0;
}

E - Edge Deletion

給出一張 \(n\) 個點 \(m\) 條邊的簡單連通無向圖,一條邊能被刪去,當且僅當:

  • 刪去後原圖依然連通。
  • 刪去後,原圖任意兩點間最短路不變。

求最多刪去多少條邊。(\(2\le n\le 300,n-1\le m\le \frac{n(n-1)}{2},1\le w\le 10^9\)\(w\) 表示邊權)

考慮轉化原題目給出的條件。什麼樣的邊能被刪去呢?看起來這個問題似乎不太好像,那我們換一邊,什麼樣的邊不能被刪去呢?這個問題就比較好解決了,首先,一條邊 \((x,y,z)\) 不能被刪去的必要條件是 \(dis_{x,y}=z\),其中 \(dis\) 表示最短路。首先肯定有 \(dis_{x,y}\le z\),而如果 \(dis_{x,y}<z\),那最短路上一定沒有 \((x,y,z)\),刪掉啥都不影響。而另一個必要條件是,\(x,y\) 之間,不存在長度大於 \(1\) 的,權值為 \(z\) 的路徑。原因比較顯然,因為這樣刪掉後原圖就不連通了。可以 證明,這兩個條件加起來就是充要條件了。

必要性已經證明完了,考慮充分性。其實也比較顯然,因為這倆條件滿足後,刪掉這條邊會對最短路和連通性造成影響。所以我們現在的問題變為判斷兩點間最短路,和是否存在長度大於 \(1\),權值為 \(z\) 的路徑。這些需要的資訊都可以用一遍 \(\rm floyd\) 求出,時間複雜度 \(\mathcal{O}(n^3)\)

#include <cstdio>
#include <cstring>
const int N = 310; typedef long long ll;
ll dis[N][N]; int E[N][N], id[N][N];
int main()
{
    int n, m; scanf("%d%d", &n, &m);
    memset(dis, 0x3f, sizeof (dis));
    for (int i = 1; i <= n; ++i) dis[i][i] = 0;
    for (int i = 1, x, y, z; i <= m; ++i)
        scanf("%d%d%d", &x, &y, &z), E[x][y] = E[y][x] = z,
        id[x][y] = id[y][x] = 1, dis[x][y] = dis[y][x] = z;
    for (int k = 1; k <= n; ++k)
        for (int i = 1; i <= n; ++i)
            for (int j = 1; j <= n; ++j)
                if (dis[i][k] + dis[k][j] <= dis[i][j]) 
                    dis[i][j] = dis[i][k] + dis[k][j];
    int cnt = 0;
    for (int i = 1, flg; i <= n; ++i)
        for (int j = 1; j <= n; ++j)
        {
            if (i == j || dis[i][j] != E[i][j]) continue;
            flg = 1;
            for (int k = 1; k <= n; ++k)
                if (k != i && k != j && dis[i][k] + dis[k][j] == E[i][j]) { flg = 0; break; }
            cnt += flg;
        }
    printf("%d\n", m - cnt / 2); return 0;
}

F - Lottery

\(n\) 種獎品,每種獎品有一個權值 \(w_i\),每次抽獎抽到第 \(i\) 個獎品的概率是:

\[\dfrac{w_i}{\sum\limits_{j=1}^nw_j} \]

\(K\) 次抽獎,恰好抽到 \(m\) 種獎品的概率。(\(1\le n,m,K\le 50,m\le n\))

考慮描述一個抽獎的狀態,注意到我們關心的是種數,所以考慮用一個序列 \(\left<c\right>\) 來描述:

\[\left<c_1,c_2,c_3,\cdots,c_n\right> \]

\(c_i\) 表示 \(i\) 種物品出現的次數。則這個狀態出現的概率是:

\[\dfrac{K!}{\prod\limits_{i=1}^nc_i!}\prod_{i=1}^np_i^{c_i} \]

其中 \(p_i=\frac{w_i}{\sum\limits_{j=1}^nw_j}\),意義就是可重集排列數(一共有幾種情況)和樸素的概率。

而注意到,這東西乘起來,每個 \(i\) 的貢獻是獨立的,可以考慮對每種型別分別考慮,做 \(\rm dp\)

\(f_{i,j,k}\) 表示前 \(i\) 種物品,選了 \(j\) 種,共選了 \(k\) 個物品的,所有方案權值和。其中一種方案 \(\left<c\right>\) 對應的權值:

\[\dfrac{1}{\prod\limits_{i=1}^n c_i!}\prod_{i=1}^n p_i^{c_i} \]

最終答案即為 \(K!f_{n,m,K}\)。轉移就是列舉 \(c_i\) 的值:

\[f_{i,j,k}=f_{i-1,j,k}+\sum_{l=1}^k \dfrac{f_{i-1,j-1,k-l}}{l!}p_i^{l} \]

統計答案即可。時間複雜度 \(\mathcal{O}(nmK^2)\)

#include <cstdio>
const int N = 60, mod = 998244353; typedef long long ll;
int W[N], p[N], fac[N], ifac[N], f[N][N][N];
inline int ksm(int a, int b)
{
    int ret = 1;
    while (b)
    {
        if (b & 1) ret = (ll)ret * a % mod;
        a = (ll)a * a % mod; b >>= 1;
    }
    return ret;
}
int main()
{
    int n, m, K, sum = 0; scanf("%d%d%d", &n, &m, &K); fac[0] = 1;
    for (int i = 1; i < N; ++i) fac[i] = (ll)fac[i - 1] * i % mod;
    ifac[N - 1] = ksm(fac[N - 1], mod - 2);
    for (int i = N - 2; i; --i) ifac[i] = (ll)ifac[i + 1] * (i + 1) % mod;
    for (int i = 1; i <= n; ++i) scanf("%d", W + i), (sum += W[i]) %= mod;
    for (int i = 1; i <= n; ++i) p[i] = (ll)W[i] * ksm(sum, mod - 2) % mod;
    f[0][0][0] = 1;
    for (int i = 1; i <= n; ++i)
        for (int j = 0; j <= m; ++j)
            for (int k = 0; k <= K; ++k)
            {
                (f[i][j][k] += f[i - 1][j][k]) %= mod;
                if (!j) continue;
                for (int c = 1; c <= k; ++c)
                    (f[i][j][k] += (ll)f[i - 1][j - 1][k - c] * ifac[c] % mod * ksm(p[i], c) % mod) %= mod;
            }
    int ans = (ll)fac[K] * f[n][m][K] % mod;
    printf("%d\n", ans); return 0;
}

G - Sqrt

\(t\) 組資料,每組給出一個序列 \(A=(x)\),執行一下操作無窮次:

  • 設末尾的數為 \(y\),向末尾加入一個 \([1,\lfloor\sqrt{y}\rfloor]\) 之間的整數。

求最終能得到多少種不同的最終序列。(\(1\le t\le 20,1\le x\le 9\times10^{18}\))

考慮樸素的 \(\rm dp\),設 \(f_x\) 表示以 \(x\) 開頭的序列個數,列舉下一次放哪個即可得到樸素轉移:

\[f_x=\sum_{i=1}^{\lfloor\sqrt{\overline{x}}\rfloor}f_i \]

處理出所有 \(\le \sqrt{x}\)\(f\),順便維護個字首和,即可在 \(\mathcal{O}(t\sqrt{x})\) 的時間複雜度內求解。

當然,這麼做是不能通過的,考慮優化。注意到只要我們知道 \(A_i,A_{i+2}\),其實 \(A_{i+1}\) 的範圍我們是能知道的。考慮從 \(A_i\) 直接轉移到 \(A_{i+2}\),並算上所有 \(A_{i+1}\) 可能的取值。即如果我們假設 \(A_{i+1}\in[l,r]\),則:

\[f_x=\sum_{i=1}^{\lfloor\sqrt[4]{\overline{x}}\rfloor}f_i(r-l+1) \]

容易發現,如果 \(A_i=x,A_{i+2}=i\),則 \(l=i^2,r=\sqrt{x}\)。這樣,我們分別維護 \(i^2f_i,f_i\) 的字首和,即可做到 \(\mathcal{O}(t\sqrt[4]{x})\) 的時間複雜度。

順便,提前預處理即可做到 \(\mathcal{O}(\sqrt[4]{x})\)。注意,cmathsqrt() 會掉精度,需要處理一下。

#include <cmath>
#include <cstdio>
#include <climits>
const int N = 6e4 + 10; typedef long long ll; 
ll f[N], pref[N], p[N]; typedef long double ld;
inline ll Sqrt(ll x)
{
    ll ret = sqrt(x) + 1;
    while (ret * ret > x) --ret;
    return ret;
}
inline void pre(int n)
{
    f[1] = pref[1] = p[1] = 1; ll t;
    for (ll i = 2; i <= n; ++i)
    {
        t = Sqrt(Sqrt(i));
        f[i] = pref[t] * ((ll)Sqrt(i) + 1) - p[t];
        pref[i] = pref[i - 1] + f[i];
        p[i] = p[i - 1] + f[i] * i * i;
    }
}
int main()
{
    int T; scanf("%d", &T); pre(N - 1);
    while (T--)
    {
        ll x, n; scanf("%lld", &x); n = Sqrt(Sqrt(x));
        printf("%lld\n", pref[n] * ((ll)Sqrt(x) + 1) - p[n]);
    }
    return 0;
}