2021牛客多校第二场
B. Cannon
G. League of Legends
题意:给定n个区间,要求将它们分成k组,每组之间有交,最大化每组交长度之和
简化问题,对于如果一个大区间能够完整包含某个小区间,则该大区间只有两种最优决策:
- 单独归为一组,贡献r-l
- 与任意一个其包含的小区间归为一组,不对结果产生影响
可以用反证法证明结论,假如大区间和其他区间归为一组,那么将该大区间移出该组,该组的交一定不下降,再放入其包含的小区间组中,小区间所在的组的交也不下降,即大小区间在同一组的决策一定最优
于是可以把所有大区间取出来,单独讨论所有小区间,可以发现,若将剩下的区间按照左端点从小到大排序,其右端点也一定是有序的,接着考虑dp,$f[i][j]$表示考虑前i个区间,分成j组的最优答案,而一组的贡献只与最左和最右两个区间相关,显然有转移$f[k][j+1]=\max\limits_{r[i+1]-l[k]>0} \{f[i][j]+r[i+1]-l[k]\}$
是一个比较模板的单调队列优化dp,对于每个j维护一个单调队列,维护队列中$f[i][j]+r[i+1]$单调递减,可以用双向队列deque实现
最后把大小区间的情况合并,枚举单独成组的大区间个数即可统计答案
#include <bits/stdc++.h>
#define all(x) x.begin(), x.end()
using namespace std;
typedef pair<int, int> pii;
const int N = 5e3 + 5, INF = 0x3f3f3f3f;
struct Interval {
int l, r;
bool operator < (Interval &I) const {
if (l != I.l) return l < I.l;
else return r > I.r;
}
}a[N];
int n, k, m, p;
int f[N][N];
vector<Interval> V;
vector<int> G;
int sum[N];
deque<int> q[N];
int main() {
scanf("%d %d", &n, &k);
for (int i = 1; i <= n; i++) {
scanf("%d %d", &a[i].l, &a[i].r);
}
sort(a + 1, a + 1 + n);
int nr;
for (int i = n; i >= 1; i--) {
if (i == n || a[i].r < nr) {
V.push_back(a[i]);
nr = a[i].r;
}
else G.push_back(a[i].r - a[i].l);
}
V.push_back({-1, -1});
sort(all(V));
sort(all(G), [&](int u, int v) {
return u > v;
});
m = V.size() - 1; p = G.size();
for (int i = 1; i <= p; i++) sum[i] = sum[i - 1] + G[i - 1];
memset(f, -0x3f, sizeof f);
for (int i = 1; i <= m; i++) {
for (int j = min(i, k); j >= 2; j--) {
while(!q[j - 1].empty() && V[q[j - 1].front() + 1].r <= V[i].l) q[j - 1].pop_front();
if (!q[j - 1].empty()) {
f[i][j] = f[q[j - 1].front()][j - 1] + V[q[j - 1].front() + 1].r - V[i].l;
while(!q[j].empty() && (f[i][j] + V[i + 1].r >= f[q[j].back()][j] + V[q[j].back() + 1].r)) q[j].pop_back();
q[j].push_back(i);
}
}
if (V[1].r - V[i].l > 0) {
f[i][1] = V[1].r - V[i].l;
while(!q[1].empty() && (f[i][1] + V[i + 1].r >= f[q[1].back()][1] + V[q[1].back() + 1].r)) q[1].pop_back();
q[1].push_back(i);
}
}
int ans = 0;
for (int i = 0; i <= min(p, k); i++) ans = max(ans, f[m][k - i] + sum[i]);
cout << ans << endl;
return 0;
}
J. Product of GCDs
K. Stack
L. WeChat Walk
题意:给定n个人之间的好友关系,每次单点增加一个人的步数,求每个人在自己列表里保持冠军的时间
考虑临界值$B=\sqrt n$,以B为基准对点分类,分为度数>=B的大点和<B的小点,同时观察到每次修改至多使一个人从非冠军变为冠军,也就是说冠军的变化次数是$O(n)$的,意味着我们可以在每次修改后都更新冠军情况,保持所有人的冠军情况是最新的,从而求出答案。
对于小点,每次修改后主动查询相邻的所有点并更新这些点的冠军情况$O(B)$,并且将修改后的权值信息传递给相邻的大点$O(B)$,记录在对应大点的集合中
对于大点,主动查询周围所有大点并更新这些点的冠军情况$O(B)$,然后查询周围所有可能对该点冠军情况相互影响的小点,这样的信息在所有大点的集合中的数量为$O(nB)$,且不会重复查询,因此复杂度保证。为了避免重复查询,可以根据权值再将集合分成多个区间,每次将点u增加v后,仅查询$[val[u]+1,val[u]+v]$区间
#include <bits/stdc++.h>
using namespace std;
const int N = 2e5 + 5, BB = 300;
vector<int> G[N], B[N];
vector<int> vc[501][10001];
bool big[N];
int n, m, q, val[N], cha[N], ans[N], mxneigh[N], TT, mp[N];
void modify(int x, int y, int type) {
if (type == 0) {
if (cha[x] != -1) ans[x] += y - cha[x];
cha[x] = -1;
} else {
if (cha[x] == -1) cha[x] = y;
}
}
int main() {
scanf("%d %d %d", &n, &m, &q);
for (int i = 1; i <= m; i++) {
int u, v; scanf("%d %d", &u, &v);
G[u].push_back(v);
G[v].push_back(u);
}
for (int i = 1; i <= n; i++) {
if (G[i].size() >= BB) {
big[i] = 1;
mp[i] = TT++;
for (int to : G[i]) {
B[to].push_back(i);
}
}
}
memset(cha, -1, sizeof(cha));
for (int i = 1; i <= q; i++) {
int u, v; scanf("%d %d", &u, &v);
if (cha[u] != -1) {
val[u] += v;
if (!big[u]) {
for (int to : B[u]) {
vc[mp[to]][val[u]].push_back(u);
}
}
continue;
}
if (!big[u]) {
val[u] += v;
bool can = 1;
for (int to : G[u]) {
if (cha[to] && val[to] <= val[u]) modify(to, i, 0);
if (val[to] >= val[u]) can = 0;
}
if (can) {
modify(u, i, 1);
for (int to : B[u]) {
vc[mp[to]][val[u]].push_back(u);
}
}
} else {
bool can = 1;
for (int to : B[u]) {
if (cha[to] && val[to] <= val[u] + v) modify(to, i, 0);
if (val[to] >= val[u] + v) can = 0;
}
for (int j = val[u] + 1; j <= val[u] + v; j++) {
for (int x : vc[mp[u]][j]) {
if (val[x] <= val[u] + v) modify(x, i, 0);
}
}
if (can) {
modify(u, i, 1);
}
val[u] += v;
}
}
for (int i = 1; i <= n; i++) modify(i, q, 0);
for (int i = 1; i <= n; i++) printf("%d\n", ans[i]);
return 0;
}