2024.3.17 模擬賽

心海秋的墨木仄發表於2024-04-05

A 貿易

題目保證輸入的邊 \(u < v\),說明題目中的圖是一個有向無環圖 \(DAG\),但是不一定連通。可以記錄 \(f[i]\) 表示到達 \(i\) 之前能遇到的最小的價格,使用拓撲排序進行 \(dp\) 轉移。對於每一個點 \(i\) , 如果其價格為 \(a[i]\), 就可以用 \(a[i] - f[i]\) 更新答案,取最大值即可,複雜度 \(O(n+m)\)

#include<bits/stdc++.h>
using namespace std;
const int maxn = 2e5 + 10;
vector<int> v[maxn];
int du[maxn], n, m;
int val[maxn], f[maxn];

int main() {
    cin >> n >> m;
    for(int i = 1; i <= n; i++) cin >> val[i], f[i] = 2e9 + 7;
    for(int i = 1; i <= m; i++) {
        int x, y;
        cin >> x >> y;
        v[x].push_back(y);
        du[y]++;
    }
    queue<int> q;
    for(int i = 1; i <= n; i++) if(!du[i]) q.push(i);
    int ans = -2e9 - 7;
    while(!q.empty()) {
        int now = q.front(); q.pop();
        ans = max(ans, val[now] - f[now]);
        f[now] = min(f[now], val[now]);
        for(int i = 0; i < v[now].size(); i++) {
            int to = v[now][i];
            f[to] = min(f[to], f[now]);
            du[to]--;
            if(!du[to]) q.push(to);
        }
    }
    cout << ans;
    return 0;
}

B 格子游戲

因為每次操作會影響右邊和下邊,因此根據貪心,最優的一定是從左上到右下操作,這樣已經變成 \(0\) 的格子不會再改變。容易發現,這樣的策略下,每個點的操作次數等於它上面和左面操作次數的和,因此這是一個楊輝三角。接下來就簡單了,根據楊輝三角第 \(i\) 行第 \(j\) 列的值是 \(C_{i-1}^{j-1}\),對應到這個傾斜的楊輝三角上就是 \(C_{i-1}^{i+j-2}\)

所以對於每一行,產生的答案就是楊輝三角第 \(i+1\) 行前 \(a_i+1\) 個數的和,而這個值就是第 \(i+1\) 行第 \(a_i\) 列的值。因此答案為 \(\sum_{i=0}^n C_{i+a_i}^i\)

#include <bits/stdc++.h>
using namespace std;
const int maxn = 2e5 + 10;
const int mod = 1e9 + 7;
long long ksm(int x, int y) {
	int res = 1;
	while(y) {
		if(y & 1) res = 1LL * res * x % mod;
		x = 1LL * x * x % mod;
		y >>= 1;
	}
	return res;
}
int mul[maxn << 1];
int calc(int x, int y) {
	return 1LL * mul[x] * ksm(mul[y], mod - 2) % mod * ksm(mul[x - y], mod - 2) % mod;
}
int a[maxn];
int main() {
	int n;
	cin >> n;
	for(int i = 1; i <= n + 1; i++) cin >> a[i];
	mul[0] = 1;
	for(int i = 1; i < (maxn << 1); i++) mul[i] = 1LL * mul[i - 1] * i % mod;
	int ans = 0;
	for(int i = 1; i <= n + 1; i++) (ans += calc(i + a[i] - 1, i)) %= mod;
	printf("%d", ans);
	return 0;
}

C \(ST\) 距離

首先,考慮如何計算兩個串的答案。 首先把奇數位置的值取反,那每次操作相當於 \(01\rightarrow 10\)\(10\rightarrow 01\)。於是當兩個串 \(1\) 的個數相等時可以達成。 可以看作若干個 \(1\) 在一條鏈上移動到新的位置。答案為距離之和,把移動貢獻均攤到每條邊上,那每一條邊的貢獻就是 兩個串 \([1,i]\) 中的 \(1\) 個數的差值。 列舉 \([1,i]\) 中兩個串 \(?\) 處填寫 \(0\) 的差值個數,那另一邊 \([i+1,n]\) 中 ? 處填寫 \(0\) 的差值個數確定。 如果確定了一邊的 \(?\) 處填寫 \(0\) 的差值個數,那列舉第一個串中的 0 個數,一邊的答案形如:

\[\sum_j C_A^j C_B^{j+d} \]

\[\sum_j C_A^j C_B^{B-j-d} \]

可以用範德蒙德卷積,化成一個組合數:

\[C_{A+B}^{B-d} \]

另一邊也相同,方案數就是兩邊乘起來。於是可以得到 \(O(n^2)\) 的做法。 觀察這個式子,發現我們每次要求的形如:

\[C_A^{A_2-j}C_B^{B_2+j}|j| \]

要求這個答案 \(n\) 次,並且每次的 \(A+B,A_2+B_2\) 為定值。 這個式子很像範德蒙德卷積,考慮把絕對值先拆成 \(|j|=(-j)+(2j)[0\leq j]\)。第一部分要求 \(C_A^{A_2-j}C_B^{B_2+j}j\) 可以變成

\[C_A^{A_2-j}C_B^{B_2+j}(B_2+j)-C_A^{A_2-j}C_B^{B_2+j}B_2 \]

組合數收縮公式:

\[C_A^{A_2-j}C_{B-1}^{B_2+j-1}B-C_A^{A_2-j}C_B^{B_2+j}B_2 \]

再用範德蒙德卷積:

\[C_{A+B-1}^{A_2+B_2-1}B-C_{A+B}^{A_2+B_2}B_2 \]

於是可以單次 \(O(1)\)。 第二部分要求 \(C_A^{A_2-j}C_B^{B_2+j}j[0\leq j]\)。用相同的方法變成:

\[C_A^{A_2-j}C_{B-1}^{B_2+j-1}B[0\leq j]-C_A^{A_2-j}C_B^{B_2+j}B_2[0\leq j] \]

兩部分是相同的,只考慮其中一部分,那就是要求:

\[C_A^{A_2-j}C_B^{B_2+j}[0\leq j] \]

\[C_A^jC_B^{A_2+B_2-j}[j\leq A_2] \]

由於每次 \(A+B,A_2+B_2\)為定值,設 \(s_1=A+B,s_2=A_2+B_2\)。考慮其組合意義,相當於有 \(S_1\) 個球,其中要放 \(S_2\) 個黑球,並且要求前 \(A\) 個球中只有 \(\leq A_2\)​ 個黑球的方案數。

考慮在這條鏈上,從 \(i\rightarrow i+1\) 的時候,\(A\)\(A_2\) 都只會有 \(O(1)\) 的變化。如果能 \(O(1)\) 移動 \(A\pm 1,A_2\pm 1\) 並且實時維護答案,那就能做了。 如果 \(A_2\rightarrow A_2+1\) ,答案只需要加上新的貢獻 \(C_A^{A_2+1}C_{S_1-A}^{S_2-A_2-1}\)。如果 \(A\rightarrow A+1\),答案會減少,再考慮其組合意義,減少的量就是 “第 \(A_2+1\) 個黑球放在 \(A+1\) 位置”的方案數。於是答案只需要減去 \(C_A^{A_2}C_{S_1-A-1}^{S_2-A_2-1}\)。於是我們解決了 \(O(1)\) 移動端點的問題,整個問題可以 \(O(n)\) 解決。

#include<bits/stdc++.h>
#define For(i,l,r) for(int i=(l);i<=(r);++i)
#define ReFor(i,r,l) for(int i=(r);i>=(l);--i)
const int N=1000010;
const int mod=1000000007;
typedef long long ll;
using namespace std;
int n;
int S[N],T[N],S_pre_cnt[3],S_suf_cnt[3],T_pre_cnt[3],T_suf_cnt[3];
ll fac[N],invfac[N];
template<typename T1,typename T2>
void Add(T1 &a,T2 b){a+=b;if(a>=mod)a-=mod;return;}
template<typename T1,typename T2>
void Sub(T1 &a,T2 b){a-=b;if(a<0)a+=mod;return;}
ll qpow(ll a,int b)
{
    ll res=1;
    while(b)
    {
        if(b&1)(res*=a)%=mod;
        (a*=a)%=mod;b>>=1;
    }
    return res;
}
ll binom(int n,int m)
{
    if((n<0) || (m<0) || (n<m))return 0;
    ll res=fac[n];(res*=invfac[m])%=mod;(res*=invfac[n-m])%=mod;return res;
}
struct Binom
{
    int lim,A,S,S1;
    ll res;
    void clear(){res=0;lim=0;A=0;S=0;S1=0;return;}
    void brute_calc(){res=0;For(j,0,lim){ll delta=binom(A,j);(delta*=binom((S-A),(S1-j)))%=mod;Add(res,delta);}return;}
    void add_lim(){++lim;ll delta=binom(A,lim);(delta*=binom((S-A),(S1-lim)))%=mod;Add(res,delta);return;}
    void sub_lim(){ll delta=binom(A,lim);(delta*=binom((S-A),(S1-lim)))%=mod;Sub(res,delta);--lim;return;}
    void add_A(){ll delta=binom(A,lim);(delta*=binom((S-A-1),(S1-lim-1)))%=mod;Sub(res,delta);++A;return;}
}Part_1,Part_2;
void solve()
{
    cin>>n;For(i,1,n){char ch;cin>>ch;if(ch!='?')S[i]=(ch-'0');else S[i]=2;}For(i,1,n){char ch;cin>>ch;if(ch!='?')T[i]=(ch-'0');else T[i]=2;}
    For(i,1,n){if(!(i&1)){if(S[i]!=2)S[i]^=1;if(T[i]!=2)T[i]^=1;}}
    ll ans=0;For(i,0,2){S_pre_cnt[i]=0;T_pre_cnt[i]=0;S_suf_cnt[i]=0;T_suf_cnt[i]=0;}For(i,1,n){++S_suf_cnt[S[i]];++T_suf_cnt[T[i]];}
    Part_1.clear();Part_2.clear();
    Part_1.S=(S_suf_cnt[2]+T_suf_cnt[2]-1);Part_1.S1=(T_suf_cnt[2]-(S_suf_cnt[0]-T_suf_cnt[0])-1);
    Part_2.S=(S_suf_cnt[2]+T_suf_cnt[2]);Part_2.S1=(T_suf_cnt[2]-(S_suf_cnt[0]-T_suf_cnt[0]));
    For(i,1,n-1)
    {
        ++S_pre_cnt[S[i]];--S_suf_cnt[S[i]];++T_pre_cnt[T[i]];--T_suf_cnt[T[i]];
        int A=(S_pre_cnt[2]+T_pre_cnt[2]),A1=(T_pre_cnt[2]+(T_pre_cnt[0]-S_pre_cnt[0]));
        int B=(S_suf_cnt[2]+T_suf_cnt[2]),B1=(T_suf_cnt[2]-(S_suf_cnt[0]-T_suf_cnt[0]));
        if(i==1){Part_1.lim=(A1-1);Part_1.A=A;Part_1.brute_calc();Part_2.lim=(A1-1);Part_2.A=A;Part_2.brute_calc();}
        else
        {
            if(S[i]==0){Part_1.sub_lim();Part_2.sub_lim();}
            if(S[i]==2){Part_1.add_A();Part_2.add_A();}
            if(T[i]==0){Part_1.add_lim();Part_2.add_lim();}
            if(T[i]==2){Part_1.add_lim();Part_1.add_A();Part_2.add_lim();Part_2.add_A();}
        }
        {
            ll res1=B;if(res1<0)Add(res1,mod);(res1*=binom((A+B-1),(A1+B1-1)))%=mod;ll res2=B1;if(res2<0)Add(res2,mod);(res2*=binom((A+B),(A1+B1)))%=mod;
            ll res=res1;Sub(res,res2);Sub(ans,res);
        };
        {ll res=Part_1.res;(res*=B)%=mod;if(res<0)Add(res,mod);(res*=2)%=mod;Add(ans,res);};
        {ll res=Part_2.res;(res*=B1)%=mod;if(res<0)Add(res,mod);(res*=2)%=mod;Sub(ans,res);};
    }
    cout<<ans<<"\n";return;
}
int main()
{
    fac[0]=1;For(i,1,N-1){fac[i]=i;(fac[i]*=fac[i-1])%=mod;}
    invfac[N-1]=qpow(fac[N-1],(mod-2));ReFor(i,N-2,1){invfac[i]=(i+1);(invfac[i]*=invfac[i+1])%=mod;}invfac[0]=1;
    ios::sync_with_stdio(0);cin.tie(0);cout.tie(0);
    int t;cin>>t;while(t--)solve();return 0;
}

D 傳火

最佳方案就是所有人都朝 \(k\) 號跑,遇到了就跟隨,\(k\) 號火炬要燃盡時就點燃一根續上,所以每個跟上的人相當於將燃燒時間 \(+t\) 秒。考慮二分答案,轉化成以下問題:

有兩個佇列,要按順序點燃,每個物品點燃要代價 \(a_i\),會獲得 \(t\) 的收益,初始收益為 \(t\),要求任何時候收益不能為負,問是否能全部點燃。

考慮將兩個佇列分成若干組,每組總收益為正,但任意一個字首收益為負。

如果可以全部分組,那麼可以貪心選擇,能點盡點,如果某時刻不行答案就為否,否則回答是。

如果不能全部分組,那麼將不能分組的物品拿出來,它們點燃每個字首收益會減少。

注意到最後總收益是確定的,因此可以時間倒流,將貢獻加上 \(\sum a_i-t\),並將這些物品翻轉,就成了順序點燃,每點燃一個物品付出 \(t\) 的代價,獲得 \(a_i\) 收益,要求任何時候收益不為負。

因此可以將貢獻取負,倒著搞一下之前的演算法即可。

#include <bits/stdc++.h>
using namespace std;
template <typename T>
inline void read(T &x) {
    x = 0;
    short f = 1;
    char c = getchar();
    for (; c < '0' || c > '9'; c = getchar())
        if (c == '-')
            f = -1;
    for (; c >= '0' && c <= '9'; c = getchar()) x = (x << 1) + (x << 3) + (c ^ 48);
    x *= f;
    return;
}
#define ll long long
const int N = 1e6 + 5;
ll x[N], a[N];
int n, k, t;
bool check(ll v) {
    for (int i = 1; i <= n; ++i) a[i] = x[i] - 2ll * v * i * t;
    if (a[1] < a[n])
        return 0;
    int L = k, R = k;
    for (int i = 1; i <= k - 1; ++i)
        if (a[i] > a[L])
            L = i;
    for (int i = k + 1; i <= n; ++i)
        if (a[i] <= a[R])
            R = i;
    int l = k, r = k;
    while (l != L || r != R) {
        bool flag = 0;
        int tl = l, tr = r;
        while (tl > L && a[tl - 1] >= a[r])
            if (a[--tl] >= a[l])
                break;
        if (tl < l && a[tl] >= a[l])
            flag = 1, l = tl;
        while (tr < R && a[tr + 1] <= a[l])
            if (a[++tr] <= a[r])
                break;
        if (tr > r && a[tr] <= a[r])
            flag = 1, r = tr;
        if (!flag)
            return 0;
    }
    l = 1, r = n;
    while (l != L || r != R) {
        bool flag = 0;
        int tl = l, tr = r;
        while (tl < L && a[tl + 1] >= a[r])
            if (a[++tl] >= a[l])
                break;
        if (tl > l && a[tl] >= a[l])
            flag = 1, l = tl;
        while (tr > R && a[tr - 1] <= a[l])
            if (a[--tr] <= a[r])
                break;
        if (tr < r && a[tr] <= a[r])
            flag = 1, r = tr;
        if (!flag)
            return 0;
    }
    return 1;
}
int main() {
    read(n), read(k), read(t);
    for (int i = 1; i <= n; ++i) read(x[i]);
    ll l = 0, r = 1e9, ans = r;
    while (l <= r) {
        ll mid = (l + r) >> 1;
        if (check(mid))
            r = mid - 1, ans = mid;
        else
            l = mid + 1;
    }
    printf("%lld\n", ans);
    return 0;
}

E 樹權

容易發現答案一定包含直徑的某個端點,列舉是哪個端點,以這個端點為根建樹,進行長鏈剖分。

此時答案一定選擇了根到一個葉子結點的路徑,以及 \(k-1\) 對葉子,可以看成是選擇了 \(2k-1\) 個葉子。

貪心地進行選擇,一個葉子的貢獻是它到它所在鏈頂點的距離,只需選出貢獻最大的 \(2k-1\) 個葉子,容易證明一定存在使得這種貪心合法的方案。

接下來考慮必須覆蓋點 \(a\) 的限制,將葉子按貢獻從大到小排序,令 \(h_x\)\(x\) 子樹內葉子排名的最小值。對於點 \(x\),若 \(h_x\leq 2k-1\),則直接貪心的方案合法。否則要把一個葉子換成 \(x\) 子樹內的葉子。假設換掉了葉子 \(y\)

\(y\) 的頂端不是 \(x\) 的祖先,那我們換掉它的代價就是 \(y\) 的貢獻。

否則代價就是 \(y\)\(LCA(x,y)\) 的距離。

第一種情況可以直接換掉貢獻最小的葉子,第二種情況可以倍增解決。

#include <bits/stdc++.h>
using namespace std;
char buf[1000000], *p1 = buf, *p2 = buf;
#define getchar() (p1 == p2 && (p2 = (p1 = buf) + fread(buf, 1, 1000000, stdin), p1 == p2) ? EOF : *p1++)
template <typename T>
inline void read(T &x) {
    x = 0;
    short f = 1;
    char c = getchar();
    for (; c < '0' || c > '9'; c = getchar())
        if (c == '-')
            f = -1;
    for (; c >= '0' && c <= '9'; c = getchar()) x = (x << 1) + (x << 3) + (c ^ 48);
    x *= f;
    return;
}
char out[1 << 23], *pout = out, *eout = out + (1 << 23);
inline void flush() { fwrite(out, 1, pout - out, stdout), pout = out; }
inline void pc(char c) {
    pout == eout && (fwrite(out, 1, 1 << 23, stdout), pout = out);
    (*pout++) = c;
}
template <typename T>
inline void print(T x) {
    static int stk[35], top;
    if (x < 0)
        pc('-'), x = -x;
    if (x == 0)
        pc('0');
    while (x) stk[++top] = x % 10, x /= 10;
    while (top) pc(stk[top--] + '0');
    pc('\n');
}
const int N = 5e5 + 5;
#define ll long long
struct node {
    int v, w, nxt;
} e[N * 2];
int head[N], cnt = 1;
void add(int u, int v, int w) {
    e[cnt].v = v;
    e[cnt].w = w;
    e[cnt].nxt = head[u];
    head[u] = cnt++;
}
struct Tree {
    int rt;
    int son[N], f[N][20], rk[N];
    int p[N], tot;
    ll ans[N], len[N], dis[N], maxd[N];
    void dfs1(int u, int fa) {
        if (dis[u] > dis[rt])
            rt = u;
        for (int i = head[u]; i; i = e[i].nxt) {
            int v = e[i].v;
            if (v == fa)
                continue;
            dis[v] = dis[u] + e[i].w;
            dfs1(v, u);
        }
    }
    void dfs2(int u, int fa) {
        f[u][0] = fa;
        for (int k = 1; k <= 19; ++k) f[u][k] = f[f[u][k - 1]][k - 1];
        for (int i = head[u]; i; i = e[i].nxt) {
            int v = e[i].v, w = e[i].w;
            if (v == fa)
                continue;
            dis[v] = dis[u] + w;
            dfs2(v, u);
            if (w + maxd[v] > maxd[u])
                maxd[u] = maxd[v] + w, son[u] = v;
        }
        for (int i = head[u]; i; i = e[i].nxt) {
            int v = e[i].v;
            if (v == fa || v == son[u])
                continue;
            p[++tot] = v;
            len[v] = maxd[v] + e[i].w;
        }
        if (!fa)
            p[++tot] = u, len[u] = maxd[u];
    }
    void init(int x) {
        dfs1(x, 0);
        dis[rt] = 0;
        dfs2(rt, 0);
        sort(p + 1, p + tot + 1, [&](int x, int y) { return len[x] > len[y]; });
        for (int i = 1; i <= tot; ++i) {
            ans[i] = ans[i - 1] + len[p[i]];
            for (int u = p[i]; u; u = son[u]) rk[u] = i;
        }
    }
    ll ask(int x, int y, int tp) {
        ll w = maxd[x] + dis[x] + ans[y];
        for (int k = 19; k >= 0; --k)
            if (rk[f[x][k]] > y)
                x = f[x][k];
        return w - dis[f[x][0]] - (tp ? maxd[f[x][0]] : 0);
    }
    ll query(int x, int y) {
        y = 2 * y - 1;
        return rk[x] > y ? max(ask(x, y - 1, 0), ask(x, y, 1)) : ans[min(tot, y)];
    }
} A, B;
int n, m;
int main() {
    read(n);
    read(m);
    for (int i = 1; i < n; ++i) {
        int u, v, w;
        read(u), read(v), read(w);
        add(u, v, w), add(v, u, w);
    }
    A.init(1);
    B.init(A.rt);
    while (m--) {
        int a, k;
        read(a), read(k);
        print(max(A.query(a, k), B.query(a, k)));
    }
    flush();
    return 0;
}

相關文章