2022NOIP A层联测18 算术(a) 刷墙(b) 重复(c) 公交(d)
[区间DP]T2:给出n次操作[l,r],第i次操作把从l米到r米的区间颜色变成i,操作具有覆盖性质。求一种最优染色顺序使得最后区间的颜色最多。(n<=300)
考场
显然的假贪心按照区间长度排序,然后就不管了,一分也没有。
正解
300的范围\(n^3\)DP,显然染色具有阶段划分特点。
\(f[i][j]:表示[l,r]区间用区间内的操作最多染色的种类。\)
\(考虑对于最终的形态,如果i次操作有效一定是最后一次,\)
\(对于一个区间如果有用一定存在一段只自己出现的区间,所以枚举x断点,\)
\(表示[x,x+1]区间被最后覆盖,划分成子问题[l,x][x+1,r]\)
\(判断x--x+1位置是否存在[l,r]范围内的区间:就是对于第一维元素要求 l -x之间,第二维要求x+1-r之间,\)
\(二维前缀和经典维护的问题\)
点击查看代码
// ubsan: undefined
// accoders
#include <bits/stdc++.h>
using namespace std;
#define rint register int
#define ll long long
#define ull unsigned long long
#define chu printf
inline ll re() {
ll x = 0, h = 1;
char ch = getchar();
while (ch < '0' || ch > '9') {
if (ch == '-')
h = -1;
ch = getchar();
}
while (ch <= '9' && ch >= '0') {
x = (x << 1) + (x << 3) + (ch ^ 48);
ch = getchar();
}
return x * h;
}
const int N = 310;
int f[N << 1][N << 1], sum[N << 1][N << 1];
int mp[N << 1];
int n;
struct node {
int l, r;
} e[N];
inline int safe(int l1, int l2, int r1, int r2) {
return sum[l2][r2] - sum[l2][r1 - 1] - sum[l1 - 1][r2] + sum[l1 - 1][r1 - 1];
}
int main() {
freopen("b.in", "r", stdin);
freopen("b.out", "w", stdout);
n = re();
for (rint i = 1; i <= n; ++i) {
e[i].l = re(), e[i].r = re();
mp[++mp[0]] = e[i].l;
mp[++mp[0]] = e[i].r;
}
sort(mp + 1, mp + 1 + mp[0]);
mp[0] = unique(mp + 1, mp + 1 + mp[0]) - mp - 1;
for (rint i = 1; i <= n; ++i) {
e[i].l = lower_bound(mp + 1, mp + 1 + mp[0], e[i].l) - mp;
e[i].r = lower_bound(mp + 1, mp + 1 + mp[0], e[i].r) - mp;
sum[e[i].l][e[i].r]++;
}
for (rint i = 1; i <= mp[0]; ++i)
for (rint j = 1; j <= mp[0]; ++j)
sum[i][j] = sum[i - 1][j] + sum[i][j - 1] - sum[i - 1][j - 1] + sum[i][j];
for (rint i = 1; i <= mp[0]; ++i) //枚举区间长度
for (rint st = 1; st + i - 1 <= mp[0]; ++st) {
int ed = st + i - 1;
for (rint k = st; k < ed; ++k)
f[st][ed] = max(f[st][ed], f[st][k] + f[k + 1][ed] + (safe(st, k, k + 1, ed) > 0));
}
chu("%d", f[1][mp[0]]);
return 0;
}
/*
6
2 3
3 4
2 3
2 3
0 1
2 3
*/
[字符串:LCP or Hash]T3:求对于给出的S串的子串,划分成6个连续子串ABCDEF,使得A=B=E,C=F的方案数。(n<=5000)
考场
没看到子串,就是凑不出来。发现模不出样例,先读题!
正解
首先对于暴力来说,枚举AB,枚举子串[l,r],O(n^3),其实如果你枚举子串思路就卡死了,
可以换思路,比如只枚举第一个AB,单独处理后面可以对应的AB,然后就均摊成n^2了。
kmp:
lcp[x][y]:x和y为起点的最长公共前缀:lcp[x][y]=lcp[x+1][y+1]+1(if(s[x]==s[y]))
枚举AB,枚举后面ABC断点,那么2次前缀和求出所有可能(第一次传递影响,第二次计数),
就可以直接统计答案了。
hash
类似
用mp映射子串个数,通过遍历顺序控制位置要求
点击查看代码
// ubsan: undefined
// accoders
#include <bits/stdc++.h>
using namespace std;
#define rint register int
#define ll long long
#define ull unsigned long long
#define chu printf
inline ll re() {
ll x = 0, h = 1;
char ch = getchar();
while (ch < '0' || ch > '9') {
if (ch == '-')
h = -1;
ch = getchar();
}
while (ch <= '9' && ch >= '0') {
x = (x << 1) + (x << 3) + (ch ^ 48);
ch = getchar();
}
return x * h;
}
const int N = 5000 + 100;
int lcp[N][N], sum[N][N];
char s[N];
int n;
int main() {
freopen("c.in", "r", stdin);
freopen("c.out", "w", stdout);
scanf("%s", s + 1);
n = strlen(s + 1);
for (rint i = n - 1; i >= 1; --i)
for (rint j = i + 1; j <= n; ++j)
if (s[i] == s[j])
lcp[i][j] = lcp[i + 1][j + 1] + 1;
for (rint i = 2; i <= n - 4; ++i) {
for (rint j = i + 3; j <= n - 1; ++j) // sum[i][j]:表示以i为第二个A的起点,AB长度是j的方案数
//枚举的j是第3个A的起点
sum[i][min(j - i - 1, lcp[i][j])]++;
for (rint j = n; j >= 1; --j) sum[i][j] += sum[i][j + 1];
for (rint j = n; j >= 1; --j) sum[i][j] += sum[i][j + 1];
}
ll ans = 0;
for (rint i = 1; i <= n - 5; ++i) {
for (rint j = i + 1; j <= n - 4; ++j) {
if (lcp[i][j] >= j - i) {
ans += sum[j][j - i + 1];
}
}
}
chu("%lld", ans);
return 0;
}
/*
*/
[图论:路径统计的问题转化+换根思想]T4:给出一棵树,每个节点的权值是ai,如果root是根,那么每个点的花费是dis(root,x)*ax,可以选择k个点使得root-choose的路径上的点变成root性质的点,求每个点是root的最少代价。(n<=2e5)
暴力
不好累计每个点加上的贡献,累计去掉某个点的贡献,发现就是子树内ai的和(因为删除的一定是到根的连续段)。可以直接以sum_a[i]为权值,长链剖分,删前k大的链。\(O(n^2)\),注意root本身的siz不累加,因为它不需要到自己。
正解
发现每次换根,最多改变2条链。那么dfs动态修改维护每个点是根的mx(不计算sizx的最大链)和cmx(次大)和dis(不计算x的siz和,即路径花费),回溯时恢复,对于答案,用2个set,一个维护前k大,一个维护剩下的,这样
可以O(1)查询,否则复杂度不对
点击查看代码
// ubsan: undefined
// accoders
#include <bits/stdc++.h>
using namespace std;
#define rint register int
#define ll long long
#define ull unsigned long long
#define chu printf
inline ll re() {
ll x = 0, h = 1;
char ch = getchar();
while (ch < '0' || ch > '9') {
if (ch == '-')
h = -1;
ch = getchar();
}
while (ch <= '9' && ch >= '0') {
x = (x << 1) + (x << 3) + (ch ^ 48);
ch = getchar();
}
return x * h;
}
const int N = 2e5 + 100;
int n, head[N], tot, k, a[N];
ll dis[N], siz[N], mx[N], cmx[N], ans[N], sum, now;
multiset<ll> s, mk;
struct node {
int to, nxt;
} e[N << 1];
inline void add_e(int u, int v) {
e[++tot] = (node){ v, head[u] };
head[u] = tot;
}
inline void pre(int x, int ff) {
siz[x] = a[x];
for (rint i = head[x]; i; i = e[i].nxt) {
int to = e[i].to;
if (to == ff)
continue;
pre(to, x);
siz[x] += siz[to];
dis[x] += dis[to] + siz[to];
if (mx[x] <= mx[to] + siz[to]) {
cmx[x] = mx[x];
mx[x] = mx[to] + siz[to];
s.insert(cmx[x]);
} else {
s.insert(mx[to] + siz[to]);
cmx[x] = max(cmx[x], mx[to] + siz[to]);
}
}
}
inline void del(ll val) {
if (!val)
return;
if (mk.size() && val >= (*mk.begin())) {
mk.erase(mk.lower_bound(val));
now -= val;
} else
s.erase(s.lower_bound(val));
while (mk.size() < k && s.size()) {
mk.insert(*(--s.end()));
now += *(--s.end());
s.erase(--s.end());
}
}
inline void ins(ll val) {
if (!val)
return;
if (s.size() && val <= (*(--s.end())))
s.insert(val);
else
mk.insert(val), now += val;
while (mk.size() > k) {
s.insert(*mk.begin());
now -= *mk.begin();
mk.erase(mk.begin());
}
}
inline void change(int x, int fa) {
ans[x] = dis[x] - now;
for (rint i = head[x]; i; i = e[i].nxt) {
int to = e[i].to;
if (to == fa)
continue;
del(mx[to] + siz[to]);
ins(mx[to]);
ll rm = mx[to], rv = 0;
if (mx[x] == mx[to] + siz[to]) {
del(cmx[x]);
ins(cmx[x] + sum - siz[to]);
rv = cmx[x] + sum - siz[to];
} else {
del(mx[x]);
ins(mx[x] + sum - siz[to]);
rv = mx[x] + sum - siz[to];
}
if (rv >= mx[to]) {
cmx[to] = mx[to];
mx[to] = rv;
} else
cmx[to] = max(cmx[to], rv);
dis[to] = dis[x] - 2 * siz[to] + sum;
change(to, x);
mx[to] = rm;
if (mx[x] == mx[to] + siz[to]) {
ins(cmx[x]);
del(cmx[x] + sum - siz[to]);
} else {
ins(mx[x]);
del(mx[x] + sum - siz[to]);
}
ins(mx[to] + siz[to]);
del(mx[to]);
}
}
int main() {
freopen("d.in", "r", stdin);
freopen("d.out", "w", stdout);
n = re();
k = re();
for (rint i = 1; i <= n; ++i) a[i] = re(), sum += a[i];
for (rint i = 1; i < n; ++i) {
int x = re(), y = re();
add_e(x, y);
add_e(y, x);
}
pre(1, 0);
s.insert(mx[1]);
for (rint i = 1; i <= k && s.size(); ++i) {
mk.insert(*(--s.end()));
now += *(--s.end());
s.erase((--s.end()));
}
change(1, 0);
for (rint i = 1; i <= n; ++i) chu("%lld ", ans[i]);
return 0;
}
/*
*/