还是比去年要难的。

种花

Description

给一个 $n\times m$ 的 01 矩阵,问其中有多少个由 0 组成的 C 形状和 F 形状。

$n,m\leq1000$

Solution

从上向下扫,记录每一列 $\Gamma$ 形状的数量和二阶 $\Gamma$ 形状(其实就是 F)的数量即可,利用行后缀 0 数量统计即可。

Code

#include <iostream>
#include <cstdio>
#include <algorithm>
#include <cstring>
#include <vector>
using namespace std;

namespace solve
{
    const int maxn = 1010;
    typedef long long ll;
    const int mod = 998244353;

    int a[maxn][maxn], n, m, c, f;

    void main()
    {
        cin >> n >> m >> c >> f;
        for (int i = 1; i <= n; i++)
            for (int j = 1; j <= m; j++)
            {
                char ch;
                cin >> ch;
                a[i][j] = ch - '0';
            }
        static ll C[maxn], suf[maxn], sum[maxn];
        memset(C, 0, sizeof(C)), memset(suf, 0, sizeof(suf)), memset(sum, 0, sizeof(sum));
        ll resc = 0, resf = 0;
        for (int i = 1; i <= n; i++)
        {
            for (int j = m; j >= 1; j--)
            {
                if (a[i][j] == 1)
                {
                    C[j] = suf[j] = sum[j] = 0;
                    continue;
                }
                (resc += 1ll * C[j] * suf[j + 1] % mod) %= mod;
                (resf += sum[j]) %= mod;
                (sum[j] += C[j] * 1ll * suf[j + 1] % mod) %= mod;
                if (suf[j])
                    C[j] += suf[j] - 1;
                suf[j] = suf[j + 1] + 1;
            }
        }
        cout << resc * c % mod << " " << resf * f % mod << '\n';
    }
}

int main()
{
    // freopen("plant.in", "r", stdin), freopen("plant.out", "w", stdout);
    ios::sync_with_stdio(false), cin.tie(0), cout.tie(0);
    int T, id;
    cin >> T >> id;
    while (T--)
        solve::main();
}

喵了个喵

Description

有一个长度为 $m$ 的卡牌队列,共有 $k$ 种不同图案的卡牌,你有 $n$ 个栈。有两种操作。

  • 选择一个栈,将牌堆顶上的卡牌放入栈的顶部。如果这么操作后,这个栈最上方的两张牌有相同的图案,则会自动将这两张牌消去。

  • 选择两个不同的栈,如果这两个栈栈底的卡牌有相同的图案,则可以将这两张牌消去,原来在栈底上方的卡牌会成为新的栈底。如果不同,则什么也不会做。

$m\leq 2\times 10^6,n\leq300, k=2n-1,1\leq a_i\leq k$。

有一个部分分是 $k=2n-2$。

Solution

一眼看起来非常神秘,完全不可做,题目保证了 $k=2n-2$ 或 $k=2n-1$,只能从此入手。

首先当一个栈顶的牌和要放的牌一样时,显然一定要消掉。

当 $k=2n-2$,一个显然的做法是留出一个空栈,其他的栈都放两张不同的牌,这样再来一张牌肯定能消掉一张牌,就赢了。

当 $k=2n-1$,可以尝试让一个栈里面有 $3$ 种牌,然后对于这一堆,要保证最下面的牌在后面的队列里先出现。

考虑到我们可能被迫会往剩下的那个空栈放牌,我们要保证在出现这种情况后,被盖在下面的牌不会寄,这就对当若干个栈里只有一张牌的时候,新牌放哪个栈里有要求。

当没有一个合法的两牌栈的时候,我们优先把这张牌放到出现最晚的牌上面。这样就能满足我们的要求了。

Code


#include <iostream>
#include <cstdio>
#include <algorithm>
#include <cstring>
#include <vector>
#include <array>
#include <cassert>
using namespace std;

namespace solve
{
    const int maxn = 2e6 + 10;

    vector<int> s[maxn];
    int n, m, KKK, a[maxn], nxt[maxn], lst[maxn], match[maxn], pos[maxn];
    
    void main()
    {
        cin >> n >> m >> KKK;
        for (int i = 1; i <= KKK; i++)
            match[i] = pos[i] = lst[i] = 0;
        for (int i = 1; i <= m; i++)
            cin >> a[i];
        for (int i = m; i >= 1; i--)
            nxt[i] = lst[a[i]], lst[a[i]] = i;
        for (int i = 1; i <= n; i++)
            s[i].clear();
        vector<array<int, 3>> ans;
        for (int i = 1; i <= m; i++)
        {
            if (pos[a[i]])
            {
                int x = pos[a[i]];
                pos[a[i]] = 0;
                if (s[x].back() == a[i])
                    ans.push_back({0, x, 0}), s[x].pop_back();
                else
                {
                    int p = 0;
                    for (int i = 1; i <= n; i++)
                        if (s[i].size() == 0)
                        {
                            p = i;
                            break;
                        }
                    ans.push_back({0, p, 0}), ans.push_back({1, x, p});
                    assert(s[x][0] == a[i]);
                    s[x].erase(s[x].begin());
                }
            }
            else
            {
                int p = 0;
                auto find1 = [&]()
                {
                    for (int j = 1; j <= n; j++)
                        if (s[j].size() == 2 && match[s[j][0]] < match[s[j][1]])
                            return j;
                    return 0;
                };
                auto find2 = [&]()
                {
                    int mx = 0, res = 0;
                    for (int j = 1; j <= n; j++)
                        if (s[j].size() == 1 && match[s[j][0]] > mx)
                            mx = match[s[j][0]], res = j;
                    return res;
                };
                auto find3 = [&]()
                {
                    for (int j = 1; j <= n; j++)
                        if (s[j].size() == 0)
                            return j;
                    assert(0);
                    return 0;
                };
                p = find1();
                if (!p)
                    p = find2();
                if (!p)
                    p = find3();
                pos[a[i]] = p, s[p].push_back(a[i]), ans.push_back({0, p, 0});
                match[a[i]] = nxt[i];
            }
        }
        cout << ans.size() << '\n';
        for (const auto &o : ans)
            if (o[0] == 0)
                cout << "1 " << o[1] << '\n';
            else
                cout << "2 " << o[1] << " " << o[2] << '\n';
    }
}

int main()
{
    // freopen("meow.in", "r", stdin), freopen("meow.out", "w", stdout);
    ios::sync_with_stdio(false), cin.tie(0), cout.tie(0);
    int T;
    cin >> T;
    while (T--)
        solve::main();
}

建造军营

Description

有一个 $n$ 个点,$m$ 个边的无向图,A 国要选择至少一个城市建造军营。B 国可以摧毁一条道路。

现在 A 国可以保护任意条道路,被保护的道路不会被 B 国摧毁。A 国希望制定一个建造军营和看守道路的方案,使得 B 国袭击的无论是 A 国的哪条道路,都不会造成某两座军营无法互相到达。求可能的建造军营和看守道路的方案数共有多少。

$n\leq5\times10^5,m\leq10^6$。

Solution

对于一个边双,摧毁是没用的,启发我们边双缩点成一个树。

然后考虑树形dp,每种方案在若干个要建兵营的点的lca处贡献答案。计算的时候保护一次边答案就记得除以 $2$,最后再乘上 $2^m$ 即可得到最终答案。

Code

#include <iostream>
#include <cstdio>
#include <algorithm>
#include <cstring>
#include <vector>
#include <array>
#include <cassert>
using namespace std;

namespace solve
{
    const int maxn = 1e6 + 10;
    const int mod = 1e9 + 7;
    const int inv2 = (mod + 1) / 2;
    typedef long long ll;

    ll mi[maxn];

    vector<int> e[maxn], e2[maxn];
    inline void add(int x, int y) { e[x].push_back(y), e[y].push_back(x); }

    int dfn[maxn], low[maxn], stk[maxn], top, cnt;
    int scc[maxn], tot, siz[maxn];

    int n, m;

    void tarjan(int x, int fa)
    {
        stk[++top] = x, dfn[x] = low[x] = ++cnt;
        for (int v : e[x])
            if (v != fa)    
            {
                if (!dfn[v])
                    tarjan(v, x);
                low[x] = min(low[x], low[v]);
            }
        if (low[x] == dfn[x])
        {
            tot++;
            int now = 0;
            while (now != x)
            {
                now = stk[top--];
                siz[tot]++, scc[now] = tot;
            }
        }
    }

    ll ans = 0;

    ll f[maxn];

    void dfs(int x, int fa)
    {
        (ans += f[x] = mi[siz[x]] - 1) %= mod;
        for (int v : e2[x])
            if (v != fa)
            {
                dfs(v, x);
                (ans += f[x] * f[v] % mod * inv2 % mod) %= mod;
                (f[x] += f[v] * (f[x] + 1) % mod * inv2 % mod) %= mod;
                // 这里只在子树里的情况已经在儿子处统计过了,因此f[x]要加上的是f[v]/2而不是f[v]
            }
    }

    void main()
    {
        mi[0] = 1;
        for (int i = 1; i < maxn; i++)
            mi[i] = mi[i - 1] * 2 % mod;
        cin >> n >> m;
        for (int i = 0, x, y; i < m; i++)
            cin >> x >> y, add(x, y);
        tarjan(1, 0);
        for (int i = 1; i <= n; i++)
            for (int v : e[i])
                if (scc[i] != scc[v])
                    e2[scc[i]].push_back(scc[v]);
        dfs(1, 0);
        cout << ans * mi[m] % mod << endl;
    }
}

int main()
{
    // freopen("barrack.in", "r", stdin), freopen("barrack.out", "w", stdout);
    ios::sync_with_stdio(false), cin.tie(0), cout.tie(0);
    solve::main();
}

比赛

Description

给出两个长度为 $n$ 的序列 $a, b$,$q$ 次询问,每次询问给出 $l,r$,求出

$$ \sum_{p = l}^r \sum_{q = p}^r \bigl(\max\nolimits_{i=p}^q a_i\bigr)\bigl(\max\nolimits_{i=p}^q b_i\bigr). $$

$n,q\leq2.5\times10^5$。

Solution

和其他题解说的一样,这道题可以离线询问,把询问挂在右端点上,进行扫描线。这部分别的题解都说了,我就简单说一下。

扫描右端点(记为 $pos$)的同时,用线段树各自维护每个位置到当前右端点中 $a,b$ 的最大值 $x_i,y_i$,用单调栈可以得出每次新数影响的区间,对应线段树的区间赋值操作。接着我们需要把 $[1,pos]$ 的答案都加上 $x_i\times y_i$。

整理一下需要支持的操作:

  • 序列 $x$ 区间赋值;

  • 序列 $y$ 区间赋值;

  • 区间答案加上 $x_iy_i$;

  • 查询区间答案和。

三操作提示我们记录区间 $\sum x_iy_i$,而为了维护区间 $x_i,y_i$,我们需要维护 $\sum x_i,\sum y_i$ 和区间长度 $len$。

直接做可能不太方便,我们可以用矩阵方便地描述转移:

答案矩阵是

$$ \begin{bmatrix} ans \\ \sum xy \\ \sum x \\ \sum y \\ len \end{bmatrix} $$

赋值 $x$,赋值 $y$,区间加 $xy$ 都可以用矩阵表示: $$ \begin{bmatrix} 1 & 0 & 0 & 0 & 0 \\ 0 & 0 & 0 & x & 0 \\ 0 & 0 & 0 & 0 & x \\ 0 & 0 & 0 & 1 & 0 \\ 0 & 0 & 0 & 0 & 1 \end{bmatrix} \begin{bmatrix} 1 & 0 & 0 & 0 & 0 \\ 0 & 0 & y & 0 & 0 \\ 0 & 0 & 1 & 0 & 0 \\ 0 & 0 & 0 & 0 & y \\ 0 & 0 & 0 & 0 & 1 \end{bmatrix} \begin{bmatrix} 1 & 1 & 0 & 0 & 0 \\ 0 & 1 & 0 & 0 & 0 \\ 0 & 0 & 1 & 0 & 0 \\ 0 & 0 & 0 & 1 & 0 \\ 0 & 0 & 0 & 0 & 1 \end{bmatrix} $$

直接用线段树维护矩阵,复杂度 $O(5^3q\log n)$,进行一些普通的常数优化后在 loj 上可以卡着时限通过,但洛谷显然不行。

注意到矩阵很稀疏,打一个表可以发现很多地方永远会是 $0$,因此可以把矩阵乘法展开,手动乘,常数会下降很多。

Code

在洛谷上最大点跑 1.55s,不算太慢。

#include <iostream>
#include <cstdio>
#include <algorithm>
#include <cstring>
#include <vector>
#include <cassert>
using namespace std;

bool MEM1;

namespace IO
{
    const int mxsiz = 1 << 14;

    char inbuf[mxsiz], *p1, *p2;
    char outbuf[mxsiz], *op = outbuf;

    inline void flush() { fwrite(outbuf, 1, op - outbuf, stdout), op = outbuf; }
    struct endio
    {
        endio(){};
        ~endio() { flush(); }
    } useless_var;
    inline char gc()
    {
        if (p1 == p2)
            p1 = inbuf, p2 = p1 + fread(inbuf, 1, mxsiz, stdin);
        return p1 == p2 ? EOF : *p1++;
    }
    inline int read()
    {
        int x = 0, f = 1;
        char ch = gc();
        for (; !isdigit(ch); ch = gc())
            if (ch == '-')
                f = -1;
        for (; isdigit(ch); ch = gc())
            x = x * 10 + ch - '0';
        return x * f;
    }
    template <typename T>
    inline void read(T &x)
    {
        x = 0;
        int f = 1;
        char ch = gc();
        for (; !isdigit(ch); ch = gc())
            if (ch == '-')
                f = -1;
        for (; isdigit(ch); ch = gc())
            x = x * 10 + ch - '0';
        x *= f;
    }
    template <typename T, typename... Args>
    inline void read(T &x, Args &...args) { read(x), read(args...); }
    inline void read(char &ch)
    {
        ch = gc();
        while (!isgraph(ch))
            ch = gc();
    }
    inline void push(char ch)
    {
        if (op - outbuf == mxsiz)
            fwrite(outbuf, 1, mxsiz, stdout), op = outbuf;
        *op++ = ch;
    }
    template <typename... Args>
    inline void push(char ch, Args... args) { push(ch), push(args...); }
    inline void endln() { push('\n'); }
    inline void space() { push(' '); }
    template <typename T>
    inline void work_wt(T x)
    {
        if (x > 9)
            work_wt(x / 10);
        push(x % 10 + '0');
    }
    template <typename T>
    inline void write(T x)
    {
        if (x < 0)
            x = -x, push('-');
        work_wt(x);
    }
    inline void write(char ch) { push(ch); }
    inline void write(const char *s)
    {
        int n = strlen(s);
        for (int i = 0; i < n; i++)
            push(s[i]);
    }
    template <typename T, typename... Args>
    inline void write(T x, Args... args) { write(x), write(args...); }
    template <typename T>
    inline void write_with_space(T x) { write(x), space(); }
    template <typename T, typename... Args>
    inline void write_with_space(T x, Args... args) { write_with_space(x), write_with_space(args...); }
    template <typename... Args>
    inline void writeln(Args... x) { write_with_space(x...), endln(); }
};
using IO::endln;
using IO::gc;
using IO::read;
using IO::space;
using IO::write;
using IO::write_with_space;
using IO::writeln;

namespace solve
{
    typedef unsigned long long ull;
    const int maxn = 2.5e5 + 10;

    const vector<int> qwq[5] =
        {
            {0, 1, 2, 3, 4},
            {1, 2, 3, 4},
            {2, 4},
            {3, 4},
            {4}};

    struct matrix
    {
        ull a[5][5];
        int n, m;
        const ull *operator[](int x) const { return a[x]; }
        matrix operator+(const matrix &b) const
        {
            matrix res;
            for (int i = 0; i < 5; i++)
                for (int j = 0; j < 1; j++)
                    res.a[i][j] = a[i][j] + b[i][j];
            return res;
        }
        matrix operator*(const matrix &b) const
        {
            matrix res;
            res.clear();
            /*
11111
01111
00101
00011
00001
            */
            // for (int i = 0; i < 5; i++)
            //     for (int k : qwq[i])
            //         for (int j : qwq[k])
            //             res.a[i][j] += a[i][k] * b[k][j];
            res.a[0][0] = a[0][0] * b[0][0];
            res.a[0][1] = a[0][0] * b[0][1] + a[0][1] * b[1][1];
            res.a[0][2] = a[0][0] * b[0][2] + a[0][1] * b[1][2] + a[0][2] * b[2][2];
            res.a[0][3] = a[0][0] * b[0][3] + a[0][1] * b[1][3] + a[0][3] * b[3][3];
            res.a[0][4] = a[0][0] * b[0][4] + a[0][1] * b[1][4] + a[0][2] * b[2][4] + a[0][3] * b[3][4] + a[0][4] * b[4][4];
            res.a[1][1] = a[1][1] * b[1][1];
            res.a[1][2] = a[1][1] * b[1][2] + a[1][2] * b[2][2] + a[1][3] * b[3][2];
            res.a[1][3] = a[1][1] * b[1][3] + a[1][3] * b[3][3];
            res.a[1][4] = a[1][1] * b[1][4] + a[1][2] * b[2][4] + a[1][3] * b[3][4] + a[1][4] * b[4][4];
            res.a[2][2] = a[2][2] * b[2][2];
            res.a[2][4] = a[2][2] * b[2][4] + a[2][4] * b[4][4];
            res.a[3][3] = a[3][3] * b[3][3];
            res.a[3][4] = a[3][3] * b[3][4] + a[3][4] * b[4][4];
            res.a[4][4] = a[4][4] * b[4][4];
            return res;
        }
        matrix operator&(const matrix &b) const
        {
            matrix res;
            res.clear();
            for (int i = 0; i < 5; i++)
                for (int k = 0; k < 5; k++)
                    for (int j = 0; j < 1; j++)
                        res.a[i][j] += a[i][k] * b[k][j];
            return res;
        }
        void init()
        {
            memset(a, 0, sizeof(a));
            a[0][0] = a[1][1] = a[2][2] = a[3][3] = a[4][4] = 1;
        }
        void clear()
        {
            memset(a, 0, sizeof(a));
        }
    } lzy[maxn << 2], sum[maxn << 2];
    bool vis[maxn << 2];

    matrix opt3 =
        {
            {{1, 1, 0, 0, 0},
             {0, 1, 0, 0, 0},
             {0, 0, 1, 0, 0},
             {0, 0, 0, 1, 0},
             {0, 0, 0, 0, 1}},
            5,
            5};
    inline matrix opt1(ull x)
    {
        return {
            {{1, 0, 0, 0, 0},
             {0, 0, 0, x, 0},
             {0, 0, 0, 0, x},
             {0, 0, 0, 1, 0},
             {0, 0, 0, 0, 1}},
            5,
            5};
    }
    inline matrix opt2(ull x)
    {
        return {
            {{1, 0, 0, 0, 0},
             {0, 0, x, 0, 0},
             {0, 0, 1, 0, 0},
             {0, 0, 0, 0, x},
             {0, 0, 0, 0, 1}},
            5,
            5};
    }

    ull a[maxn], b[maxn];

    int n, m;

    struct Q
    {
        int l, r, id;
    } q[maxn];

    inline int ls(int x) { return x * 2; }
    inline int rs(int x) { return x * 2 + 1; }

    inline void push_up(int x)
    {
        sum[x] = sum[ls(x)] + sum[rs(x)];
    }
    inline void push_tag(int k, const matrix &x)
    {
        sum[k] = x & sum[k];
        lzy[k] = x * lzy[k];
        vis[k] = 1;
    }
    inline void push_down(int k)
    {
        if (!vis[k])
            return;
        push_tag(ls(k), lzy[k]), push_tag(rs(k), lzy[k]);
        vis[k] = 0, lzy[k].init();
    }

    void build(int l, int r, int k)
    {
        lzy[k].init(), lzy[k].n = lzy[k].m = 5;
        sum[k].n = 5, sum[k].m = 1;
        if (l == r)
        {
            sum[k].a[4][0] = 1, sum[k].a[3][0] = b[l], sum[k].a[2][0] = a[l], sum[k].a[1][0] = b[l] * a[l];
            return;
        }
        int mid = (l + r) / 2;
        build(l, mid, ls(k)), build(mid + 1, r, rs(k));
        push_up(k);
    }

    void modify(int l, int r, int x, int y, const matrix &v, int k)
    {
        if (l >= x && r <= y)
        {
            push_tag(k, v);
            return;
        }
        push_down(k);
        int mid = (l + r) / 2;
        if (x <= mid)
            modify(l, mid, x, y, v, ls(k));
        if (y > mid)
            modify(mid + 1, r, x, y, v, rs(k));
        push_up(k);
    }

    ull query(int l, int r, int x, int y, int k)
    {
        if (l >= x && r <= y)
            return sum[k][0][0];
        push_down(k);
        int mid = (l + r) / 2;
        ull res = 0;
        if (x <= mid)
            res += query(l, mid, x, y, ls(k));
        if (y > mid)
            res += query(mid + 1, r, x, y, rs(k));
        return res;
    }

    ull ans[maxn];

    void main()
    {
        read(n);
        for (int i = 1; i <= n; i++)
            read(a[i]);
        for (int i = 1; i <= n; i++)
            read(b[i]);
        read(m);
        for (int i = 1; i <= m; i++)
            read(q[i].l, q[i].r), q[i].id = i;
        sort(q + 1, q + 1 + m, [&](const Q &x, const Q &y) { return x.r != y.r ? x.r < y.r : x.l < y.l; });
        int pos = 1;
        static int stk1[maxn], stk2[maxn], top1 = 0, top2 = 0;
        build(1, n, 1);
        for (int i = 1; i <= n; i++)
        {
            while (top1 && a[stk1[top1]] < a[i])
                top1--;
            while (top2 && b[stk2[top2]] < b[i])
                top2--;
            modify(1, n, stk1[top1] + 1, i, opt1(a[i]), 1);
            modify(1, n, stk2[top2] + 1, i, opt2(b[i]), 1);
            modify(1, n, 1, i, opt3, 1);
            stk1[++top1] = i, stk2[++top2] = i;
            while (pos <= m && q[pos].r == i)
                ans[q[pos].id] = query(1, n, q[pos].l, i, 1), pos++;
        }
        for (int i = 1; i <= m; i++)
            write(ans[i]), endln();
    }
}

bool MEM2;

int main()
{
    ios::sync_with_stdio(false), cin.tie(0);
    int id;
    read(id);
    solve::main();
}