CSP-S模拟15

T1.网格图

3秒过3e9,我赛时只打了\(O(n^2k^2)\)的暴力,极限时间复杂度应该是\(O(250^4)\),但不知道为什么只跑了\(2.5s\)
考虑枚举左上角时正方形的变化,只有左右两列发生了变化,那么实际上只需要扫这两列即可。注意到外部连通块的点可能会在内部存在,所以不能直接合并。分三步:与连通块相连的,正方形中1的个数,被正方形完全包含的连通块。\(details in code\)

代码
#define sandom signed
#define fre(x, y) freopen(#x ".in", "r", stdin), freopen(#y ".out", "w", stdout);
#include <bits/stdc++.h>
#define re register int
using namespace std;
const int Z = 520; typedef pair<int, int> paint;
inline int max(int a, int b) { return a > b ? a : b; } inline int min(int a, int b) { return a < b ? a : b; }

int n, m, k, ans;
char CH[Z];
int sqr[Z][Z], bel[Z][Z], block[Z * Z], num;
int dx[4] = {0, 1, 0, -1}, dy[4] = {1, 0, -1, 0};
queue <paint> q;
void bfs(int x, int y)//扩展连通块
{
    q.push(make_pair(x, y)); ++num;
    while (!q.empty())
    {
        int x = q.front().first, y = q.front().second; q.pop();
        if (bel[x][y]) continue;
        bel[x][y] = num; block[num]++;
        for (re i = 0; i < 4; i++)
        {
            int xx = x + dx[i], yy = y + dy[i];
            if (sqr[xx][yy] && !bel[xx][yy]) q.push(make_pair(xx, yy));
        }
    }
}
int sum[Z][Z];
inline void init()//障碍物的前缀和
{
    for (re i = 1; i <= n; i++)
        for (re j = 1; j <= n; j++)
            sum[i][j] = (!sqr[i][j]) + sum[i - 1][j] + sum[i][j - 1] - sum[i - 1][j - 1];
}
bool vs[Z * Z], in[Z * Z];
int clean[Z * Z], cs, res, tot;
inline void link(int x, int y)//新联通上的
{
    if (sqr[x][y] && !vs[bel[x][y]])
    {
        res += block[bel[x][y]];
        vs[bel[x][y]] = 1;
        clean[++cs] = bel[x][y];
    }
}
inline void cut(int x, int y)//左边需要出去的点
{
    if (sqr[x][y] && !vs[bel[x][y]])
    {
        tot -= block[bel[x][y]];
        vs[bel[x][y]] = 1;
        clean[++cs] = bel[x][y];
    }
}
inline void add(int x, int y)//右边新加入的点
{
    if (sqr[x][y] && !vs[bel[x][y]] && !in[bel[x][y]])
    {
        tot += block[bel[x][y]];
        in[bel[x][y]] = 1;
        clean[++cs] = bel[x][y];
    }
}

sandom main()
{
    cin >> n >> k;
    for (re i = 1; i <= n; i++) 
    {
        scanf("%s", CH + 1);
        for (re j = 1; j <= n; j++) sqr[i][j] = (CH[j] == '.');
    }
    for (re i = 1; i <= n; i++)//先找到所有的连通块
        for (re j = 1; j <= n; j++)
            if (sqr[i][j] && !bel[i][j]) bfs(i, j);
    init();
    for (re i = 1; i <= n; i++)//枚举子正方形的左上角
        for (re j = 1; j <= n; j++)
        {
            int x = i + k - 1, y = j + k - 1;
            if (x > n || y > n) continue;
            res = sum[x][y] + sum[i - 1][j - 1] - sum[i - 1][y] - sum[x][j - 1];
            for (re t = j; t <= y; t++) link(i - 1, t), link(x + 1, t);
            for (re t = i; t <= x; t++) link(t, j - 1), link(t, y + 1);
            if (j == 1)
            {
                tot = 0;//清空内部点
                for (re t = i; t <= x; t++) for (re s = j; s <= y; s++) add(t, s);//第一块暴力扫
            }
            else for (re t = i; t <= x; t++) add(t, y);
            ans = max(ans, res + tot);
            for (re t = i; t <= x; t++) cut(t, j);
            while (cs) vs[clean[cs]] = in[clean[cs]] = 0, cs--;
        }
    cout << ans;
    return 0;
}

T2.保险箱

什么伞兵数论题,不知道群是什么,虽然没有影响罢了。代码前摇:
\(G\)为最终的群,即所有可能的正确密码的集合。
1.若\(a,b, \in G\),则\(gcd(a, b), \in G\)。根据裴蜀定理,\(ax+by=c\)有解,当且仅当\(gcd(a, b) | c\),并且因为取模的存在,\(c\)一定可以取到\(gcd(a, b)\)
2.若\(a \in G\),则$gcd(a, n) \in G \(,也就是\)n \in G$,首先\(0\)一定\(\in G\),那么把\(0\)看作\(n\% n\);也可以设\(n=ak+r\),则\(n-r=ka \in G\),则\(r \in G\)\(\therefore gcd(a, r) \in G\), \(\because gcd(a, r) = gcd(a, n)\),(裴蜀定理),\(\therefore gdc(a, n) \in G\)
3.设群的最小表示元为\(g\),则\(g\)为群中所有数的\(gcd\),即\(g\)能整除\(G\)中的所有数。那么一个数如果\(\notin G\),那么它的所有因数也\(\notin G\)

代码
#define sandom signed
#define fre(x, y) freopen(#x ".in", "r", stdin), freopen(#y ".out", "w", stdout);
#include <bits/stdc++.h>
#define re register int
#define int long long 
using namespace std;
const int Z = 3e5 + 10;
inline int read() { int x = 0, f = 0; char c = getchar(); while (!isdigit(c)) f = c == '-', c = getchar(); while (isdigit(c)) x = (x << 1) + (x << 3) + (c ^ 48), c = getchar(); return f ? -x : x; }
inline int gcd(int a, int b) { return b ? gcd(b, a % b) : a; }

int n, k, d, m[Z], p[Z];
set <int> fac;
inline void divide(int x)//分解质因数(凑出x的所有因数)
{
    int t = sqrt(x);
    for (re i = 2; i <= t; i++)
        if (x % i == 0)
        {
            p[++p[0]] = i;
            while (x % i == 0) x /= i;
        }
    if (x != 1) p[++p[0]] = x;
}
inline void factor(int x)//x的所有因数(只有这些解有可能)
{
    int t = sqrt(x);
    for (re i = 1; i <= t; i++)
        if (x % i == 0)
        {
            fac.insert(i);
            if (i * i != x) fac.insert(x / i);
        }
}
void del(int x)//递归删除
{
    if (fac.find(x) == fac.end()) return;
    fac.erase(x);
    for (re i = 1; i <= p[0]; i++)//凡是x的因数的都删掉
        if (x % p[i] == 0) del(x / p[i]);
}

sandom main()
{
    n = read(), k = read();
    for (re i = 1; i <= k; i++) m[i] = read();
    d = gcd(m[k], n);//一定在这个范围里
    divide(d), factor(d);
    for (re i = 1; i < k; i++) del(gcd(m[i], d));
    cout << n / (*fac.begin());
    return 0;
}

T3.追逐

其实是一道比较板的换根\(dp\),暴力的树形\(dp\)应该很简单,枚举每一个点作为起点,定义\(dp[i][j]\),表示以\(i\)为出发点,使用\(j\)个磁铁的最大贡献。转移也很基础,就是分选与不选。\(dp[u][j]=max\{ dp[v][j], dp[v][j-1]+\sum a_v \}\)。之前在Accoders上做过一道换根\(dp\),类似的,只需要记录每一个点的子树中\(dp\)的最大值和次大值。并维护一个子树外的\(dp\)数组,转移方式基本一致,不再赘述。

代码
#define sandom signed
#define fre(x, y) freopen(#x ".in", "r", stdin), freopen(#y ".out", "w", stdout);
#include <bits/stdc++.h>
#define re register int
#define int long long 
using namespace std; typedef long long ll;
const int Z = 1e5 + 2;
inline int read() { int x = 0, f = 0; char c = getchar(); while (!isdigit(c)) f = c == '-', c = getchar(); while (isdigit(c)) x = (x << 1) + (x << 3) + (c ^ 48), c = getchar(); return f ? -x : x; }
inline int max(int a, int b) { return a > b ? a : b; } inline int min(int a, int b) { return a < b ? a : b; }

int n, m; ll ans;
struct edge { int v, ne; } e[Z << 1];
int head[Z], cnt;
inline void add(int x, int y) { e[++cnt] = edge{y, head[x]}; head[x] = cnt; }
int a[Z]; ll w[Z];
ll f[Z][102][2], g[Z][102][2];
int mx[Z][102][2], mn[Z][102][2];//最大值与次大值
inline void update(int rt, int son, int j, int op)
{
    if (f[mx[rt][j][op]][j][op] <= f[son][j][op]) mn[rt][j][op] = mx[rt][j][op], mx[rt][j][op] = son;
    else if (f[mn[rt][j][op]][j][op] < f[son][j][op]) mn[rt][j][op] = son;
}
inline int query(int rt, int son, int j, int op)
{
    if (mx[rt][j][op] == son) return mn[rt][j][op];
    else return mx[rt][j][op];
}
void dfs1(int rt, int fa)
{
    w[rt] = 0;
    for (re i = head[rt]; i; i = e[i].ne)
    {
        int son = e[i].v;
        if (son == fa) continue;
        dfs1(son, rt);
        w[rt] += a[son];
    }
    for (re j = 0; j <= m; j++)
    {
        for (re i = head[rt]; i; i = e[i].ne)
        {
            int son = e[i].v;
            if (son == fa) continue;
            update(rt, son, j, 0), update(rt, son, j, 1);
            f[rt][j][0] = max(f[rt][j][0], max(f[son][j][0], f[son][j][1]));
            if(j) f[rt][j][1] = max(f[rt][j][1], max(f[son][j - 1][0], f[son][j - 1][1]) + w[rt]);
        }
    }
}
void dfs2(int rt, int fa)
{
    ll mx0 = 0, mx1 = 0;
    for (re j = 0; j <= m; j++)
    {
        mx0 = max(max(g[fa][j][0], g[fa][j][1]), max(f[mx[rt][j][0]][j][0], f[mx[rt][j][1]][j][1]));
        if(j) mx1 = max(max(g[fa][j - 1][0], g[fa][j - 1][1]), max(f[mx[rt][j - 1][0]][j - 1][0], f[mx[rt][j - 1][1]][j - 1][1])) + w[rt] + a[fa];
        ans = max(ans, max(mx0, mx1));
    }
    for (re i = head[rt]; i; i = e[i].ne)
    {
        int son = e[i].v;
        if (son == fa) continue;
        for (re j = 0; j <= m; j++)
        {
            mx0 = query(rt, son, j, 0), mx1 = query(rt, son, j, 1);
            g[rt][j][0] = max(max(g[fa][j][0], g[fa][j][1]), max(f[mx0][j][0], f[mx1][j][1]));
            if (j) mx0 = query(rt, son, j - 1, 0), mx1 = query(rt, son, j - 1, 1);
            if (j) g[rt][j][1] = max(max(g[fa][j - 1][0], g[fa][j - 1][1]), max(f[mx0][j - 1][0], f[mx1][j - 1][1])) + w[rt] + a[fa] - a[son];
        }
        dfs2(son, rt);
    }
}

sandom main()
{
    n = read(), m = read();
    for (re i = 1; i <= n; i++) a[i] = read();
    for (re i = 1; i < n; i++)
    {
        int x = read(), y = read();
        add(x, y), add(y, x);
    }
    dfs1(1, 0); dfs2(1, 0);
    cout << ans;
    return 0;
}

T4.字符串

把题意转换一下就变成了山海经??把\(C、T\)分别看做\(1、-1\),那么实际上最后的序列应该是所有前缀(\(pre\))和后缀(\(suf\))都是\(\ge 0\)的。首先考虑前缀,发现删去的位置分别是\(pre_i\)第一次等于\(-1、-2、-3……\),这个结论很显然。那么对于前缀需要操作的次数显然就是\(-min\{pre_i\}\),这时后缀发生了变化,记为\(suf_i`\),单独考虑\(p\)这个位置的后缀什么时候发生变化:它前面的修改对它没有影响,而后面的每一次修改都会\(+1\),所以我们得到\(suf_i`=suf_i-min_{pre}+min_{p< i}\{ pre_p \}\),最后的答案就是\(-(min_{p<q}\{pre_p\}+min\{suf_q\})\),说白了就是求一个前缀与后缀加和的最小值,且不能覆盖,可以转化成求一个序列的最大子段和,总和减去最大子段和就是答案。因为有负号,取个相反数就行了。

代码
#define sandom signed
#define fre(x, y) freopen(#x ".in", "r", stdin), freopen(#y ".out", "w", stdout);
#include <bits/stdc++.h>
#define re register int
using namespace std; int wrt[20], TP;
const int Z = 5e5 + 10;
inline int read() { int x = 0, f = 0; char c = getchar(); while (!isdigit(c)) f = c == '-', c = getchar(); while (isdigit(c)) x = (x << 1) + (x << 3) + (c ^ 48), c = getchar(); return f ? -x : x; }
inline void write(int x) { TP = 0; if (x < 0) putchar('-'), x = -x; while (x >= 10) wrt[++TP] = x % 10, x /= 10; wrt[++TP] = x; while (TP) putchar(wrt[TP--] | 48); putchar('\n'); }
inline int max(int a, int b) { return a > b ? a : b; } inline int min(int a, int b) { return a < b ? a : b; }

int n, m, q;
char s[Z];
struct tree
{
    int pre, suf, max, sum;
    #define lk (rt << 1)
    #define rk (rt << 1 | 1)
    #define mid (L + R >> 1)
}; tree tr[Z << 2], ans;
inline tree pushup(tree lc, tree rc)
{
    tree rt;
    rt.sum = lc.sum + rc.sum;
    rt.pre = lc.pre, rt.suf = rc.suf;
    rt.pre = max(lc.pre, lc.sum + rc.pre);
    rt.suf = max(rc.suf, rc.sum + lc.suf);
    rt.max = max(max(lc.max, rc.max), max(rt.pre, rt.suf));
    rt.max = max(rt.max, lc.suf + rc.pre);
    return rt;
}
void build(int rt, int L, int R)
{
    if (L == R)
    {
        tr[rt].pre = tr[rt].suf = tr[rt].max = tr[rt].sum = (s[L] == 'C') ? 1 : -1;
        tr[rt].max = max(tr[rt].max, 0);
        return;
    }
    build(lk, L, mid), build(rk, mid + 1, R);
    tr[rt] = pushup(tr[lk], tr[rk]);
}
tree query(int rt, int L, int R, int l, int r)
{
    if (l <= L && r >= R) return tr[rt];
    if (r <= mid) return query(lk, L, mid, l, r);
    else if (l > mid) return query(rk, mid + 1, R, l, r);
    else return pushup(query(lk, L, mid, l, r), query(rk, mid + 1, R, l, r));
}

sandom main()
{
    n = read(); scanf("%s", s + 1); q = read();
    build(1, 1, n);
    while (q--)
    {
        int l = read(), r = read();
        ans = query(1, 1, n, l, r);
        write(ans.max - ans.sum);
    }
    return 0;
}
posted @ 2022-10-02 21:19  sandom  阅读(30)  评论(0编辑  收藏  举报