NOIP模拟赛R8

A

绷,看错题导致自己被硬控 1 个小时。

其实也还好,题目问你最多可以被分成多少段,按照贪心不难想到要尽可能让每一段的和变小。

这个时候考虑前缀和 \(sum_i\),不难发现,如果要一段的和 \(\geq 0\),只需要让 $sum_i \geq sum_j $ 并且 \(i > j\)

于是考虑邪修方法从后往前看,发现只需要记录当前看到的所有数字的最小值的位置,然后统计个数即可。

#include <bits/stdc++.h>
using namespace std;
#define ll long long
#define ull unsigned long long
const int INF = 1e9+100;

void solve () {
    int n; cin >> n;
    vector <int> a(n+1), sum(n+1);
    for (int i = 1;i <= n;i++) {
        cin >> a[i];
        sum[i] = sum[i-1] + a[i];
    }
    vector <int> ans;
    int minn = INF;
    for (int i = n;i >= 1;i--) 
        if (sum[i] <= minn) ans.push_back(sum[i]), minn = sum[i];
    cout << ans.size() << "\n";
}

int main () {
    ios::sync_with_stdio(false);
    cin.tie(nullptr), cout.tie(nullptr);
    int _ = 1; 
    while (_--) solve();
    return 0;
}

B

又是被图论杀死的一天。

手推样例,不难发现最优解就是从一个点出发到所有点然后再从所有点返回,而且不是 bfs 那种一层层往下递进的,而是一个一个点地走,类似拓扑排序,事实上还真是拓扑。

假设一个子树的根节点为 \(u\),不难发现,如果从 \(u\) 开始拓扑和从叶子节点开始拓扑,都会有一样数量的答案,而这两种情况的答案可以随机组合,所以不难发现对于一个子树而言,他的答案就是拓扑序的数量的平方。

拓扑序的公式我们先记住吧,\(\frac{size_u!}{\prod\text{u的所有子树的大小}}\)

所以我们可以用 \(dp_i\) 表示以 \(i\) 为根的子树的 \(\prod\text{u的所有子树大小}\) 的逆元,然后对于每个节点去做一次换根 dp,转移方程挺好想,因为当转移到根节点的一个儿子时,以那个儿子为根的子树已经变为了整棵树,而这个逆元我们可以不动,先把再转移前搞得儿子的子树节点数量的逆元给他抵消,然后加上新的以原来根节点为根的子树的逆元即可(这里 \(T_u\) 表示 \(u\) 的子树集合):

\[dp_u = \frac{size_u!}{\prod_{i \in T_u}size_i} \]

\[dp_v = \frac{size_u!}{\prod_{i \in T_u}size_i} \times size_v \times \frac{1}{n-size_v} \]

#include <bits/stdc++.h>
using namespace std;
#define ll long long
#define ull unsigned long long
const ll MOD = 1e9+7;
const ll N = 2e5+1000;

ll fac[N], inc[N];

void solve () {
    ll n; cin >> n;
    vector <vector <ll>> E(n+1);
    vector <ll> dp(n+1), si(n+1);
    for (ll i = 1;i < n;i++) {
        ll a, b; cin >> a >> b;
        E[a].push_back(b), E[b].push_back(a);
    } 

    ll root = (n+1)/2;
    ll ans = 0;
    auto dfs = [&](auto self, ll fa, ll now) -> void {
        dp[now] = si[now] = 1;
        for (auto to : E[now]) {
            if (to == fa) continue;
            self(self, now, to);
            dp[now] = (dp[now]*dp[to])%MOD;
            si[now] += si[to];
        }
        dp[now] = (dp[now]*inc[si[now]]%MOD)%MOD;
    };
    auto count = [&](auto self, ll fa, ll now, ll tmp) -> void {
        ans = (ans + tmp*tmp%MOD) % MOD;
        ll _tmp = 0;
        for (auto to : E[now]) {
            if (to == fa) continue;
            _tmp = (tmp*inc[n-si[to]]%MOD*si[to])%MOD;
            self(self, now, to, _tmp);
        }
    };
    dfs(dfs, -1, root);
    count(count, -1, root, dp[root]*fac[n]%MOD);
    cout << ans%MOD << "\n";
}

int main () {
    ios::sync_with_stdio(false);
    cin.tie(nullptr), cout.tie(nullptr);
    fac[0] = fac[1] = inc[1] = 1;
    for (ll i = 2;i < N;i++) 
        fac[i] = (i*fac[i-1])%MOD,
        inc[i] = (MOD-MOD/i)*inc[MOD%i]%MOD;

    ll _ = 1; while (_--) solve();
    return 0;
}

C

逆天东西。

类似扫描线的思路?反正得先离散坐标,然后从左到右逐个删掉左侧的点,统计出包含任意点的矩形数量,需要用乘法原理。这里插播一条,假设有能分别包含两个点的 \(y\) 范围为 \([top_1, bottom_1], [top_2, bottom_2]\),不难发现如果假设底下的方案数为 \(cnt_i\),有结果:

\[cnt_i \times (top_1-bottom_1) + cnt_i \times (top2-bottom_2) \]

所以可以用乘法分配律,直接全部加上即可。

#include <bits/stdc++.h>
using namespace std;
#define ll long long
#define ull unsigned long long
#define pii pair <int, int> 
const int MOD = 1e9+7;

void solve () {
    int n, m, k; cin >> n >> m >> k;
    vector <pii> pos(k+10);
    vector <int> val(k+10);
    for (int i = 1;i <= k;i++) {
        cin >> pos[i].first >> pos[i].second;
        val[i] = pos[i].first;
    }
    sort(pos.begin()+1, pos.begin()+k+1,
        [&](pii x, pii y) { return (x.first != y.first ? x.first < y.first : x.second < y.second); } );
    sort(val.begin()+1, val.begin()+k+1);
    int tot = unique(val.begin()+1, val.begin()+1+k)-val.begin(); 
    // cout << tot << "\n";
    val[0] = 0, val[tot] = n+1;
    ll ans = 0;
    for (int i = 1;i < tot;i++) {
        ll tmp = 0;
        int pre = val[i] - val[i-1];
        int r = 1;
        while (r <= k && pos[r].first < val[i]) r++;
        set <int> s;
        s.insert(0), s.insert(m+1);
        for (int j = i;j < tot;j++) {
            int top, bot;
            for (;r <= k && pos[r].first == val[j];++r) {
                if (s.count(pos[r].second)) continue;
                set <int>::iterator now = s.lower_bound(pos[r].second);
                top = *now, bot = *(--now);
                tmp = (tmp + (ll)(top-pos[r].second)*(pos[r].second-bot)
                %MOD)%MOD;
                s.insert(pos[r].second);
            }
            int _tmp = val[j+1]-val[j];
            ans = (ans + tmp*_tmp%MOD*pre%MOD)%MOD;
        }
    }
    cout << ans << "\n";
}

int main () {
    ios::sync_with_stdio(false);
    cin.tie(nullptr), cout.tie(nullptr);
    int _ = 1; while (_--) solve();
    return 0;
}