首页 > 其他分享 >20240908

20240908

时间:2024-09-08 14:51:50浏览次数:7  
标签:dist 20240908 int res mid ecnt dp

T1

NFLSOJ 服装

考虑逐次确定每个人。从前往后枚举,维护一个栈表示当前前缀里所有颜色不同的人。加入一个人的时候和栈里的东西并起来查,查出来是栈大小说明这个颜色出现过,在栈里二分找到那个颜色相同的人。否则当前颜色没出现过,将其加入栈。

代码
#include <iostream>
using namespace std;
int n;
int stk[155], sz;
int Ask(int l, int r, int x) {
    cout << r - l + 2 << " ";
    for (int i = l; i <= r; i++) cout << stk[i] << " ";
    cout << x << endl;
    cin >> x;
    return x;
}
int clr[155], ccnt;
int main() {
    cin >> n;
    stk[++sz] = 1;
    clr[1] = 1;
    for (int i = 2; i <= n; i++) {
        if (Ask(1, sz, i) == sz) {
            int l = 1, r = sz, mid;
            while (l < r) {
                mid = (l + r) >> 1;
                if (Ask(l, mid, i) == mid - l + 1) 
                    r = mid;
                else 
                    l = mid + 1;
            }
            clr[i] = clr[stk[l]];
        } else {
            stk[++sz] = i;
            clr[i] = sz;
        }
    }
    cout << 0 << " ";
    for (int i = 1; i <= n; i++) cout << clr[i] << " ";
    cout << endl;
    return 0;
}

T2

NFLSOJ 跑步

考虑图确定的 dp 就是 \(dp[i]\) 表示从 \(i\) 出发的合法路径条数的奇偶性。这 dp 只有 \(0 / 1\),考虑 dp 套 dp。设 \(dp[i][x][y][z][w]\) 表示从后往前处理到 \(i\),后面有 \(x\) 个黑点使得从它出发的路径条数为奇数,有 \(y\) 个黑点使得出发的条数是偶数,后两维同理。发现如果一个点出发的路径条数是偶数,那这个点就不会有贡献,因为任何一条路径如果走到他,往后的方案数就是偶数条,因此就相当于没贡献。于是只需要记三维,转移考虑当前点能否为某颜色点,然后考虑当前点是否作为有贡献点。设在 \(n\) 个数中选出偶 / 奇数个的方案数为 \(c(n, 0 / 1)\)。设当前点为黑点,则之后所有黑点和偶数白点都没有贡献,向它们的边可连可不连,转移即为 \(dp[i][x][y] = dp[x - 1][y] \times c(x - 1, 0) \times 2^{n - i - x + 1} + dp[i + 1][x][y] \times c(x - 1, 1) \times 2^{n - i - x + 1}\)。根据二项式定理,\(n \ne 0\) 时,\(c(n, 0) = c(n, 1) = 2^{n - 1}\),发现 \(x\) 刚好抵消,则不需要记录 \(x\) 的值,只需要其是否为 \(0\) 及其奇偶性(用来判定答案),于是就没了。

代码
#include <iostream>
#define int long long
using namespace std;
const int P = 998244353;
inline void Madd(int& x, int y) { (x += y) >= P ? (x -= P) : 0; }
int n;
int a[200005];
int dp[200005][3][3];
int pw[200005];
signed main() {
    freopen("life.in", "r", stdin);
    freopen("life.out", "w", stdout);
    cin >> n;
    pw[0] = 1;
    for (int i = 1; i <= n; i++) cin >> a[i], pw[i] = (pw[i - 1] << 1) % P;
    dp[n + 1][2][2] = 1;
    for (int i = n; i; i--) {
        for (int x : { 0, 1, 2 }) {
            for (int y : { 0, 1, 2 }) {
                // this vertex white
                if (a[i] != 1) {
                    if (y != 2) {
                        Madd(dp[i][x][y], dp[i + 1][x][y] * pw[n - i - 1] % P);
                        Madd(dp[i][(x + 1) & 1][y], dp[i + 1][x][y] * pw[n - i - 1] % P);
                    } else 
                        Madd(dp[i][(x + 1) & 1][y], dp[i + 1][x][y] * pw[n - i] % P);
                }
                // this vertex black
                if (a[i] != 0) {
                    if (x != 2) {
                        Madd(dp[i][x][y], dp[i + 1][x][y] * pw[n - i - 1] % P);
                        Madd(dp[i][x][(y + 1) & 1], dp[i + 1][x][y] * pw[n - i - 1] % P);
                    } else 
                        Madd(dp[i][x][(y + 1) & 1], dp[i + 1][x][y] * pw[n - i] % P);
                }
            }
        }
    }
    cout << (dp[1][0][1] + dp[1][1][0] + dp[1][1][2] + dp[1][2][1]) % P << "\n";
    return 0;
}

T3

NFLSOJ 排列计数机

权值即为最大值个数。\(f[i][j][k]\) 表示到 \(i\),最大值为 \(j\),所有方案权值的 \(k\) 次方和。对于 \(i\),只有 \(f[i][a[i]]\) 需要特殊转移,其他位置转移平凡。考虑维护给所有方案的权值 \(+1\) 后的 \(k\) 次方和。二项式定理展开一下,发现只需要组合数和在较小的 \(k\) 时的 dp 值。手推一下 dp 式子,发现只需要线段树支持区间乘以 \(2\) 和区间求和以及单点加即可。对每个 \(k \le m\) 都开一棵线段树维护。由于转移还需要方案数,再开一棵线段树维护方案数即可。

代码
#include <iostream>
#define int long long
using namespace std;
const int P = 1000000007;
inline void Madd(int& x, int y) { (x += y) >= P ? (x -= P) : 0; }
int fac[200005], inv[200005], ifac[200005];
void Cpre(int x) {
    fac[0] = fac[1] = inv[0] = inv[1] = ifac[0] = ifac[1] = 1;
    for (int i = 2; i <= x; 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 C(int n, int m) { return (n < 0 || m < 0 || n < m) ? 0 : fac[n] * ifac[m] % P * ifac[n - m] % P; }
int n, m;
int a[100005];
struct Segment_Tree {
    int tgm[400005];
    int s[400005];
    void tag(int o, int v) {
        s[o] = s[o] * v % P;
        tgm[o] = tgm[o] * v % P;
    }
    void pushdown(int o) {
        if (tgm[o] == 1) 
            return;
        tag(o << 1, tgm[o]);
        tag(o << 1 | 1, tgm[o]);
        tgm[o] = 1;
    }
    void pushup(int o) { s[o] = (s[o << 1] + s[o << 1 | 1]) % P; }
    void Build(int o, int l, int r) {
        tgm[o] = 1;
        if (l == r) 
            return;
        int mid = (l + r) >> 1;
        Build(o << 1, l, mid);
        Build(o << 1 | 1, mid + 1, r);
    }
    void Multi(int o, int l, int r, int L, int R, int v) {
        if (L <= l && r <= R) {
            tag(o, v);
            return;
        }
        pushdown(o);
        int mid = (l + r) >> 1;
        if (L <= mid) 
            Multi(o << 1, l, mid, L, R, v);
        if (R > mid) 
            Multi(o << 1 | 1, mid + 1, r, L, R, v);
        pushup(o);
    }
    void Add(int o, int l, int r, int x, int v) {
        if (l == r) {
            s[o] = (s[o] + v) % P;
            return;
        }
        pushdown(o);
        int mid = (l + r) >> 1;
        if (x <= mid) 
            Add(o << 1, l, mid, x, v);
        else 
            Add(o << 1 | 1, mid + 1, r, x, v);
        pushup(o);
    }
    int Query(int o, int l, int r, int L, int R) {
        if (L <= l && r <= R) 
            return s[o];
        pushdown(o);
        int mid = (l + r) >> 1;
        if (R <= mid) 
            return Query(o << 1, l, mid, L, R);
        if (L > mid) 
            return Query(o << 1 | 1, mid + 1, r, L, R);
        return (Query(o << 1, l, mid, L, R) + Query(o << 1 | 1, mid + 1, r, L, R)) % P;
    }
} f[25], g;
int tmp[25];
signed main() {
    cin >> n >> m;
    for (int i = 1; i <= n; i++) cin >> a[i];
    Cpre(m);
    for (int i = 1; i <= m; i++) f[i].Build(1, 0, n);
    g.Build(1, 0, n);
    g.Add(1, 0, n, 0, 1);
    for (int i = 1; i <= n; i++) {
        for (int k = 1; k <= m; k++) f[k].Multi(1, 0, n, a[i] + 1, n, 2);
        for (int k = 1; k <= m; k++) tmp[k] = f[k].Query(1, 0, n, 0, a[i] - 1);
        for (int k = 1; k <= m; k++) {
            int t = 0;
            for (int x = 1; x <= k; x++) 
                t = (t + C(k, x) * tmp[x] % P);
            f[k].Add(1, 0, n, a[i], t);
            f[k].Add(1, 0, n, a[i], g.Query(1, 0, n, 0, a[i] - 1));
        }
        g.Multi(1, 0, n, a[i] + 1, n, 2);
        g.Add(1, 0, n, a[i], g.Query(1, 0, n, 0, a[i] - 1));
    }
    cout << f[m].Query(1, 0, n, 0, n) << "\n";
    return 0;
}

T4

NFLSOJ 安装软件包

线性规划,转对偶,变网络流,即为给定 DAG,要选出若干路径,每个点被覆盖一次有收益,每选出一条路径有代价。一个点有覆盖次数上限,若超过上限则不再有收益。拆点,求一个最大费用可行流即可。

代码
#include <iostream>
#include <string.h>
#include <queue>
#include <map>
#define int long long
using namespace std;
const int inf = 0x7ffffffffff;
const int N = 500, M = 2000000;
struct Flow {
    int n, S, T, c;
    int head[N + 5], nxt[M + 5], to[M + 5], res[M + 5], cst[M + 5], ecnt = 1;
    int cur[N + 5], dist[N + 5];
    bool inq[N + 5], vis[N + 5];
    queue<int> q;
    void add(int u, int v, int x, int y) {
        to[++ecnt] = v, nxt[ecnt] = head[u], head[u] = ecnt, res[ecnt] = x, cst[ecnt] = y;
        to[++ecnt] = u, nxt[ecnt] = head[v], head[v] = ecnt, res[ecnt] = 0, cst[ecnt] = -y;
    }
    bool spfa() {
        for (int i = 1; i <= n; i++) dist[i] = inf;
        dist[S] = 0;
        q.push(S);
        while (!q.empty()) {
            int x = q.front();
            q.pop();
            inq[x] = 0;
            for (int i = head[x]; i; i = nxt[i]) {
                int v = to[i];
                if (dist[v] > dist[x] + cst[i] && res[i]) {
                    dist[v] = dist[x] + cst[i];
                    if (!inq[v]) {
                        q.push(v);
                        inq[v] = 1;
                    }
                }
            }
        }
        return (dist[T] < 0);
    }
    int dfs(int x, int flow) {
        if (x == T) 
            return flow;
        int ret = 0;
        vis[x] = 1;
        for (int i = cur[x]; i && flow; i = nxt[i]) {
            cur[x]= i;
            int v = to[i];
            if (!vis[v] && dist[v] == dist[x] + cst[i] && res[i]) {
                int tmp = dfs(v, min(flow, res[i]));
                if (tmp) {
                    res[i] -= tmp;
                    res[i ^ 1] += tmp;
                    flow -= tmp;
                    ret += tmp;
                    c += tmp * cst[i];
                }
            }
        }
        if (!ret) 
            dist[x] = inf;
        vis[x] = 0;
        return ret;
    }
    int MinCost() {
        c = 0;
        while (spfa()) {
            for (int i = 1; i <= n; i++) cur[i] = head[i];
            dfs(S, inf);
        }
        return c;
    }
    void clear() {
        memset(head, 0, sizeof head);
        ecnt = 1;
    }
} g;
int n, m, w;
int t[60], c[60];
int eu[405], ev[405];
bool chk(int K) {
    g.clear();
    g.S = n * 2 + 1, g.n = g.T = n * 2 + 2;
    for (int i = 1; i <= n; i++) {
        g.add(g.S, i, inf, K);
        g.add(i + n, g.T, inf, 0);
        g.add(i, i + n, c[i], -t[i]);
        g.add(i, i + n, inf, 0);
    }
    for (int i = 1; i <= m; i++) g.add(eu[i] + n, ev[i], inf, 0);
    return -g.MinCost() <= w;
}
signed main() {
    freopen("soft.in", "r", stdin);
    freopen("soft.out", "w", stdout);
    cin >> n >> m >> w;
    for (int i = 1; i <= n; i++) cin >> t[i];
    for (int i = 1; i <= n; i++) cin >> c[i];
    for (int i = 1; i <= m; i++) cin >> eu[i] >> ev[i];
    int l = 0, r = 100000, mid, ans = 0;
    while (l <= r) {
        mid= (l + r) >> 1;
        if (chk(mid)) 
            ans = mid, r = mid - 1;
        else 
            l = mid + 1;
    }
    cout << ans << "\n";
    return 0;
}

标签:dist,20240908,int,res,mid,ecnt,dp
From: https://www.cnblogs.com/forgotmyhandle/p/18402850

相关文章