maya 我突然发现你们都在水 blog(那我也来)

可能是我上次写自己爆炸的缘故,但更多的是数据辣鸡的缘故,我这次莫名其妙 AK 了):

而且题解和我的 2,3 题根本不一样,我甚至刚开始没看懂(:

而且讲题人周日晚上咕咕咕了,周一晚上 51nod 在维修所以只能今天订正题目 QAQ(蒟蒻只有晚修在机房 QAQ

好进入正题

A. 砍树

description: 小 D 有一棵树,这棵树有 n 个节点,n-1 条边,保证连通。树上每个点要么被染成黑色,要么是白色。他定义一棵树的奇怪值为:这棵树中,白色节点与黑色节点数量的差的绝对值。比如树上有 5 个白节点,3 个黑节点,奇怪值就是|3-5|=2。小 D 想让你在原树中砍出来一个连通块,使得这个连通块的奇怪值最大。(注意连通块当然也会是棵树。)$n\leq 10^5$

签到题,就随便树形 dp 一下就行

$f[u][0]$表示白点减黑点的最大值,$f[u][1]$表示黑点到白点的最大值,显然把每个点所在的子树节点正的答案累加起来就行了,复杂度 $O(n)$,代码比赛时候打的有点丑(打了一堆没用的东西

#include<bits/stdc++.h> 
#define fo(i, a, b) for (int i = (a); i <= (b); ++i)
#define fd(i, a, b) for (int i = (a); i >= (b); --i)
#define edge(i, u) for (int i = head[u], v = e[i].v; i; i = e[i].nxt, v = e[i].v)
#define N 100005
#define pb push_back
#define F first
#define S second
#define ll long long
#define inf 1000000007
#define mp std::make_pair
#define lowbit(x) (x & -x)
#define ls (k << 1)
#define rs (k << 1 | 1)
#define mod 998244353
#define up 10000
int n, ans;
struct edge{
    int nxt, v;
}e[N << 1];
int head[N], tot, sz[N], bl[N], sum, a[N], u, v, f[N][2];
inline void addedge (int u, int v)
{
    e[++tot] = (edge) {head[u], v};
    head[u] = tot;
}
inline int abs (int x) {return x < 0 ? -x : x;}
inline void dfs (int u, int fa)
{
    if (a[u]) 
    {
        bl[u] = 1;
        f[u][0] = -1;
        f[u][1] = 1;
    }
    else
    {
        f[u][0] = 1;
        f[u][1] = -1;
    }
    sz[u] = 1;
    edge (i, u)
    {
        if (v == fa) continue;
        dfs(v, u);
        sz[u] += sz[v];
        bl[u] += bl[v];
        if (f[v][0] > 0)
            f[u][0] += f[v][0];
        if (f[v][1] > 0)
            f[u][1] += f[v][1];
    }
}
int main ()
{
    scanf("%d", &n);
    fo (i, 1, n)
    {
        scanf("%d", &a[i]);
        sum += a[i];
    }
    fo (i, 2, n)
    {
        scanf("%d %d", &u, &v);
        addedge(u, v);
        addedge(v, u);
    }
    dfs(1, 0);
    fo (i, 1, n)
        ans = std::max(ans, std::max(f[i][0], f[i][1]));
    printf("%d", ans);
    return 0;
}

B. 奇怪的回文串

description: 小 D 对字符串有着奇怪的认识。对于一个字符串 S,他认为 S 是满足 “奇数-回文” 性质,当且仅当这个串的所有长度为奇数的子串都是回文串。注意一个串的子串指的是串中任意连续位置形成的串,一个 “奇数-回文” 串本身并不需要长度为奇数。现在小 D 有一个长度为 N 的字符串 S。他有 K 次修改这个字符串的机会。每次修改,他可以选择字符串上的任意一个位置,把这个位置修改成任意一种字符。他希望使得修改后的串中满足 “奇数-回文” 性质的子串的长度最大。注意 K 次机会不必用完。$N,K\leq 5e5$,权值 $1e9$

这个题我写了一个魔法贪心然后就莫名其妙 A 了,首先题目就是让你修改 K 次求一个’ABABAB..’ 形的最长子串,我就二分了一个答案,然后滑动这个窗口,维护奇数位和偶数位的相同字符出现次数的最大值,我就暴力离散化后写了个桶,那怎么求相同字符出现次数的最大值呢?暴力显然是会 gg 的,因为是滑动窗口,我就记录了一下上一次的最大值,因为每次移动一次窗口只会增加一个数减少一个数,我们就看一下增加的那个数可不可能更新上一次的最大值,如果能的话就暴力求一遍最大值复杂度是 $O(n)$的,否则最大值还是上一次的最大值(其实也不是那么容易卡这种贪心了啦 QAQ)(但是写了 150 行心态快崩了还是压时间过的)

然而题解显然更简单呀,维护一对双指针 l,r,l 从左往右移,r 找到能扩展到的最远距离,用一个可删除的堆就可以维护相同字符出现次数的最大值了,复杂度 $O(nlogn)$的,但是懒得写手写堆,我们魔改一下优先队列就行,维护当前区间每个字符出现的最大次数,堆是按字符出现次数排序的大根堆,如果这个堆的顶部这个字符=当前区间该字符出现的最大次数,那就是它最大了,否则删除堆顶继续(说明这个字符已经被删除过了)

代码:

#include<bits/stdc++.h> 
#define fo(i, a, b) for (int i = (a); i <= (b); ++i)
#define fd(i, a, b) for (int i = (a); i >= (b); --i)
#define edge(i, u) for (int i = head[u], v = e[i].v; i; i = e[i].nxt, v = e[i].v)
#define N 500005
#define pb push_back
#define F first
#define S second
#define ll long long
#define inf 1000000007
#define mp std::make_pair
#define lowbit(x) (x & -x)
#define ls (k << 1)
#define rs (k << 1 | 1)
#define mod 998244353
#define up 10000
struct node{
    int cnt, id;
    friend bool operator < (node x, node y)
    {
        return x.cnt < y.cnt;
    }
};
int a[N], q, ans, n, m;
std::map<int, int> cnt1, cnt2;
std::priority_queue<node> q1, q2;
int main ()
{
    scanf("%d %d", &m, &n);
    fo (i, 1, n)
        scanf("%d", &a[i]);
    int l = 1, r = 0, now;
    node max1, max2;
    fo (i, 1, n)
    {
        while (r < n)
        {
            ++r;
            if (r & 1)
            {
                q1.push((node){++cnt1[a[r]], a[r]});
                while (!q1.empty())
                {
                    max1 = q1.top();
                    if (max1.cnt == cnt1[max1.id]) break;
                    q1.pop();
                }
            }
            else
            {
                q2.push((node){++cnt2[a[r]], a[r]});
                while (!q2.empty())
                {
                    max2 = q2.top();
                    if (max2.cnt == cnt2[max2.id]) break;
                    q2.pop();
                }
            }
            now = r - l + 1 - max1.cnt - max2.cnt;
            if (now > m) break;
            ans = std::max(ans, r - l + 1);
        }
        if (l & 1)
        {
            cnt1[a[l]]--;
        }
        else
        {
            cnt2[a[l]]--;
        }
        ++l;
    }
    printf("%d", ans);
    return 0;
}

启示:不要瞎二分验证,答案满足单调性也可以是双指针+数据结构维护,减一个 log 是一个 log。

C. 范围查询

小 D 得到了一个数组 $A=[a_0,a_1,…,a_(n-1)]$。他对这个数组 A 进行了 q 个查询,每个查询都会给定四个数 left,right,x,y,你需要求出在数组 A 中,有多少位置 i 满足下列条件:

$1.left≤i≤right$

$2.a_i≡y(mod x)$

其中 a_i≡y(mod x) 表示,a_i 对 x 取模的值为 y。

$1≤n,q≤40000,0≤a_i≤40000,1≤x≤40000,0≤left≤right$

数据出锅了,有 $left>right$

后来改了数据之后又有 y>x 的点,而且标程似乎没有处理这个情况,我现在都 A 不了这题 QAQ

不过里面的思想还是值得借鉴的

当 x 比较大的时候,我们直接就暴力查询有多少值=kx+y,怎么查等等看程序就行,这里比较简单。

当 x 比较小的时候比如小于 1000,我们可以暴力枚举 x,可是现在的问题是什么呢?查询的区间是和 n 同数量级的,区间我们又想到了什么呢?就直接转换成前缀和,在一头一尾打个标记就行。

具体来讲,就是从左往右枚举数组,将它们弄到统计数组 c 里面,在如果碰到了标记的话就更新答案就行。

代码:

#include<bits/stdc++.h> 
#define fo(i, a, b) for (int i = (a); i <= (b); ++i)
#define fd(i, a, b) for (int i = (a); i >= (b); --i)
#define edge(i, u) for (int i = head[u], v = e[i].v; i; i = e[i].nxt, v = e[i].v)
#define N 40005
#define pb push_back
#define F first
#define S second
#define ll long long
#define inf 1000000007
#define mp std::make_pair
#define lowbit(x) (x & -x)
#define ls (k << 1)
#define rs (k << 1 | 1)
#define mod 1000000007
int n, m, ans[N], a[N], opt, cnt[2];
struct node{
    int l, r, x, y, id;
    inline friend bool operator < (node qwq, node qaq)
    {
        return qwq.x < qaq.x;
    }
}q[2][N], tmp;
struct fen{//差分统计答案
    int id, y, v;
};
std::vector<int> vet[N];
std::vector<fen> t[N];
int c[1005];
int Max;
inline void solve1 ()//在 x 小的时候,我们可以暴力枚举 x,然后对于每一个 q[i].x,将它前缀和处理一把,那么答案就是 q[i].r 之前等于 y 的数-(q[i].l-1) 之前的数 [注意,核心思想就是差分,减小枚举区间的复杂度]
{
    for (int i = 1, j; i <= cnt[0]; i = j + 1)
    {
        j = i;
        memset(c, 0, sizeof c);
        while (q[0][i].x == q[0][j].x) 
        {
            int l = q[0][j].l, r = q[0][j].r;
            t[l - 1].pb((fen){q[0][j].id, q[0][j].y, -1});
            t[r].pb((fen){q[0][j].id, q[0][j].y, 1});
            ++j;
        }
        --j;
        int x = q[0][i].x;
        fo (k, 1, n)
        {
            ++c[a[k] % x];
            if (t[k].size())
            {
                int sz = t[k].size() - 1;
                fo (l, 0, sz)
                {
                    ans[t[k][l].id] += c[t[k][l].y] * t[k][l].v;
                }
                t[k].clear();
            }
        }
    }
}
inline void solve2 ()//统计区间有多少个数字 x 的方法,值域小的时候可以反过来跑,将权值的 pos 存入 vet,我们就可以在给定权值的 vet 里面找出有多少个符合这个区间,二分即可。
{
    fo (i, 1, n)
        vet[a[i]].pb(i);
    fo (i, 1, cnt[1])
    {
        int l = q[1][i].l, r = q[1][i].r;
        for (int j = q[1][i].y; j <= Max; j += q[1][i].x)
            if (vet[j].size())
            {
                int L = lower_bound(vet[j].begin(), vet[j].end(), l) - vet[j].begin();
                int R = upper_bound(vet[j].begin(), vet[j].end(), r) - vet[j].begin();
                ans[q[1][i].id] += R - L;
            }
    }
}
int main ()
{
    scanf("%d %d", &n, &m);
    fo (i, 1, n) {scanf("%d", &a[i]); Max = std::max(Max, a[i]);}
    fo (i, 1, m)
    {
        scanf("%d %d %d %d", &tmp.l, &tmp.r, &tmp.x, &tmp.y);
        tmp.id = i;
        if (tmp.x == 0) continue;
     //   tmp.y %= tmp.x;
        ++tmp.l; ++tmp.r;
        opt = (1000 < tmp.x);
        q[opt][++cnt[opt]] = tmp;
    }
    std::sort(q[0] + 1, q[0] + cnt[0] + 1);
    solve1();
    solve2();
    fo (i, 1, m)
        printf("%d\n", ans[i]);
    return 0;
}

总结:前缀和各种奇怪的 trick 我还是菜啊。

分类: 文章

HomuraCat

明日は何になる? やがて君になる!

0 条评论

发表回复

Avatar placeholder

您的电子邮箱地址不会被公开。 必填项已用 * 标注