2021牛客寒假算法基础集训营4

B 武辰延的字符串 || 字符串哈希+二分

#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
typedef unsigned long long ull;
const int maxn = 1e5 + 5;
const int mod = 1e9 + 7;
const int INF = 0x3f3f3f3f;
const int P = 131;
const double pi = acos(-1.0);
char s[maxn], t[maxn];
ull p[maxn], hs[maxn], ht[maxn];
ll ans;
ull getHash(int l, int r, ull h[])
{
    return h[r] - h[l-1] * p[r-l+1];
}
int main()
{
    cin >> s + 1 >> t + 1;
    int n = strlen(s + 1), m = strlen(t + 1);
    p[0] = 1;
    for(int i = 1; i < maxn; ++i) p[i] = p[i-1] * P;
    for(int i = 1; i <= n; ++i) hs[i] = hs[i-1] * P + s[i];
    for(int i = 1; i <= m; ++i) ht[i] = ht[i-1] * P + t[i];
    for(int i = 1; i < m; ++i)
    {
        if(s[i] != t[i]) break;
        int l = 1, r = min(n, m - i);
        if(s[1] != t[l+i]) continue;
        if(getHash(1, r, hs) == getHash(l + i, r + i, ht)) {
            ans += r;
            continue;
        }
        while(r - l > 1)
        {
            int mid = (r + l) / 2;
            if(getHash(1, mid, hs) == getHash(i + 1, mid + i, ht)) l = mid;
            else r = mid;
        }
        ans += l;
    }
    cout << ans << endl;
}

G 九峰与蛇形填数

比赛时T了,n <= 2000,m <= 3000
比赛时想到了倒着处理,但是循环的顺序没有想出来。每个位置的最终状态取决于最后一次修改它时的结果。我们可以先遍历每个位置,再对每个位置遍历m次操作,如果该位置位于该次操作范围内,就计算出结果并填入,然后跳出循环。这样就能保证每个位置最多只处理一次,复杂度上限是2000 * 2000 * 3000 = 1e10,但是跑不满,因为不可能每个位置都需要遍历m次操作。
还有一个处理就是,记录下来m次操作的过程中,找出修改过的最大范围,这样在之后遍历位置的时候,从来没有被修改过的数就无需遍历操作。

#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
typedef unsigned long long ull;
const int maxn = 2022;
const int maxm = 3033;
const int mod = 1e9 + 7;
const int INF = 0x3f3f3f3f;
const int P = 131;
const double pi = acos(-1.0);
int x[maxm], y[maxm], k[maxm];
int a[maxn][maxn];
int main()
{
    int n, m, tmp;
    int xmin = INF, ymin = INF, xmax = 0, ymax = 0;
    cin >> n >> m;
    for(int i = 1; i <= m; ++i)
    {
        scanf("%d %d %d", &x[i], &y[i], &k[i]);
        xmin = min(xmin, x[i]);
        ymin = min(ymin, y[i]);
        xmax = max(xmax, x[i] + k[i] - 1);
        ymax = max(ymax, y[i] + k[i] - 1);
    }
    for(int i = 1; i <= n; ++i)
    {
        for(int j = 1; j <= n; ++j)
        {
            if(i < xmin || i > xmax || j < ymin || j > ymax) continue;
            for(int h = m; h >= 1; --h)
            {
                if(i >= x[h] && i <= x[h] + k[h] - 1 && j >= y[h] && j <= y[h] + k[h] - 1)
                {
                    tmp = (i - x[h]) * k[h];
                    if((i - x[h]) % 2 == 0) a[i][j] = tmp + j - y[h] + 1;
                    else a[i][j] = tmp + k[h] + y[h] - j;
                    break;
                }
            }
        }
    }
    for(int i = 1; i <= n; ++i)
    {
        for(int j = 1; j <= n; ++j)
            printf("%d ", a[i][j]);
        printf("\n");
    }
}

F 魏迟燕的自走棋 || 并查集

建图思想:并查集维护基环树

图的问题:从联通块开始入手分析

注意到 \(k\) 只能取0、1,一个装备只能分给一个或两个人,想到转化到图
首先我们考虑把 \(m\) 件装备看作 \(m\) 条连接两个点的边(如果 \(k=1\) 则当成自环)。于是我们的问题转化为选出一张边权和最大的生成子图。
这个图是由0个或若干个联通块构成。对于每一个联通块,它至少是一棵树,保证其联通。如果是一棵树的话,它表示每个人可以分到一个和他相连的装备,然后会多出一个点。此时在这个树的基础上多一条边,那么就变成了一棵基环树,基环树表示每个人都正好可以分到一个装备。此时如果再加一条边,那么就会出现装备没人使用,而我们要求最多这些装备能提供多大战斗力,所以这样的边就没用了。由此可以看出,每个联通块要么是树,要么是基环树。

一个结论是每次都选最大边一定不劣。这个结论来自kruskal算法的推广。

一旦我们选择一条最大边后,我们把两个点缩成一个点(选中非自环的情况),或者直接把这个点丢掉(选中自环的情况),就面临一个规模更小且完全一致的问题,可以继续执行选取最大边的策略。

也就是说我们只需要按照边权从大到小的顺序贪心地拿边,用一个并查集维护联通性和是否带环即可。

证明:

考虑一个不取最大边的方案。

如果把最大边加入后生成子图依旧满足条件,那么加入这条最大边会更优。

如果加入后发现这条边所在的连通块的边数超过了点数,说明原方案已经构成一棵基环树或两棵的基环树(被最大边连接),那么我们肯定可以断掉一条环边,把它变成一棵树或者一棵树+一棵基环树,再把最大边加入使其变成一棵基环树。

由于删掉的边的权值一定不大于最大边,因此新方案一定不劣于原方案。

#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
typedef unsigned long long ull;
const int maxn = 1e5 + 7;
const int maxm = 3033;
const int mod = 1e9 + 7;
const int INF = 0x3f3f3f3f;
const int P = 131;
const double pi = acos(-1.0);
struct node
{
    int x, y, w;
}pro[maxn];

bool cmp(const node& a, const node& b)
{
    return a.w > b.w;
}

int f[maxn];
bool mark[maxn];
ll ans;

void init(int n)
{
    for(int i = 1; i <= n; ++i)
        f[i] = i;
}

int find(int x)
{
    if(f[x] == x) return x;
    return f[x] = find(f[x]);
}

int main()
{
    int n, m, k;
    cin >> n >> m;
    for(int i = 1; i <= m; ++i)
    {
        scanf("%d %d", &k, &pro[i].x);
        if(k == 2) scanf("%d", &pro[i].y);
        else pro[i].y = INF;
        scanf("%d", &pro[i].w);
    }
    sort(pro + 1, pro + 1 + m, cmp);
    init(n);
    for(int i = 1; i <= m; ++i)
    {
        if(pro[i].y == INF)
        {
            int fx = find(pro[i].x);
            if(!mark[fx])
            {
                ans += pro[i].w;
                mark[fx] = 1;
            }
        }
        else
        {
            int fx = find(pro[i].x), fy = find(pro[i].y);
            if(fx == fy) // 一棵树自己和自己合并,变成基环树
            {
                if(mark[fx]) continue;
                mark[fx] = 1;
                ans +=pro[i].w;
                continue;
            }
            if(mark[fx] && mark[fy]) continue;
            ans += pro[i].w;
            f[fx] = fy;
            if(mark[fx] || mark[fy]) mark[fy] = 1;
        }
    }
    printf("%lld\n", ans);
}

H 吴楚月的表达式 || dfs

一个非空表达式前缀可以表示成 \(a+b\) 的形式。
如果后面接了一个 \(+x\) ,则变成 \((a+b)+x\)
如果后面接了一个 \(-x\) ,则变成 \((a+b)+(-x)\)
如果后面接了一个 \(*x\) ,则变成 $a+b∗x $;
如果后面接了一个 \(/x\) ,则变成 $a+b/x $;
最后还是可以表示成 \(a+b\) 的形式。
因此只需要遍历整棵树维护每个节点对应的 \(a+b\) 即可,分别用q1[x],q2[x]表示。
注意:除法取模需要逆元:限制 mod 必须为质数。a/b % mod == a * bmod-2 (模意义下)

#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
typedef unsigned long long ull;
const int maxn = 1e5 + 7;
const int maxm = 3033;
const int mod = 1e9 + 7;
const int INF = 0x3f3f3f3f;
const int P = 131;
const double pi = acos(-1.0);
ll a[maxn];
ll q1[maxn], q2[maxn];
vector<int>v[maxn];
char op[maxn];
ll qpow(ll a, ll b)
{
    ll t = 1;
    for(; b; b >>= 1, a = a * a % mod)
        if(b & 1)  t = t * a % mod;
    return t;
}
void dfs(int x)
{
    for(auto y : v[x])
    {
        if(op[y] == '+'){
            q1[y] = (q1[x] + q2[x]) % mod;
            q2[y] = a[y];
        }else if(op[y] == '-'){
            q1[y] = (q1[x] + q2[x]) % mod;
            q2[y] = -a[y] + mod;
        }else if(op[y] == '*'){
            q1[y] = q1[x];
            q2[y] = q2[x] * a[y] % mod;
        }else if(op[y] == '/'){
            q1[y] = q1[x];
            q2[y] = q2[x] * qpow(a[y], mod - 2) % mod;
        }
        dfs(y);
    }
}

int main()
{
    int n, x;
    cin >> n;
    for(int i = 1; i <= n; ++i) scanf("%lld", &a[i]);
    for(int i = 2; i <= n; ++i)
    {
        scanf("%d", &x);
        v[x].push_back(i);
    }
    scanf("%s", op + 2);
    q1[1] = 0, q2[1] = a[1];
    dfs(1);
    for(int i = 1; i <= n; ++i)
        printf("%lld ", (q1[i] + q2[i]) % mod);
}

官方题解

posted @ 2021-02-20 10:13  .Ivorelectra  阅读(85)  评论(0编辑  收藏  举报