CF2161

CF2161B Make Connected

赛时看错题了。是只能出现连续两个,又是出现连续三个感觉很不可做啊。
最后的结论是,这个 # 的点要么形成 2 乘 2 的正方形,要么形成一个 L 形。
这种斜着的往往跟 \(x - y\)\(x + y\) 什么的有关系,所以一种很优雅的判断方式就是判断一下 \(\max(x - y) - \min(x - y) \le 1\) 之类的。

Code
#include <bits/stdc++.h>
using namespace std;
const int N = 1e2 + 5;
typedef pair<int, int> pii;
int n, mnx, mny, mxs, mns, mxm, mnm;
char c[N][N];
vector<pii> pos;
void solve(){
    cin >> n;
    pos.clear();
    mxs = mxm = -1e9, mnm = mns = mnx = mny = 1e9;
    for(int i = 1; i <= n; ++i){
        for(int j = 1; j <= n; ++j){
            cin >> c[i][j];
            if(c[i][j] == '#'){
                pos.emplace_back(i, j);
                mnx = min(i, mnx), mny = min(j, mny);
                mns = min(mns, i + j), mxs = max(mxs, i + j);
                mnm = min(mnm, j - i), mxm = max(mxm, j - i); 
            }
        }
    }
    if(mxs - mns <= 1 || mxm - mnm <= 1){
        return cout << "Yes\n", void();
    }
    if(pos.size() > 4){
        cout << "No\n"; 
        return;
    }
    // cout << mny << '\n';
    for(auto [x, y] : pos){
        x -= mnx, y -= mny;
        // cout << x << ' ' << y << '\n';
        if(!(x >= 0 && x <= 1 && y >= 0 && y <= 1)) return cout << "No\n", void();
    }
    cout << "Yes\n";
}
int main(){
    cin.tie(nullptr)->sync_with_stdio(0);
    int T; cin >> T;
    while(T--) solve();
    return 0;
}

CF2161D Locked Out

如果 \(i < j \land a_i + 1 = a_j\) 就把 \((i,j)\) 连边,然后就是一个最大点独立集问题。但我们知道这样还是不好做。考虑按值域一层一层考虑,这一层的决策只跟上一层有关,并且发现我们总是选择一段前缀。那么按此设计 \(f_{i, j}\) 表示第 \(i\) 层选了前 \(j\) 个,\(\le i\) 的最大点独立集。直接转是 \(N^2\) 的,前缀/后缀 max 优化一下,再加个双指针可以做到 \(O(N)\),下面写的是二分。

Code
#include <bits/stdc++.h>
using namespace std;
const int N = 3e5 + 5;
vector<int> pos[N], pre[N], suf[N], dp[N];
int n, a[N], ans;
void solve(){
    cin >> n;
    for(int i = 1; i <= n + 1; ++i){
        pos[i].clear(), pre[i].clear(), suf[i].clear(), dp[i].clear();
        pos[i].emplace_back(0);
    }
    for(int i = 1; i <= n; ++i){
        cin >> a[i];
        pos[a[i]].emplace_back(i);
    }
    for(int i = 1; i <= n; ++i){
        int siz = pos[i].size();
        pre[i].resize(siz), suf[i].resize(siz), dp[i].resize(siz);
        for(int j = 0; j < siz; ++j){
            int p = pos[i][j];
            int x = upper_bound(pos[i - 1].begin(), pos[i - 1].end(), p) - pos[i - 1].begin();
            if(x > 0) dp[i][j] = max(dp[i][j], pre[i - 1][x - 1]);
            if(x < pos[i - 1].size()) dp[i][j] = max(dp[i][j], suf[i - 1][x] - (x - 1));
            dp[i][j] += j;
        }
        for(int j = 0; j < siz; ++j)
            pre[i][j] = max((j == 0 ? 0 : pre[i][j - 1]), dp[i][j] - j);
        for(int j = siz - 1; j >= 0; --j)
            suf[i][j] = max((j == siz - 1 ? 0 : suf[i][j + 1]), dp[i][j]);
    }
    cout << n - suf[n][0] << '\n';
}
int main(){
    cin.tie(nullptr)->sync_with_stdio(0);
    int T; cin >> T;
    while(T--) solve();
    return 0;
}

CF2161E Left is Always Right

如果记 \(d\)\([i, i + k - 1]\) 中 1 的个数 - 0 的个数。发现当 \(i \gets i + 1\) 时,\(d \gets d + 2/d/d - 2\)。因此当一个地方 \(i \to i + 1\) 出现 \(1 \to 0\) 时,前面的 \(d\) 肯定是 1,后面的 \(d\) 肯定是 -1,并且 \(i + k\) 这个位置肯定是 0。
然后就卡在这了。实际上再往后模一步会发现,如果下一位继续放 0,那么 \(i + 1 + k\) 也得放 0,如果下一位放 1,那么 \(i + 1 + k\) 也得放 1。发现有周期性,循环节长度是 \(k - 1\),也就是从此之后的所有位置都确定了。
那么考虑枚举前缀 1 的长度 \(i\)(如果第一位是 0 就枚举 0,对称的,第一位是 ? 的话就赋值之后都做一遍),这意味着 \(i + 1\) 是 0。那么此时要保证 \(d_{[i, i + k - 1]} = 1\),也就是保证 \([i + 1, i + k - 1]\) 中 1 的个数 - 0 的个数为 0,也即 1 和 0 都放 \(\frac{k - 1}{2}\) 个。但由于周期性,后面的数有可能已经确定了,并影响前面的决策。这要求后面所有模 \(k - 1\) 同余的位置的取值都相同。然后维护一些桶判断是否冲突,然后再对没有确定的位置进行计数即可(简单组合数)。
最后 \(k\) 位题目的限制直接没了,有一些 corner。

Code
#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
const int N = 1e5 + 5, mod = 998244353;
int n, k, a[N];
int fl;
map<int, int> cnt[N], num;
ll fac[N], ifac[N], inv[N];
void erase(int i){
    if(a[i] == 0) return;
    int y = i % (k - 1), &x = cnt[y][a[i]];
    --x; 
    if(x == 0){
        cnt[y].erase(a[i]);
        if(cnt[y].size() == 1) fl--, num[cnt[y].begin() -> first]++;
        else num[a[i]]--;
    }
}
void add(ll &x, ll y){ (x += y) %= mod; }
ll C(int x, int y){
    if(y > x || y < 0) return 0;
    return fac[x] * ifac[y] % mod * ifac[x - y] % mod;
}
void solve(){
    cin >> n >> k;
    for(int i = 1; i <= n; ++i){
        char c; cin >> c;
        if(c == '0') a[i] = -1;
        else if(c == '1') a[i] = 1;
        else a[i] = 0;
    }
    auto cal = [](){
        ll ret = 0;
        if(a[1] == -1) for(int i = 1; i <= n; ++i) a[i] *= -1;
        for(int i = 0; i < k; ++i) cnt[i].clear();
        for(int i = 1; i <= n; ++i) if(a[i] != 0) cnt[i % (k - 1)][a[i]]++;
        fl = 0; num.clear();
        for(int i = 0; i < k - 1; ++i){
            if(cnt[i].size() == 2) fl++;
            else if(!cnt[i].empty()) num[cnt[i].begin() -> first]++;
        }
        int d = k - 1;
        for(int i = 1; i <= n - k + 1; ++i){
            if(a[i] != -1 && i == n - k + 1){
                int n0 = 0, n1 = 0;
                for(int j = i; j <= n; ++j){
                    if(a[j] == 1) n1++;
                    else if(a[j] == -1) n0++;
                }
                n1 += a[i] == 0;
                for(int x = max(n1, (k + 1) / 2); x <= n; ++x){
                    add(ret, C(k - n0 - n1, x - n1));
                }
            }
            if(a[i] != 1 && fl == 0){
                int x = i % (k - 1);
                if(cnt[x].size() == 2 || (!cnt[x].empty() && cnt[x].begin() -> first == 1));
                else{
                    int num0 = num[-1] + cnt[x].empty(), num1 = num[1];
                    add(ret, C(d - num0 - num1, d / 2 - num0));
                }
            }
            if(a[i] == -1) break;
            erase(i);
        } 
        return ret;
    };
    ll ans = 0;
    if(a[1] == 0){
        for(int o : {1, -1}){
            a[1] = o; add(ans, cal());
        }
    }
    else ans = cal();
    cout << ans << '\n';
}
int main(){
    cin.tie(nullptr)->sync_with_stdio(0);
    fac[0] = ifac[0] = fac[1] = ifac[1] = inv[1] = 1;
    for(int i = 2; i <= 1e5; ++i){
        fac[i] = (fac[i - 1] * i) % mod;
        inv[i] = (mod - mod / i) * inv[mod % i] % mod;
        ifac[i] = (ifac[i - 1] * inv[i]) % mod;
    }
    int T; cin >> T;
    while(T--) solve();
    return 0;
}

CF2161F SubMST

考虑所选点集的最小斯坦纳树(就是虚树),如果最小斯坦纳树上的点都在原点集中,那么贡献就是虚树上的边权和。
这一部分的贡献是 $ \sum_{(u, v)} (2^{siz_u} - 1)(2^{siz_v} - 1)$。
接着考虑虚点 \(s\) 不在原点集中的情况。本来一个点 \(u\) 连到 \(s\) 就行了,现在我们发现它还要多走一些连到最近的点集中的点 \(x\)。也就是说,对于虚树上所有与 \(u\) 相邻的点,除了离 \(u\) 最近的点 \(x\),都得多走 \(dis(u, x)\)。然后 \(dis(u, x)\) 在原树上本来也被贡献了一次,也就是多 \((deg_u - 2)dis(u, x)\),发现这样能保证联通而且最小。注意考察一下有重叠的情况,我们总是钦定一个方向走树上的原边,其余方向算多的,然后中转多次的情况就是拼起来,因此这么贡献没问题。
那么我们考虑枚举每个点 \(u\) 不在点集中,然后钦定深度 dp,把所有方案的 \(deg_u\) 之和算出来即可。因为直接写不好写,所以加了个容斥,算最小深度 \(\ge d\) 的。

Code ``` #include using namespace std; typedef long long ll; const int N = 5e3 + 5, mod = 1e9 + 7; vector e[N]; int n, siz[N]; ll pw[N], f[N], g[N], cnt[N], sm[N], ways[N], ans; void add(ll &x, ll y){ (x += y) %= mod; } void dfs(int u, int fa, int dep){ cnt[dep]++; for(int v : e[u]){ if(v != fa) dfs(v, u, dep + 1); } } void get(int u, int fa){ siz[u] = 1; for(int v : e[u]){ if(v != fa){ get(v, u); siz[u] += siz[v]; } } for(int v : e[u]){ if(v != fa) add(ans, (pw[siz[v]] - 1) * (pw[n - siz[v]] - 1) % mod); } } void solve(){ cin >> n; memset(e, 0, sizeof(e)); for(int i = 1; i < n; ++i){ int u, v; cin >> u >> v; e[u].emplace_back(v); e[v].emplace_back(u); } ans = 0; get(1, 0); for(int u = 1; u <= n; ++u){ for(int i = 1; i <= n + 1; ++i) f[i] = g[i] = sm[i] = 0, ways[i] = 1; for(int v : e[u]){ for(int d = 1; d <= n + 1; ++d) cnt[d] = 0; dfs(v, u, 1); for(int d = n; d >= 1; --d) cnt[d] += cnt[d + 1]; for(int d = 1; d <= n; ++d){ ll nwf = (f[d] + g[d] + ways[d]) * (pw[cnt[d]] - 1) % mod, nwg = (f[d] + g[d]) % mod; f[d] = nwf, g[d] = nwg; ways[d] = ways[d] * pw[cnt[d]] % mod; add(sm[d], pw[cnt[d]] - pw[cnt[d + 1]]); } } for(int d = 1; d <= n; ++d){ ll res = (f[d] + mod - f[d + 1] + g[d] + mod - g[d + 1]) % mod; add(ans, d * (res + (mod - 2) * (mod + ways[d] - ways[d + 1]) % mod + mod + sm[d])); } } cout << ans << '\n'; } int main(){ cin.tie(nullptr)->sync_with_stdio(0); pw[0] = 1; for(int i = 1; i <= 5e3; ++i) pw[i] = pw[i - 1] * 2 % mod; int T; cin >> T; while(T--) solve(); return 0; } ```
posted @ 2025-11-14 15:54  Hengsber  阅读(12)  评论(0)    收藏  举报