LGR 211 Div.3 题解

0.前言

没想到 AK 了基础赛。写篇比赛题解纪念一下。

T1

判断 \(l_i-r_{i-1}\ge t\) 即可,如果满足,\(ans\gets ans+l_i-r_{i-1}-t\)

T2

暴力做即可,复杂度 \(O(n^2m)\)

#include <bits/stdc++.h>
using namespace std;
int n,m,ans;
int f[503][503];
int v[503];
int a[503];
int main() {
#ifdef LOCAL
    freopen("D:/codes/exe/a.in","r",stdin);
    freopen("D:/codes/exe/a.out","w",stdout);
#endif
    cin >> n >> m;
    for (int i = 1; i <= n; i++) {
        for (int j = 1; j <= m; j++) {
            cin >> f[i][j];
        }
    }
    for (int i = 1; i <= n; i++) {
        for (int j = 1; j <= n; j++) {
            if (i == j) continue;
            int cnt = 0;
            for (int k = 1; k <= m; k++) {
                if (f[i][k] == f[j][k] && f[i][k] == 1) ++cnt;
            }
            if (i == 1) a[j] = cnt;
            if (cnt > v[i]) v[i] = cnt;
        }
    }
    for (int i = 1; i <= m; i++) {
        if (f[1][i]) continue;
        int tmp = 0;
        for (int j = 1; j <= n; j++) {
            if (j == 1) continue;
            if (f[j][i]) {
                if (a[j]+1 >= v[j]) ++tmp;
                else if (a[j] == v[j]) ++tmp;
            }else{
                if (a[j] == v[j]) ++tmp;
            }
            
        }
        ans = max(tmp,ans);
    }
    cout << ans << '\n';
    return 0;
}

T3

很典的一个 trick 啊,看清楚交换的是相邻两个字母。

先考虑如何计算子序列。

我们只需要扫一遍序列,求出每一个 a 左边有几个 v,右边有几个 n。根据乘法原理,把这两个数乘起来就是以这个 a 为中心的所有 van 子序列个数。把每个 a 的贡献都求出来加和即可。

两个树状数组就能维护,一个维护 v,一个维护 n

for (int i = 1; i <= n; i++) {
	if (s[i] == 'v') modifyv(i,1);
	if (s[i] == 'n') modifyn(i,1);
}
for (int i = 1; i <= n; i++) {
	if (s[i] == 'a') ans += queryv(i-1)*(queryn(n)-queryn(i));
}

然后考虑交换相邻字母怎么做。

首先,如果两个字母相同,显然没有任何意义。

然后,对于 vn 互换的类型,对答案没有影响,只需要修改树状数组即可。

最后,考虑如果有 a 怎么办。这里用 va 换成 av 举例:

发现这相当于破坏了所有以这个 v 开始的以这个 a 为中心的所有子序列,而满足这个条件的子序列数就是右侧 n 个数。

这样只需要减去这个数,修改树状数组即可。

其他情况类似,这里就不一一列举了。

时间复杂度为 \(O(n\log n+m\log n)\)。当然可以考虑使用线段树,但是在本题的 \(10^6\) 数据范围下不保证可以通过。

输入输出量较大,记得关同步流,同时别忘了 long long

#include <bits/stdc++.h>
#define int long long
using namespace std;
const int maxn = 1e6+40;
int n,m,ans;
string s;
int tv[maxn],tn[maxn];
inline int lowbit(int x) {
    return x & (-x);
}
inline void modifyv(int p,int x) {
    assert(p);
    while (p <= n) {
        tv[p] += x;
        p += lowbit(p);
    }
}
inline void modifyn(int p,int x) {
    assert(p);
    while (p <= n) {
        tn[p] += x;
        p += lowbit(p);
    }
}
inline int queryv(int p) {
    int ret = 0;
    while (p > 0) {
        ret += tv[p];
        p -= lowbit(p);
    }
    return ret;
}
inline int queryn(int p) {
    int ret = 0;
    while (p > 0) {
        ret += tn[p];
        p -= lowbit(p);
    }
    return ret;
}
signed main() {
#ifdef LOCAL
    freopen("D:/codes/exe/a.in","r",stdin);
    freopen("D:/codes/exe/a.out","w",stdout);
#endif
    ios::sync_with_stdio(0);
    cin.tie(0),cout.tie(0);
    cin >> n >> m >> s;
    s = ' ' + s;
    for (int i = 1; i <= n; i++) {
        if (s[i] == 'v') modifyv(i,1);
        if (s[i] == 'n') modifyn(i,1);
    }
    for (int i = 1; i <= n; i++) {
        if (s[i] == 'a') ans += queryv(i-1)*(queryn(n)-queryn(i));
    }
    while (m--) {
        int x;
        cin >> x;
        if (s[x] == s[x+1]) {
            cout << ans << '\n';
            continue;
        }
        if (s[x] == 'v') {
            if (s[x+1] == 'n') {
                modifyv(x,-1);
                modifyv(x+1,1);
                modifyn(x,1);
                modifyn(x+1,-1);
            }else{
                ans -= queryn(n)-queryn(x+1);
                modifyv(x,-1);
                modifyv(x+1,1);
            }
        }
        if (s[x] == 'n') {
            if (s[x+1] == 'v') {
                modifyv(x,1);
                modifyv(x+1,-1);
                modifyn(x,-1);
                modifyn(x+1,1);
            }else{
                ans += queryv(x);
                modifyn(x,-1);
                modifyn(x+1,1);
            }
        }
        if (s[x] == 'a') {
            if (s[x+1] == 'v') {
                ans += queryn(n)-queryn(x+1);
                modifyv(x,1);
                modifyv(x+1,-1);
            }else{
                ans -= queryv(x);
                modifyn(x,1);
                modifyn(x+1,-1);
            }
        }
        swap(s[x],s[x+1]);
        cout << ans << '\n';
    }
    return 0;
}

T4

没想到居然做出来了线段树优化 dp。

简要的题解:

发现没有后效性,考虑 dp。

\(f_i\) 表示前 \(i\) 分钟能得到的最大兴奋值(包括 \(i\)),状态转移方程如下:

\[f_i=\max^{i-k+1}_{j=0}\left(f_j+\max^{n}_{id=1}[l_{id}\le j+1][r_{id}\ge j+k]\times w_{id}\right) \]

\([]\) 是艾弗森括号。后面那一坨的意思就是,从所有满足在店时间在 \([j+1,j+k]\) 内的人中选出最大的 \(w\) 加到 \(f_j\) 上。最后取 \(\max\) 即可。

第二个 \(\max\) 可以使用线段树离线求出,前面这个 \(\max\) 用优先队列优化即可,复杂度 \(O(n\log n)\)

详细说明:

题意不难理解。

注意题目有点恶心的地方在于,区间长度是 \(r_i-l_i+1\),并非 \(r_i-l_i\),可以从第二个样例看出。

同时如果这个时刻作为了一局游戏结束点,则他就不能作为新一局游戏的开始点。

发现没有后效性,考虑 dp。

\(f_i\) 表示前 \(i\) 分钟能得到的最大兴奋值(包括 \(i\)),状态转移方程如下:

\[f_i=\max^{i-k+1}_{j=0}\left(f_j+\max^{n}_{id=1}[l_{id}\le j+1][r_{id}\ge j+k]\times w_{id}\right) \]

\([]\) 是艾弗森括号。

这一串的意思就是,我们先考虑 \(i\) 怎么转移。我们可以选择之前的任何一个时刻 \(j\),然后在 \(j+1\) 时刻开始一局游戏。显然,我们要选在店时间符合条件的最大的 \(w\)。后面那一坨的意思就是,从所有满足在店时间在 \([j+1,j+k]\) 内的人中选出最大的 \(w\) 加到 \(f_j\) 上。最后取 \(\max\) 即可。

然而朴素枚举转移是 \(O(m^2n)\) 的。

我们先考虑后面那一大坨怎么优化,它实质上就是我们要找出在 \(j+1\) 时刻开始游戏并且能在离店前打完的人中,选出最大的 \(w\)。这个问题可以这么转化:

开始序列全为 \(0\),我们有 \(n\) 次区间操作,每次将 \([l,r]\) 区间内的数变为 \(\max(a_i,w)\)

最后输出操作结束后每个位置上的值。

乍一看这不是区间最值么,难道要上吉司机?

冷静,我们要的是最后结果,离线就行了。

所以我们按照 \(w\) 给每次操作从大到小排序,这样一个位置的值一旦被赋上去,就不可能再被后面的更新。线段树维护区间最大值 \(val\) 和区间已经确定的值个数 \(cnt\)。参考代码:

inline void modify(int p,int v,int s,int e,int l,int r) {
    if (cnt[p] == r - l + 1) return;//这个子区间的数都赋过值,直接返回
    if (s <= l && r <= e) {
        val[p] = max(val[p],v);//其实可以改为如果是0(初始值)就赋值,否则不用管
        tag[p] = max(v,tag[p]);//区间操作要打tag
        return;
    }
    int mid = (l + r) >> 1;
    if (l != r && tag[p]) pushdown(p,l,r);
    if (s <= mid) modify(ls,v,s,e,l,mid);
    if (e > mid) modify(rs,v,s,e,mid+1,r);
    pushup(p);
}

pushdown

inline void pushdown(int p,int l,int r) {
    int mid = (l + r) >> 1;
    if (cnt[ls] != mid - l + 1) val[ls] = max(val[ls],tag[p]),cnt[ls] = mid - l + 1,tag[ls] = max(tag[ls],tag[p]);//别忘了如果区间都赋过值就没必要下传tag
    if (cnt[rs] != r - mid) val[rs] = max(val[rs],tag[p]),cnt[rs] = r - mid,tag[rs] = max(tag[rs],tag[p]);
    tag[p] = 0;
}

单点查询:

inline int query(int p,int pos,int l,int r) {
    if (l == r) {
        return val[p];
    }
    int mid = (l + r) >> 1;
    if (l != r && tag[p]) pushdown(p,l,r);
    if (pos <= mid) return query(ls,pos,l,mid);
    else return query(rs,pos,mid+1,r);
}

这样我们把读入的区间照如上处理,这样转移的时候就可以 \(O(\log n)\) 查询。

注意的是我们每个点上维护的是从这个时间开始并能够完成游戏\(w\) 的最大值,所以每个人给的 \([l_i,r_i]\) 实际有用的只有 \([l_i,r_i-k+1]\)\(+1\) 的原因见开始),所以修改的区间应该是后者,如果 \(l_i>r_i-k+1\),则这个人不会有任何用处(在店时间都不够打一局),直接跳过。

for (int i = 1; i <= n; i++) {
    cin >> p[i].l >> p[i].r >> p[i].w;
    p[i].r -= k-1;
}
sort(p+1,p+n+1);
for (int i = 1; i <= n; i++) {
    if (p[i].l > p[i].r) continue;
    modify(1,p[i].w,p[i].l,p[i].r,1,m);//注意值域是m不是n
}

然后我们在考虑状态转移方程前面那个 \(\max\)。我们发现,一旦求出了 \(f_j\)\(f_j\) 连着后面这一堆东西都是不变的,我们没必要反复求。所以我们每次遍历到 \(i\) 时,我们就把 \(f_{i-k}+\max^{n}_{id=1}[l_{id}\le i-k+1][r_{id}\ge i]\times w_{id}\) 这一坨扔进大根堆,转移直接取堆顶的值即可。

for (int i = 1; i <= m; i++) {
    if (i >= k) {
        q.push(f[i-k]+query(1,i-k+1,1,m));
    }
    q.push(f[i-1]);
    if (!q.empty()) f[i] = q.top();
}

答案就是 \(f_m\)

完整代码:(别忘了 long long

#include <bits/stdc++.h>
#define int long long
#define ls p<<1
#define rs p<<1|1
using namespace std;
const int maxn = 5e5+20;
int n,m,k;
int val[maxn<<2],tag[maxn<<2],cnt[maxn<<2];
struct line{
    int l,r,w;
    bool operator < (const line &y) const {
        return w > y.w;
    }
}p[maxn];
inline void pushup(int p) {
    val[p] = max(val[ls],val[rs]);
    cnt[p] = cnt[ls] + cnt[rs];
}
inline void pushdown(int p,int l,int r) {
    int mid = (l + r) >> 1;
    if (cnt[ls] != mid - l + 1) val[ls] = max(val[ls],tag[p]),cnt[ls] = mid - l + 1,tag[ls] = max(tag[ls],tag[p]);
    if (cnt[rs] != r - mid) val[rs] = max(val[rs],tag[p]),cnt[rs] = r - mid,tag[rs] = max(tag[rs],tag[p]);
    tag[p] = 0;
}
inline void modify(int p,int v,int s,int e,int l,int r) {
    if (cnt[p] == r - l + 1) return;
    if (s <= l && r <= e) {
        val[p] = max(val[p],v);
        tag[p] = max(v,tag[p]);
        return;
    }
    int mid = (l + r) >> 1;
    if (l != r && tag[p]) pushdown(p,l,r);
    if (s <= mid) modify(ls,v,s,e,l,mid);
    if (e > mid) modify(rs,v,s,e,mid+1,r);
    pushup(p);
}
inline int query(int p,int pos,int l,int r) {
    if (l == r) {
        return val[p];
    }
    int mid = (l + r) >> 1;
    if (l != r && tag[p]) pushdown(p,l,r);
    if (pos <= mid) return query(ls,pos,l,mid);
    else return query(rs,pos,mid+1,r);
}
int f[maxn];
priority_queue<int> q;
signed main() {
#ifdef LOCAL
    freopen("D:/codes/exe/a.in","r",stdin);
    freopen("D:/codes/exe/a.out","w",stdout);
#endif
    ios::sync_with_stdio(0);
    cin.tie(0),cout.tie(0);
    cin >> n >> m >> k;
    for (int i = 1; i <= n; i++) {
        cin >> p[i].l >> p[i].r >> p[i].w;
        p[i].r -= k-1;
    }
    sort(p+1,p+n+1);
    for (int i = 1; i <= n; i++) {
        if (p[i].l > p[i].r) continue;
        modify(1,p[i].w,p[i].l,p[i].r,1,m);
    }
    for (int i = 1; i <= m; i++) {
        if (i >= k) {
            q.push(f[i-k]+query(1,i-k+1,1,m));
        }
        q.push(f[i-1]);
        if (!q.empty()) f[i] = q.top();
    }
    // for (int i = 1; i <= m; i++) {
    //     cout << query(1,i,1,m) << ' ';
    // }
    // cout << '\n';
    // for (int i = 1; i <= m; i++) {
    //     cout << f[i] << ' ';
    // }
    // cout << '\n';
    cout << f[m] << '\n';
    return 0;
}
posted @ 2025-04-19 17:52  Ascnbeta  阅读(20)  评论(0)    收藏  举报