IT数码 购物 网址 头条 软件 日历 阅读 图书馆
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
图片批量下载器
↓批量下载图片,美女图库↓
图片自动播放器
↓图片自动播放器↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁
 
   -> 数据结构与算法 -> AtCoder abc256全题解(区间合并模板、矩阵快速幂优化dp、线段树……) -> 正文阅读

[数据结构与算法]AtCoder abc256全题解(区间合并模板、矩阵快速幂优化dp、线段树……)


传送门

本文CSDN

本文juejin

作者:hans774882968以及hans774882968

A

水,略。

B

模拟即可。

#include <bits/stdc++.h>
using namespace std;
typedef long long LL;
typedef pair<int, int> pii;
#define rep(i,a,b) for(int i = (a);i <= (b);++i)
#define re_(i,a,b) for(int i = (a);i < (b);++i)
#define dwn(i,a,b) for(int i = (a);i >= (b);--i)

const int N = 5 + 5;

int n, a[N];

void dbg() {
    puts ("");
}
template<typename T, typename... R>void dbg (const T &f, const R &... r) {
    cout << f << " ";
    dbg (r...);
}
template<typename Type>inline void read (Type &xx) {
    Type f = 1;
    char ch;
    xx = 0;
    for (ch = getchar(); ch < '0' || ch > '9'; ch = getchar() ) if (ch == '-') f = -1;
    for (; ch >= '0' && ch <= '9'; ch = getchar() ) xx = xx * 10 + ch - '0';
    xx *= f;
}
void read() {}
template<typename T, typename ...R>void read (T &x, R &...r) {
    read (x);
    read (r...);
}

int main() {
    read (n);
    int ans = 0;
    rep (_, 1, n) {
        int x;
        read (x);
        a[0]++;
        dwn (i, 3, 0) {
            if (i + x < 4) a[i + x] += a[i];
            else ans += a[i];
            a[i] = 0;
        }
    }
    printf ("%d\n", ans);
    return 0;
}

C-枚举

题意:有一个九宫格,每格填一个正整数,输入3 <= h[0~2] <= 30, 3 <= w[0~2] <= 30分别表示期望的每行和每列的数字的和。求方案数。

只需要枚举4个格子就能确定所有的数了,计算量30^4完全可行。我选择的是(0,0), (0,1), (1,0), (1,1)这4个格子。

#include <bits/stdc++.h>
using namespace std;
typedef long long LL;
typedef pair<int, int> pii;
#define rep(i,a,b) for(int i = (a);i <= (b);++i)
#define re_(i,a,b) for(int i = (a);i < (b);++i)
#define dwn(i,a,b) for(int i = (a);i >= (b);--i)

const int N = 2e5 + 5;

int n, w[5], h[5];

void dbg() {
    puts ("");
}
template<typename T, typename... R>void dbg (const T &f, const R &... r) {
    cout << f << " ";
    dbg (r...);
}
template<typename Type>inline void read (Type &xx) {
    Type f = 1;
    char ch;
    xx = 0;
    for (ch = getchar(); ch < '0' || ch > '9'; ch = getchar() ) if (ch == '-') f = -1;
    for (; ch >= '0' && ch <= '9'; ch = getchar() ) xx = xx * 10 + ch - '0';
    xx *= f;
}
void read() {}
template<typename T, typename ...R>void read (T &x, R &...r) {
    read (x);
    read (r...);
}

int main() {
    n = 3;
    re_ (i, 0, n) read (w[i]);
    re_ (i, 0, n) read (h[i]);
    int ans = 0;
    re_ (i0, 1, min (w[0], h[0]) - 1) {
        re_ (i1, 1, min (w[1], h[0]) - 1) {
            re_ (i3, 1, min (w[0], h[1]) - 1) {
                re_ (i4, 1, min (w[1], h[1]) - 1) {
                    int i2 = h[0] - i0 - i1, i5 = h[1] - i3 - i4, i6 = w[0] - i0 - i3, i7 = w[1] - i1 - i4;
                    if (i2 > 0 && i5 > 0 && i6 > 0 && i7 > 0) {
                        int i81 = h[2] - i6 - i7, i82 = w[2] - i2 - i5;
                        if (i81 == i82 && i81 > 0) ++ans;
                    }
                }
            }
        }
    }
    printf ("%d\n", ans);
    return 0;
}

D-区间合并模板

区间合并模板,参考:https://blog.nowcoder.net/n/834a656e47df44e58e830fdd87d3e253。

#include <bits/stdc++.h>
using namespace std;
typedef long long LL;
typedef pair<int, int> pii;
#define rep(i,a,b) for(int i = (a);i <= (b);++i)
#define re_(i,a,b) for(int i = (a);i < (b);++i)
#define dwn(i,a,b) for(int i = (a);i >= (b);--i)

const int N = 2e5 + 5;

int n;
pii a[N];

void dbg() {
    puts ("");
}
template<typename T, typename... R>void dbg (const T &f, const R &... r) {
    cout << f << " ";
    dbg (r...);
}
template<typename Type>inline void read (Type &xx) {
    Type f = 1;
    char ch;
    xx = 0;
    for (ch = getchar(); ch < '0' || ch > '9'; ch = getchar() ) if (ch == '-') f = -1;
    for (; ch >= '0' && ch <= '9'; ch = getchar() ) xx = xx * 10 + ch - '0';
    xx *= f;
}
void read() {}
template<typename T, typename ...R>void read (T &x, R &...r) {
    read (x);
    read (r...);
}

int main() {
    read (n);
    rep (i, 1, n) read (a[i].first, a[i].second);
    sort (a + 1, a + n + 1);
    vector<pii> ans;
    for (int i = 1; i <= n; ++i) {
        int l = a[i].first, r = a[i].second;
        while (i <= n && a[i + 1].first <= r) r = max (r, a[++i].second);
        ans.push_back ({l, r});
    }
    for (pii v : ans) printf ("%d %d\n", v.first, v.second);
    return 0;
}

E-图论建模,函数图的性质

题意

n <= 2e5个人排队拿糖果。输入长为n的两个数组x, c,表示如果i号人排在x[i]号人后面,则i号人会受到c[i]的伤害。请你求一个排列,使所有人受到的总伤害最小。保证i ≠ x[i]

思路

尝试往逆序对、二分、贪心和自定义排序等方面想,一无所获。只好看答案,没想到是图论题!

这个图有n条有向边,i -> x[i],官方题解指出这种图叫做functional graph,顾名思义,以i为自变量,x[i]为因变量,确实符合函数定义。函数图有如下特殊性质:它的每个无向图意义下的连通分量恰好有一个环。官方题解用数学归纳法,用了不少篇幅来证明。其实感性上更好理解:每个点都必须有一个出度,而DAG拓扑序最大的点出度为0,故必定有环。而环套环要求有些点出度至少为2。

这样每个连通分量就分为非环的部分和环上的部分。对于非环的部分,直接定顺序为拓扑序则伤害为0,最优。环上的部分至少有一个人会受伤害,那么我们只让伤害值最小的人受伤(排最后)即可,贡献min(c[y1],c[y2],...)

代码

  1. uf[]标记无向图意义下的连通分量是否被访问过。简单的并查集。
  2. 为了得到环上的所有点:vis[]是中间变量,ui开始,跳到第一次vis[u] = true的时候,u是环上的一点,接着跳就能得到环上的所有点了。
#include <bits/stdc++.h>
using namespace std;
typedef long long LL;
typedef pair<int, int> pii;
#define rep(i,a,b) for(int i = (a);i <= (b);++i)
#define re_(i,a,b) for(int i = (a);i < (b);++i)
#define dwn(i,a,b) for(int i = (a);i >= (b);--i)

const int N = 2e5 + 5;

int n, c[N], a[N], fa[N];
bool vis[N], uf[N];

void dbg() {
    puts ("");
}
template<typename T, typename... R>void dbg (const T &f, const R &... r) {
    cout << f << " ";
    dbg (r...);
}
template<typename Type>inline void read (Type &xx) {
    Type f = 1;
    char ch;
    xx = 0;
    for (ch = getchar(); ch < '0' || ch > '9'; ch = getchar() ) if (ch == '-') f = -1;
    for (; ch >= '0' && ch <= '9'; ch = getchar() ) xx = xx * 10 + ch - '0';
    xx *= f;
}
void read() {}
template<typename T, typename ...R>void read (T &x, R &...r) {
    read (x);
    read (r...);
}

int find (int x) {
    return x == fa[x] ? x : fa[x] = find (fa[x]);
}

int main() {
    read (n);
    re_ (i, 0, n) read (c[i]), c[i]--;
    re_ (i, 0, n) read (a[i]);
    re_ (i, 0, n) fa[i] = i;
    re_ (i, 0, n) {
        int f1 = find (i), f2 = find (c[i]);
        fa[f1] = f2;
    }
    LL ans = 0;
    re_ (i, 0, n) {
        int fi = find (i);
        if (uf[fi]) continue;
        uf[fi] = true;
        vis[i] = true;
        int u = c[i];
        while (!vis[u]) {
            vis[u] = true;
            u = c[u];
        }
        int mn = a[u];
        for (int v = c[u]; v != u; v = c[v])
            mn = min (mn, a[v]);
        ans += mn;
    }
    printf ("%lld\n", ans);
    return 0;
}

F-树状数组

题意

给你长为n <= 2e5的数组。有两种操作:

  1. 单点修改。
  2. 求3阶前缀和D[x](见原题,1-indexed),模998244353。

思路

套路题。首先考虑把它写成只与a[1], a[2], ..., a[x]有关的形式。对于二阶前缀和,用贡献思想很容易发现是C[x] = x*a[1]+(x-1)*a[2]+...+1*a[x],所以D[x] = (x+2-1)*(x+1-1)/2*a[1]+(x+2-2)*(x+1-2)/2*a[1]+...+(x+2-x)*(x+1-x)/2*a[x]

对于(x+2-i)*(x+1-i)/2*a[i],我们希望x的函数与和i有关的函数分离,则x的函数是系数,和i有关的函数是需要维护的数组,这样题目就做完了。分离出(x+2)*(x+1)/2 * a[i] - (2*x+3)/2 * (i*a[i]) + 1/2 * (i*i*a[i])。因此只需要维护a[i], i*a[i], i*i*a[i]的前缀和。

拓展:k阶前缀和同理可做。但k较大的时候是否能写代码来求多项式系数呢?我暂时做不到。

代码

  • 注意一切皆可模法。
#include <bits/stdc++.h>
using namespace std;
typedef long long LL;
typedef pair<int, int> pii;
#define rep(i,a,b) for(int i = (a);i <= (b);++i)
#define re_(i,a,b) for(int i = (a);i < (b);++i)
#define dwn(i,a,b) for(int i = (a);i >= (b);--i)
#define lowbit(x) ((x) & (-(x)))

const int N = 2e5 + 5;
const int mod = 998244353;
const int I2 = 499122177;

int n, q, a[N];
LL c0[N], c1[N], c2[N];

void dbg() {
    puts ("");
}
template<typename T, typename... R>void dbg (const T &f, const R &... r) {
    cout << f << " ";
    dbg (r...);
}
template<typename Type>inline void read (Type &xx) {
    Type f = 1;
    char ch;
    xx = 0;
    for (ch = getchar(); ch < '0' || ch > '9'; ch = getchar() ) if (ch == '-') f = -1;
    for (; ch >= '0' && ch <= '9'; ch = getchar() ) xx = xx * 10 + ch - '0';
    xx *= f;
}
void read() {}
template<typename T, typename ...R>void read (T &x, R &...r) {
    read (x);
    read (r...);
}

void mdy (LL C[], int x, LL v) {
    for (; x <= n; x += lowbit (x) ) C[x] = (C[x] + v) % mod;
}

LL qry (LL C[], int x) {
    LL ret = 0;
    for (; x; x -= lowbit (x) ) ret = (ret + C[x]) % mod;
    return ret;
}

int main() {
    read (n, q);
    rep (i, 1, n) read (a[i]), a[i] %= mod;
    rep (i, 1, n) {
        mdy (c0, i, a[i]);
        mdy (c1, i, 1LL * i * a[i] % mod);
        mdy (c2, i, 1LL * i * i % mod * a[i] % mod);
    }
    while (q--) {
        int op;
        read (op);
        if (op == 1) {
            int x;
            LL v;
            read (x, v);
            v %= mod;
            mdy (c0, x, (v - a[x] + mod) % mod);
            mdy (c1, x, x * (v - a[x] + mod) % mod);
            mdy (c2, x, 1LL * x * x % mod * (v - a[x] + mod) % mod);
            a[x] = v;
        } else {
            LL x;
            read (x);
            LL k0 = (x + 2) * (x + 1) % mod * I2 % mod, k1 = (2 * x + 3) * I2 % mod;
            LL ans = (k0 * qry (c0, x) % mod - k1 * qry (c1, x) % mod + I2 * qry (c2, x) % mod + mod) % mod;
            printf ("%lld\n", ans);
        }
    }
    return 0;
}

G-矩阵快速幂优化dp

太难了,只能想到2^D的做法(枚举顶点颜色的状态位),直接学习题解了……

定义dp[i][S = 0~3]为填了前i条边,第一个顶点和当前的边的顶点的颜色分别为状态位的高位和低位的方案数,1表示白色(1表示黑色同理可做)。显然第n条边的顶点和第一个顶点颜色要相同,所以答案dp[n][0] + dp[n][3]

但我们发现没法转移,因为不知道之前的白色有几种,没法保证白色点数相同。那白色点数相同的保证就用外部限制来实现,所以我们先枚举所需颜色种类数k = 0~D+1转移dp[i][S] = sum(C(D - 1, k - bc[S1]) * dp[i-1][S0]),这里SS0的高位要相同,而S1 = (S % 2) << 1 | (S0 % 2)表示上一条边的顶点和当前边的顶点颜色组成的状态位,bc[]表示白色点的个数。边界dp[1][S] = C(D - 1, k - bc[S])

转移只与i-1有关,可矩阵快速幂优化。

总结:想法要大胆,看到n较大也不一定就放弃思考dp做法了,因为还可以用矩阵快速幂来保证复杂度。

#include <bits/stdc++.h>
using namespace std;
typedef long long LL;
typedef pair<int, int> pii;
#define rep(i,a,b) for(int i = (a);i <= (b);++i)
#define re_(i,a,b) for(int i = (a);i < (b);++i)
#define dwn(i,a,b) for(int i = (a);i >= (b);--i)

const int N = 1e4 + 5;
const int mod = 998244353;

LL pw, fac[N], ifac[N];
int D, n = 4;

void dbg() {
    puts ("");
}
template<typename T, typename... R>void dbg (const T &f, const R &... r) {
    cout << f << " ";
    dbg (r...);
}
template<typename Type>inline void read (Type &xx) {
    Type f = 1;
    char ch;
    xx = 0;
    for (ch = getchar(); ch < '0' || ch > '9'; ch = getchar() ) if (ch == '-') f = -1;
    for (; ch >= '0' && ch <= '9'; ch = getchar() ) xx = xx * 10 + ch - '0';
    xx *= f;
}
void read() {}
template<typename T, typename ...R>void read (T &x, R &...r) {
    read (x);
    read (r...);
}

struct Mat {
    LL m[5][5];
    Mat() {
        memset (m, 0, sizeof m);
    }
};
Mat mul (Mat a, Mat b) {
    Mat ret;
    re_ (i, 0, n) {
        re_ (j, 0, n) {
            LL s = 0;
            re_ (k, 0, n) s = (s + a.m[i][k] * b.m[k][j] % mod) % mod;
            ret.m[i][j] = s;
        }
    }
    return ret;
}
Mat q_pow (Mat a, LL b) {
    Mat ret;
    re_ (i, 0, n) ret.m[i][i] = 1;
    for (; b; b >>= 1) {
        if (b & 1) ret = mul (ret, a);
        a = mul (a, a);
    }
    return ret;
}

LL q_pow (LL a, LL b) {
    LL ret = 1;
    for (; b; b >>= 1) {
        if (b & 1) ret = ret * a % mod;
        a = a * a % mod;
    }
    return ret;
}

void init_C (int n) {
    fac[0] = 1;
    rep (i, 1, n) fac[i] = fac[i - 1] * i % mod;
    ifac[n] = q_pow (fac[n], mod - 2);
    dwn (i, n - 1, 0) ifac[i] = (i + 1) * ifac[i + 1] % mod;
}

LL C (int x, int y) {
    if (y < 0 || x < y) return 0;
    return fac[x] * ifac[y] % mod * ifac[x - y] % mod;
}

int main() {
    init_C (N - 5);
    read (pw, D);
    LL ans = 0;
    rep (k, 0, D + 1) {
        Mat m0, bas;
        re_ (S, 0, n) {
            re_ (S0, 0, n) {
                if (S / 2 != S0 / 2) continue;
                int u = (S % 2) << 1 | (S0 % 2);
                m0.m[S][S0] = C (D - 1, k - __builtin_popcount (u) );
            }
        }
        re_ (S, 0, n) bas.m[S][0] = C (D - 1, k - __builtin_popcount (S) );
        Mat m1 = mul (q_pow (m0, pw - 1), bas);
        ans = (ans + m1.m[0][0] + m1.m[3][0]) % mod;
    }
    printf ("%lld\n", ans);
    return 0;
}

H-线段树

思路

即ex题。官方题解的两种做法都太难了,但是操作1的区间向下整除会导致数字快速减小,而操作2的区间覆盖会导致整个数组的丰富程度下降,所以我们会想尝试加一个tag,表示区间的数字是否完全相同,在区间数字完全相同时停止往下遍历。

加这个tag的思路的主要来源是这道线段树模板题:花神游历各国,hdu上有一道题,区间求欧拉函数+求区间和,也是一样的思路。

加这个tag的思路很容易理解,也能AC,但我不清楚它在这题的复杂度对不对。

实现

  1. 结构体需要3个属性:v,ctg,sv = -1表示该区间的值不是完全相同,v >= 0表示值完全相同;ctg表示区间覆盖的懒标记;s表示区间和。
  2. 我选择了先build,再对数组进行n次单点覆盖,这就要求把叶节点的值设为0,而非叶节点的ctg设为-1。也可以直接读入数组再build。
  3. s不是可选的而是必须的,把s去掉则查区间和的时候会走得更深,导致TLE。
#include <bits/stdc++.h>
using namespace std;
typedef long long LL;
typedef pair<int, int> pii;
#define rep(i,a,b) for(int i = (a);i <= (b);++i)
#define re_(i,a,b) for(int i = (a);i < (b);++i)
#define dwn(i,a,b) for(int i = (a);i >= (b);--i)
#define ls(x) ((x) << 1)
#define rs(x) ((x) << 1 | 1)

const int N = 5e5 + 5;

int n, q;

void dbg() {
    puts ("");
}
template<typename T, typename... R>void dbg (const T &f, const R &... r) {
    cout << f << " ";
    dbg (r...);
}
template<typename Type>inline void read (Type &xx) {
    Type f = 1;
    char ch;
    xx = 0;
    for (ch = getchar(); ch < '0' || ch > '9'; ch = getchar() ) if (ch == '-') f = -1;
    for (; ch >= '0' && ch <= '9'; ch = getchar() ) xx = xx * 10 + ch - '0';
    xx *= f;
}
void read() {}
template<typename T, typename ...R>void read (T &x, R &...r) {
    read (x);
    read (r...);
}

struct Node {
    int v, ctg;
    LL s;
} nd[N << 2];

void update (int x, int v, int l, int r) {
    nd[x].ctg = nd[x].v = v;
    nd[x].s = 1LL * v * (r - l + 1);
}

void pushup (int x) {
    nd[x].v = nd[ls (x)].v == nd[rs (x)].v ? nd[ls (x)].v : -1;
    nd[x].s = nd[ls (x)].s + nd[rs (x)].s;
}

void pushdown (int x, int l, int r) {
    if (!~nd[x].ctg) return;
    int v = nd[x].ctg, mid = (l + r) >> 1;
    nd[ls (x)].ctg = nd[rs (x)].ctg = v;
    nd[ls (x)].v = nd[rs (x)].v = v;
    nd[ls (x)].s = (mid - l + 1LL) * v;
    nd[rs (x)].s = 1LL * (r - mid) * v;
    nd[x].ctg = -1;
}

void build (int x, int l, int r) {
    nd[x].ctg = -1;
    if (l == r) {
        update (x, 0, l, r);
        return;
    }
    int mid = (l + r) >> 1;
    build (ls (x), l, mid);
    build (rs (x), mid + 1, r);
    pushup (x);
}

void idiv (int ql, int qr, int v, int x = 1, int l = 1, int r = n) {
    if (ql <= l && r <= qr && ~nd[x].v) {
        update (x, nd[x].v / v, l, r);
        return;
    }
    pushdown (x, l, r);
    int mid = (l + r) >> 1;
    if (ql <= mid) idiv (ql, qr, v, ls (x), l, mid);
    if (qr > mid) idiv (ql, qr, v, rs (x), mid + 1, r);
    pushup (x);
}

void cover (int ql, int qr, int v, int x = 1, int l = 1, int r = n) {
    if (ql <= l && r <= qr && ~nd[x].v) {
        update (x, v, l, r);
        return;
    }
    pushdown (x, l, r);
    int mid = (l + r) >> 1;
    if (ql <= mid) cover (ql, qr, v, ls (x), l, mid);
    if (qr > mid) cover (ql, qr, v, rs (x), mid + 1, r);
    pushup (x);
}

LL qry (int ql, int qr, int x = 1, int l = 1, int r = n) {
    if (ql <= l && r <= qr) return nd[x].s;
    pushdown (x, l, r);
    int mid = (l + r) >> 1;
    LL ans = 0;
    if (ql <= mid) ans += qry (ql, qr, ls (x), l, mid);
    if (qr > mid) ans += qry (ql, qr, rs (x), mid + 1, r);
    pushup (x);
    return ans;
}

int main() {
    read (n, q);
    build (1, 1, n);
    rep (i, 1, n) {
        int v;
        read (v);
        cover (i, i, v);
    }
    while (q--) {
        int op, l, r, v;
        read (op, l, r);
        if (op == 1) {
            read (v);
            idiv (l, r, v);
        } else if (op == 2) {
            read (v);
            cover (l, r, v);
        } else {
            printf ("%lld\n", qry (l, r) );
        }
    }
    return 0;
}
  数据结构与算法 最新文章
【力扣106】 从中序与后续遍历序列构造二叉
leetcode 322 零钱兑换
哈希的应用:海量数据处理
动态规划|最短Hamilton路径
华为机试_HJ41 称砝码【中等】【menset】【
【C与数据结构】——寒假提高每日练习Day1
基础算法——堆排序
2023王道数据结构线性表--单链表课后习题部
LeetCode 之 反转链表的一部分
【题解】lintcode必刷50题<有效的括号序列
上一篇文章      下一篇文章      查看所有文章
加:2022-06-26 17:03:41  更:2022-06-26 17:05:31 
 
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁

360图书馆 购物 三丰科技 阅读网 日历 万年历 2024年11日历 -2024/11/25 23:22:18-

图片自动播放器
↓图片自动播放器↓
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
图片批量下载器
↓批量下载图片,美女图库↓
  网站联系: qq:121756557 email:121756557@qq.com  IT数码