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));
}
然后考虑交换相邻字母怎么做。
首先,如果两个字母相同,显然没有任何意义。
然后,对于 v
和 n
互换的类型,对答案没有影响,只需要修改树状数组即可。
最后,考虑如果有 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\)),状态转移方程如下:
\([]\) 是艾弗森括号。后面那一坨的意思就是,从所有满足在店时间在 \([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\)),状态转移方程如下:
\([]\) 是艾弗森括号。
这一串的意思就是,我们先考虑 \(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;
}