20240329

T1

NFLSOJ P60000 镜中故我

通过找规律,能够发现序列一定时答案随 \(k\) 的变化是杨辉三角的某一行,再乘个 \(1\)\(-1\) 的系数。所以只需要知道到底是哪一行。算出 \(k = 1\) 时的答案即可。能够发现 \(k= 1\) 时的答案就是原序列后缀最小值的个数。所以直接组合数计算即可。

证明:考虑一对逆序 \(x, y\),对于每一个选了 \(x\) 而不选 \(y\) 的选择,我们都可以通过选上 \(y\) 来给序列的长度 \(+1\) 而保持序列合法。所以删去 \(x\) 对答案无影响。所以只需要保留所有原序列的后缀最小值,然后在这序列中任选出 \(k\) 个数。所以就是组合数。

代码
#include <iostream>
using namespace std;
const int P = 998244353;
long long fac[10000005], ifac[10000005], inv[10000005];
int a[10000005];
void Cpre(int n) {
    fac[0] = ifac[0] = inv[0] = fac[1] = ifac[1] = inv[1] = 1;
    for (int i = 2; i <= n; i++) {
        fac[i] = fac[i - 1] * i % P;
        inv[i] = (P - P / i) * inv[P % i] % P;
        ifac[i] = ifac[i - 1] * inv[i] % P;
    }
}
int main() {
    ios::sync_with_stdio(false);
    cin.tie(0);
    cout.tie(0);
    int n, k;
    cin >> n >> k;
    for (int i = 1; i <= n; i++) cin >> a[i];
    Cpre(n);
    int b = 0;
    int smn = 2147483647;
    for (int i = n; i; i--) {
        smn = min(smn, a[i]);
        b += (a[i] <= smn);
    }
    cout << (fac[b] * ifac[k] % P * ifac[b - k] % P * ((k & 1) ? -1 : 1) + P) % P << "\n";
    return 0;
}

T2

NFLSOJ P60001 K4

这个故事告诉我们,\(\frac{n^2}{\omega}\) 能艹过 \(1e5\)

结论:将无向图的所有边从度数小的往度数大的连,则每个点的出度不会超过根号级别。

感性理解一下,每个点连向的点度数都大于等于自身度数,如果出边超过了根号,则总度数就要爆炸了。所以不会超。

所以只需要枚举第一个点 \(x\),然后枚举其连向的所有点 \(y\) 并打标记,再枚举 \(y\) 连向的所有点 \(z\),如果 \(z\) 有标记,则在 bitset 中记录 \(y \rightarrow z\)。然后再枚举 \(y\),枚举 \(z\),如果 \(z\) 有标记,就把 \(y\)\(z\) 的 bitset 与起来,数一下 \(1\) 的个数,计入答案即可。

代码
#include <iostream>
#include <bitset>
#include <vector>
using namespace std;
vector<int> vec[100005];
int head[100005], nxt[200005], to[200005], ecnt;
void add(int u, int v) { to[++ecnt] = v, nxt[ecnt] = head[u], head[u] = ecnt; }
bitset<505> f[505];
int n, m;
int lim;
int id[100005], icnt;
int u[100005], v[100005];
int deg[100005];
int main() {
    ios::sync_with_stdio(false);
    cin.tie(0);
    cout.tie(0);
    cin >> n >> m;
    for (int i = 1; i <= m; i++) {
        cin >> u[i] >> v[i];
        deg[u[i]]++, deg[v[i]]++;
    }
    for (int i = 1; i <= m; i++) {
        if (deg[u[i]] > deg[v[i]])
            swap(u[i], v[i]);
        vec[u[i]].emplace_back(v[i]);
    }
    int ans = 0;
    for (int i = 1; i <= n; i++) {
        icnt = 0;
        for (int v : vec[i]) id[v] = ++icnt, f[icnt].reset();
        for (int j : vec[i]) 
            for (int k : vec[j]) 
                if (id[k]) 
                    f[id[j]][id[k]] = 1;
        for (int j : vec[i]) 
            for (int k : vec[j]) 
                if (id[k]) 
                    ans += (f[id[j]] & f[id[k]]).count();
        for (int v : vec[i]) id[v] = 0;
    }
    cout << "2021 06 52 " << ans << "\n";
    return 0;
}

T3

NFLSOJ P60002 斜二进制

首先有 \(dp[i][j]\) 表示前 \(i\) 个数,是否能选出和若干数为 \(j\)。使用 bitset 优化可以做到 \(O(\frac{nS}{\omega})\)。发现接下来做不了一点了,但是发现 \(1\) 的个数很多,所以设一个对 \(1\) 更友好的状态:\(dp[i - j][j]\) 表示和减去个数为 \(i - j\),选了 \(j\) 个数的可行性。可以发现 \(i - j\) 固定时第二维里会形成若干区间,而这区间个数最多是 4 个。因为每个区间的长度至少为 \(cnt_1 + 1\),如果有五个,总长度就是 \(5cnt_1 + 5\),而 \(cnt_1\) 的个数限制保证了这个总长度会爆炸,所以至多就是 \(4\) 个区间。转移跟 bitset 做法一样,只是把或换成合并区间。

代码
#include <iostream>
#include <vector>
#include <algorithm>
#define int long long
using namespace std;
int cnt[200005];
vector<pair<int, int> > vec[400005];
int L, R;
void Merge(vector<pair<int, int> >& v1, vector<pair<int, int> > v2, int dy) {
    vector<pair<int, int> > tmp;
    for (auto v : v1) tmp.emplace_back(v.first, v.second);
    for (auto v : v2) tmp.emplace_back(v.first + dy, v.second + dy);
    sort(tmp.begin(), tmp.end());
    int tl, c = 0;
    v1.clear();
    for (auto v : tmp) {
        if (v1.empty() || v1.back().second < v.first) 
            v1.emplace_back(v);
        else if (v1.back().second < v.second) 
            v1.back().second = v.second;
    }
}
void work(int dx, int dy) {
    if (dx > 0) {
        for (int i = R; i >= L; i--) Merge(vec[i + dx], vec[i], dy);
        R += dx;
    } else {
        for (int i = L; i <= R; i++) Merge(vec[i + dx], vec[i], dy);
        L += dx;
    }
}
signed main() {
    freopen("skew.in", "r", stdin);
    freopen("skew.out", "w", stdout);
    int n, S;
    cin >> n >> S;
    for (int i = 1; i <= n; i++) {
        int x;
        cin >> x;
        cnt[x]++;
    }
    vec[L = R = 200000].emplace_back(make_pair(0, cnt[1]));
    for (int i = 0; i <= S; i++) {
        if (i == 1 || !cnt[i]) 
            continue;
        for (int j = 1; j <= cnt[i]; j <<= 1) {
            work(j * i - j, j * i), cnt[i] -= j;
        }
        if (cnt[i]) 
            work(cnt[i] * i - cnt[i], cnt[i] * i);
    }
    int ans = 0;
    for (int i = L; i <= R; i++) {
        for (auto v : vec[i]) 
            ans += v.second - v.first + 1;
    }
    cout << ans << "\n";
    return 0;
}
</details>
posted @ 2024-03-30 16:20  forgotmyhandle  阅读(23)  评论(0)    收藏  举报