Loading

2024杭电多校第10场

10

1001 LIS (hdu7541)

网络流总诱惑我去学一些根本做不出来的东西

由Dilworth定理,偏序集能划分成的最小全序集个数等于最大反链的长度。将 \((i, a_i)\) 视为一个二元组,定义 \((i,a_i)\leq(j,a_j)\) 当且仅当 \(i\leq j, a_i\geq a_j\),则 \(i\leq j\)\(a_i<a_j\) 时,两元素不可比,此时形成的反链为单调上升的子序列。题目要求最长上升子序列长度 \(\leq k\),即等价于序列中不交叉的不降子序列不多于 \(k\) 个。为了使删数代价最小,需要用 \(k\) 个不降子序列尽可能覆盖代价更大的元素。

考虑费用流建模,将 \(i\) 拆成 \(2i - 1, 2i\) 两个节点,连接源点 \(st\)\(2i-1\),汇点 \(ed\)\(2i\),由于每个点只能选择一次,流量为 \(1\),费用为 \(0\)\(2i-1\)\(2i\) 连边,流量为 \(1\),费用为 \(-b_i\),将最大覆盖价值转换为最小费用求解。对于 \(i < j\)\(a_i>a_j\),连接 \(2i\)\(2j-1\),流量为 \(1\),费用为 \(0\),若此处有流量经过、则代表它们在同一个子序列内。题目要求计算 \(1\leq k\leq n\) 的所有答案,因为所有边的流量限制为 \(1\),每次增广的流量也一定为 \(1\),在最短路计算、边权修改之后直接输出本次答案即可,不需要重新计算每个最大流下的最小费用。

#include <bits/stdc++.h>
#define ll long long
using namespace std;
typedef pair <int, int> P;
const int N = 510;
const ll INF = 1e12;
int n, a[N], b[N];
int st, ed;
struct edge{
    int t, w, c, r;
};
vector <edge> v[N * 2];
void Clear() {
    for(int i = 0; i <= ed; i++) {
        v[i].clear();
    }
}
void add(int f, int t, int w, int c) {
    v[f].push_back({t, w, c, (int)v[t].size()});
    v[t].push_back({f, 0, -c, (int)v[f].size() - 1});
}
ll d[N * 2], h[N * 2];
int pre[N * 2], id[N * 2];
// Primal-Dual 原始对偶算法
void dijkstra() {
    fill(d, d + ed + 1, INF);
    d[st] = 0;
    priority_queue <P, vector<P>, greater<P> > q;
    q.push(P(d[st], st));
    while(!q.empty()) {
        int x = q.top().first, y = q.top().second;
        q.pop();
        if(d[y] < x) continue;
        for(int i = 0; i < v[y].size(); i++) {
            int t = v[y][i].t, w = v[y][i].w, c = v[y][i].c;
            if(w > 0 && d[t] > d[y] + c + h[y] - h[t]) {
                d[t] = d[y] + c + h[y] - h[t];
                pre[t] = y;
                id[t] = i;
                q.push(P(d[t], t));
            }
        }
    }
}
int ans;
void mcf() {
    dijkstra();
    for(int i = st; i <= ed; i++) {
        h[i] += d[i];
    }
    for(int i = ed; i != st; i = pre[i]) {
        edge &e = v[pre[i]][id[i]];
        ans += e.c;
        e.w--;
        v[i][e.r].w++;
    }
    // 一次增广后直接返回(实测每次重新算就会TLE)
}
void solve() {
    Clear();
    scanf("%d", &n);
    for(int i = 1; i <= n; i++) {
        scanf("%d", &a[i]);
    }
    int sum = 0;
    for(int i = 1; i <= n; i++) {
        scanf("%d", &b[i]);
        sum += b[i];
    }
    fill(h, h + ed + 1, 0);
    st = 0, ed = n * 2 + 1;
    for(int i = 1; i <= n; i++) {
        // 预处理初始势能
        h[i * 2 - 1] = h[i * 2 - 2];
        h[i * 2] = h[i * 2 - 1] - b[i];
        add(st, i * 2 - 1, 1, 0);
        add(i * 2 - 1, i * 2, 1, -b[i]);
        add(i * 2, ed, 1, 0);
    }
    h[ed] = h[n * 2];
    for(int i = 1; i <= n; i++) {
        for(int j = i + 1; j <= n; j++) {
            if(a[j] < a[i]) {
                add(i * 2, j * 2 - 1, 1, 0);
            }
        }
    }
    ans = 0;
    for(int f = 1; f <= n; f++) {
        mcf();
        // ans为最大覆盖价值的相反数
        printf("%d ", sum + ans);
    }
    printf("\n");
}
int main() {
    int t;
    scanf("%d", &t);
    while(t--) {
        solve();
    }
    system("pause");
    return 0;
}

1002 scenery (hdu7542)

由于 \(l\) 序列不增、 \(r\) 序列不降,每处景色的拍摄安排在可选时间的开始/结束位置显然是最优的。设 \(dp[i][j]\) 表示(从后往前)考虑到第 \(i\) 处景色、可选时间从 \(j\) 开始的最晚结束位置,则转移方程:

\(dp[i][max(l_i, j) + t_i] = max(dp[i][max(l_i, j) + t_i], dp[i - 1][j])\) (开始位置)

\(dp[i][j] = max(dp[i][j], min(dp[i - 1][j], r_i + 1) - t_i)\) (结束位置)

预先将所有状态赋为不合法情况 \(-1\),最后检查 \(dp[1][j]\) 中是否有合法情况即可。

    memset(dp, -1, sizeof(dp));
    int it = 1;
    dp[1][0] = m + 1;
    for(int i = n; i; i--) {
        it ^= 1;
        for(int j = 0; j <= m; j++) {
            dp[it][j] = -1;
        }
        for(int j = 0; j <= m; j++) {
            int l = max(a[i].l - 1, j) + a[i].t;
            if(l > a[i].r || l >= dp[it ^ 1][j]) continue;
            dp[it][l] = max(dp[it][l], dp[it ^ 1][j]);
        }
        for(int j = 0; j <= m; j++) {
            int r = min(dp[it ^ 1][j], a[i].r + 1) - a[i].t;
            if(r < a[i].l || r <= j) continue;
            dp[it][j] = max(dp[it][j], r);
        }
    }
    bool flag = 0;
    for(int j = 0; j <= m; j++) {
        if(dp[it][j] >= 0) {
            flag = 1;
            break;
        }
    }
    if(flag) printf("YES\n");
    else printf("NO\n");

居然能在赛时写对dp,挺难得的)

1008 SunBian (hdu7548)

对于 \(n\) 为偶数的情况,除非Alice能够一次将所有笋变成横向,即 \(k=n\),否则无论她如何操作,Bob都可以在对称位置模仿操作、使自己必胜。\(n\) 为奇数时,若 \(1<k<n\),Bob可将整个环隔开成为两个对称的部分、同理模仿操作;\(k=1\)\(n\) 时则Bob必败。

1010 A+B Problem (hdu7550)

一个小规律:\(ans_i\) 的二进制最低位与 \(ans_{i - 1}\) 无关,对于确定的 \(a_i,b_i\)\(ans_i\space \&\space 1\)为定值。而其他每位上的取值只与 \(a_i,b_i\) 以及 \(ans_{i - 1}\) 的较低位有关,因此确定最低位后、可通过递推求出完整的 \(ans_i\),即最终答案唯一。

    for(int i = 1; i <= q; i++) {
        scanf("%lld%lld", &a[i], &b[i]);
        c[i] = (a[i] ^ b[i]) & 1;
    }
    c[0] = c[q];
    for(int z = 1; z < 32; z++) {
        for(int i = 1; i <= q; i++) {
            ll y = (a[i] ^ c[i - 1]) + (b[i] ^ c[i - 1]);
            c[i] += (y & (1ll << z));
        }
        c[0] = c[q];
    }
    for(int i = 1; i <= q; i++) {
        printf("%lld\n", c[i]);
    }

另有:写1011的时候假设这位同学每次比赛都不幸爆零、排他前面的人都AK,后来发现题解真这么写的,好一道吉利的题目()
posted @ 2024-08-22 19:49  Aderose_yr  阅读(72)  评论(0编辑  收藏  举报