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;
}
/*
 */
posted on 2022-10-31 21:12  HZOI-曹蓉  阅读(20)  评论(0编辑  收藏  举报