zzuacm2024年3月招新赛压轴题题解

难度:H = B < G

H.抽卡记录

题意:求一个数组的子序列,序列中上升转下降,下降转上升的不超过k次,且这个子序列不能存在相邻的两个元素,求子序列的最大长度。

题解:先看数据范围,n <= 1e3且k <= 10。如果你写过最长上升子序列这个板子就知道这题大概率是dp。

考虑f[i][j][k]:以i结尾的子序列,上升转下降,下降转上升的次数不超过j次,且最后是以上升结尾或者下降结尾(0表示上升,1表示下降)的子序列最大长度。

第一层循环直接遍历数组即可,第二层循环遍历上升转下降,下降转上升的次数(有种类似背包的思路),第三层循环遍历前i个数的下标。

dp方程看代码自行理解。

这题难点不在于dp的递推式推导,在于dp状态的设计(状态有点多,需要理清楚)。

#include <bits/stdc++.h>
using namespace std;
#define IOS ios::sync_with_stdio(false),cin.tie(nullptr),cout.tie(nullptr);
#define all(x) x.begin(),x.end()
#define PII pair<int, int>
#define x first
#define y second
#define ll long long
#define int long long
#define endl '\n'
using i64 = long long;

const int N = 1e3 + 10;
int f[N][N][2];
int a[N];
void solve() {
    int n, k; cin >> n >> k;
    for (int i = 1; i <= n; i++) cin >> a[i];

    int ans = 0;
    for (int i = 1; i <= n; i++) {
        for (int j = 0; j <= k; j++) {
            f[i][j][0] = f[i][j][1] = 1;
            for (int h = 1; h < i; h++) {
                if (a[i] > a[h]) {
                    f[i][j][1] = max(f[i][j][1], f[h][j][1] + 1);
                    if (j >= 1) {
                        f[i][j][1] = max(f[i][j][1], f[h][j - 1][0] + 1);
                    }
                }
                if (a[i] < a[h]) {
                    f[i][j][0] = max(f[i][j][0], f[h][j][0] + 1);
                    if (j >= 1) {
                        f[i][j][0] = max(f[i][j][0], f[h][j - 1][1] + 1);
                    }
                }
            }
            ans = max(ans, f[i][j][0]);
            ans = max(ans, f[i][j][1]);
        }
    }
    cout << ans << '\n';
}

signed main() {
    IOS;
    // int T; cin >> T;
    // while (T--)
        solve();

    return 0;
}

B.上课签到

题意:找一条从起点到终点的路径中,路径上的边权值和小于等于h,那么最大体力消耗就是这条路径上的最大点权值。现在求所有满足条件的最大体力消耗的最小值。

首先,对于最大...的最小,都可以考虑二分思路。其次,对于双权值问题,如果可以开二维,是可以跑二维的,但是明显这题开不了二维(会爆栈和TLE),并且有单调性质,所以考虑二分。

单调性质:设最大体力消耗是x,如果x满足条件(即可以找到一条路径,路径的权值和小于等于h并且点权值都小于等于x),那么大于x的也满足条件。所以考虑二分。

然后判断是不是满足跑一下dij即可。

#include <bits/stdc++.h>
using namespace std;
#define IOS ios::sync_with_stdio(false),cin.tie(nullptr),cout.tie(nullptr);
#define all(x) x.begin(),x.end()
#define PII pair<int, int>
#define x first
#define y second
#define ll long long
#define int long long
#define endl '\n'
using i64 = long long;

const int N = 1e4 + 10;
int a[N];

struct edge {
    int v, w;
};
vector<edge> e[N];
int n, m, st, ed, h;
int dist[N];
int vis[N];
bool check(int x) {
    for (int i = 1; i <= n; i++) {
        dist[i] = 1e18;
        vis[i] = 0;
    }
    priority_queue<PII, vector<PII>, greater<PII>> q;
    q.push({0, st});
    dist[st] = 0;

    if (a[st] > x) return false;

    while (q.size()) {
        auto now = q.top();
        q.pop();
        int u = now.y;
        if (vis[u]) continue;
        vis[u] = 1;
        for (auto t: e[u]) {
            int v = t.v, w = t.w;
            if (dist[v] > dist[u] + w && a[v] <= x) {
                dist[v] = dist[u] + w;
                q.push({dist[v], v});
            }
        }
    }

    return dist[ed] <= h;
}

void solve() {
    cin >> n >> m >> st >> ed >> h;
    for (int i = 1; i <= n; i++) {
        cin >> a[i];
    }
    
    for (int i = 1; i <= m; i++) {
        int u, v, w; cin >> u >> v >> w;
        e[u].push_back({v, w});
        e[v].push_back({u, w});
    }

    int l = 1, r = 1e7 + 10;
    while (l < r) {
        int mid = l + r >> 1;
        if (check(mid)) r = mid;
        else l = mid + 1;
    }

    if (l == 1e7 + 10) cout << -1 << '\n';
    else cout << l << '\n';
}


signed main() {
    IOS;
    // int T; cin >> T;
    // while (T--)
        solve();

    return 0;
}

G.猜数字

这题考点比较多,比较吃基本功。知识点:期望+区间dp + 前缀和优化 + 乘法逆元

对于期望问题,我一般令终态的状态作为起始状态,比如这题我让f[x][x]: 在[x,x]区间中选出x的期望。然后由小区间扩展到大区间,进行区间dp。

设区间f[i][j]: 区间[i,j]最终拿到x的期望。然后画一个数轴可以知道分三种情况套路。

1.如果拿的数是x,那么f[i][j] += 1 / (j - i + 1)

2.如果拿的数小于x,那么f[i][j] += ∑ (f[k][j] + 1)/ (j - i + 1) (k从i + 1 到 x)

3.如果拿的数大于x,那么f[i][j] += ∑(f[i][k] + 1) / (j - i + 1) (k从x到 j - 1)

对于∑这部分,可以发现对于同一个i和同一个j的状态来说可以提前保存下来,可以看我代码注释的部分,这是一个n^3的写法,更易理解。

最后就是乘法逆元,解决除法取模的问题,如果不知道可以去学习一下,这里我直接用的大数取余的模板。

#include <bits/stdc++.h>
using namespace std;
#define IOS ios::sync_with_stdio(false),cin.tie(nullptr),cout.tie(nullptr);
#define all(x) x.begin(),x.end()
#define PII pair<int, int>
#define x first
#define y second
#define ll long long
//#define int long long
#define endl '\n'
using i64 = long long;


template<const int T>
struct ModInt {
    const static int mod = T;
    int x;
    ModInt(int x = 0) : x(x % mod) {}
    ModInt(long long x) : x(int(x % mod)) {}
    int val() { return x; }
    ModInt operator + (const ModInt &a) const { int x0 = x + a.x; return ModInt(x0 < mod ? x0 : x0 - mod); }
    ModInt operator - (const ModInt &a) const { int x0 = x - a.x; return ModInt(x0 < 0 ? x0 + mod : x0); }
    ModInt operator * (const ModInt &a) const { return ModInt(1LL * x * a.x % mod); }
    ModInt operator / (const ModInt &a) const { return *this * a.inv(); }
    bool operator == (const ModInt &a) const { return x == a.x; };
    bool operator != (const ModInt &a) const { return x != a.x; };
    void operator += (const ModInt &a) { x += a.x; if (x >= mod) x -= mod; }
    void operator -= (const ModInt &a) { x -= a.x; if (x < 0) x += mod; }
    void operator *= (const ModInt &a) { x = 1LL * x * a.x % mod; }
    void operator /= (const ModInt &a) { *this = *this / a; }
    friend ModInt operator + (int y, const ModInt &a){ int x0 = y + a.x; return ModInt(x0 < mod ? x0 : x0 - mod); }
    friend ModInt operator - (int y, const ModInt &a){ int x0 = y - a.x; return ModInt(x0 < 0 ? x0 + mod : x0); }
    friend ModInt operator * (int y, const ModInt &a){ return ModInt(1LL * y * a.x % mod);}
    friend ModInt operator / (int y, const ModInt &a){ return ModInt(y) / a;}
    friend ostream &operator<<(ostream &os, const ModInt &a) { return os << a.x;}
    friend istream &operator>>(istream &is, ModInt &t){return is >> t.x;}

    ModInt pow(int64_t n) const {
        if(n == 0) return 1;
        ModInt res(1), mul(x);
        while(n){
            if (n & 1) res *= mul;
            mul *= mul;
            n >>= 1;
        }
        return res;
    }

    ModInt inv() const {
        int a = x, b = mod, u = 1, v = 0;
        while (b) {
            int t = a / b;
            a -= t * b; swap(a, b);
            u -= t * v; swap(u, v);
        }
        if (u < 0) u += mod;
        return u;
    }

};
using mint = ModInt<998244353>;
const int N = 5e3 + 10;
mint fact[N], invfact[N];
void init(){
    fact[0] = invfact[0] = 1;
    for(int i = 1; i < N; i ++) fact[i] = fact[i - 1] * i;
    invfact[N - 1] = fact[N - 1].inv();
    for(int i = N - 2; i; i --)
        invfact[i] = invfact[i + 1] * (i + 1);
}
inline mint C(int a, int b){
    if (a < 0 || b < 0 || a < b) return 0;
    return fact[a] * invfact[b] * invfact[a - b];
}


mint f[N][N];
mint s[N][N];

mint add[N];
void solve() {
    int n, x; cin >> n >> x;
    f[x][x] = 0;
    mint t2 = 0;
    for (int i = x; i >= 1; i--) {
        mint t1 = 0;
        for (int j = x; j <= n; j++) {
            f[i][j] += (t1 + j - x) / mint(j - i + 1);
            f[i][j] += (add[j] + x - i) / mint(j - i + 1);
            
            // for (int k = x; k <= j - 1; k++) {
            //     f[i][j] += (f[i][k] + 1) / (mint)(j - i + 1);
            // }
            // for (int k = i + 1; k <= x; k++) {
            //     f[i][j] += (f[k][j] + 1) / (mint)(j - i + 1);
            // }
            f[i][j] += 1 / (mint)(j - i + 1);    
            t1 += f[i][j];
            add[j] += f[i][j];
        }
    }
    cout << f[1][n] <<'\n';
}

signed main() {
    IOS;
    //init();
    // int T; cin >> T;
    // while (T--)
    // freopen("1.in", "r", stdin);
    // freopen("1.out", "w", stdout);
        solve();

    return 0;
} 

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值