首页 > 其他分享 >NOI2021 Day1

NOI2021 Day1

时间:2024-06-18 10:12:31浏览次数:6  
标签:int top tp Day1 NOI2021 dfn long using

轻重边

把询问和修改都转到点上考虑。

相当于给某些路径上的点都染上一种未出现过的颜色,然后查询某些路径上颜色相同的相邻点对数。

注意初始时所有点的颜色应该互不相同

树剖 + 线段树就做完了。需要特别注意的是树剖跳链时也会产生贡献。

时间复杂度 \(\mathcal O(n \log^2 n)\)。

代码
#include <bits/stdc++.h>

using namespace std;

using uint = unsigned int;
using ll = long long;
using ull = unsigned long long;
using lll = __int128;
using ld = long double;

template<typename tp> inline void chkmax(tp &x, tp y) {x = max(x, y);}
template<typename tp> inline void chkmin(tp &x, tp y) {x = min(x, y);}

constexpr int N = 1e5 + 10;

int n, m, cnt, fa[N], dep[N], sz[N], son[N], dfn[N], top[N];

vector<int> G[N];

namespace SGT {
    #define ls o << 1
    #define rs o << 1 | 1

    constexpr int TN = N << 2;

    int lc[TN], rc[TN], cnt[TN], lz[TN];

    inline void pu(int o) {
        lc[o] = lc[ls], rc[o] = rc[rs];
        cnt[o] = cnt[ls] + cnt[rs] + (rc[ls] == lc[rs]);
    }

    inline void pd(int o, int l, int r) {
        if (lz[o]) {
            int mid = (l + r) >> 1;
            lc[ls] = rc[ls] = lz[ls] = lc[rs] = rc[rs] = lz[rs] = lz[o];
            cnt[ls] = mid - l, cnt[rs] = r - mid - 1;
            lz[o] = 0;
        }
    }

    void build(int o, int l, int r) {
        lz[o] = cnt[o] = 0;
        if (l == r) {lc[o] = rc[o] = l; return;}
        int mid = (l + r) >> 1;
        build(ls, l, mid), build(rs, mid + 1, r);
        pu(o);
    }

    void upd(int o, int l, int r, int x, int y, int c) {
        if (x <= l && r <= y) {lc[o] = rc[o] = lz[o] = c, cnt[o] = r - l; return;}
        pd(o, l, r); int mid = (l + r) >> 1;
        if (x <= mid) upd(ls, l, mid, x, y, c);
        if (y > mid) upd(rs, mid + 1, r, x, y, c);
        pu(o);
    }

    int qry(int o, int l, int r, int x, int y) {
        if (x <= l && r <= y) return cnt[o];
        pd(o, l, r); int mid = (l + r) >> 1;
        if (y <= mid) return qry(ls, l, mid, x, y);
        if (x > mid) return qry(rs, mid + 1, r, x, y);
        return qry(ls, l, mid, x, y) + qry(rs, mid + 1, r, x, y) + (rc[ls] == lc[rs]);
    }

    int col(int o, int l, int r, int x) {
        if (l == r) return lc[o];
        pd(o, l, r); int mid = (l + r) >> 1;
        return x <= mid ? col(ls, l, mid, x) : col(rs, mid + 1, r, x);
    }
}

void dfs1(int u) {
    sz[u] = 1, son[u] = 0;
    for (int v : G[u]) if (v != fa[u]) {
        dep[v] = dep[fa[v] = u] + 1, dfs1(v);
        sz[u] += sz[v];
        if (sz[v] > sz[son[u]]) son[u] = v;
    }
}

void dfs2(int u, int tp) {
    dfn[u] = ++cnt, top[u] = tp;
    if (!son[u]) return;
    dfs2(son[u], tp);
    for (int v : G[u]) if (v != fa[u] && v != son[u]) dfs2(v, v);
}

void upd(int x, int y, int c) {
    while (top[x] != top[y]) {
        if (dep[top[x]] < dep[top[y]]) swap(x, y);
        SGT::upd(1, 1, n, dfn[top[x]], dfn[x], c);
        x = fa[top[x]];
    }
    if (dfn[x] > dfn[y]) swap(x, y);
    SGT::upd(1, 1, n, dfn[x], dfn[y], c);
}

int qry(int x, int y) {
    int res = 0;
    while (top[x] != top[y]) {
        if (dep[top[x]] < dep[top[y]]) swap(x, y);
        res += SGT::qry(1, 1, n, dfn[top[x]], dfn[x]) + (SGT::col(1, 1, n, dfn[top[x]]) == SGT::col(1, 1, n, dfn[fa[top[x]]]));
        x = fa[top[x]];
    }
    if (dfn[x] > dfn[y]) swap(x, y);
    return res + SGT::qry(1, 1, n, dfn[x], dfn[y]);
}

void solve() {
    cnt = 0;
    for (int i = 1; i <= n; i++) G[i].clear();
    cin >> n >> m;
    for (int i = 1, u, v; i < n; i++) cin >> u >> v, G[u].emplace_back(v), G[v].emplace_back(u);
    dfs1(dep[1] = 1), dfs2(1, 1);
    SGT::build(1, 1, n);
    for (int i = 1, op, x, y; i <= m; i++) {
        cin >> op >> x >> y;
        if (op == 1) upd(x, y, n + i);
        else cout << qry(x, y) << '\n';
    }
}

int main() {
    ios_base::sync_with_stdio(0); cin.tie(nullptr), cout.tie(nullptr);
    int t; cin >> t;
    while (t--) solve();
    return 0;
}

路径交点

两条路径间交点的奇偶性近由它们起点和终点的相对位置决定,更具体地,记他们的起点分别为 \(S_1, S_2\),终点分别为 \(T_1, T_2\),则 \(S_1 \rightsquigarrow T_1\) 和 \(S_2 \rightsquigarrow T_2\) 有奇数个交点当且仅当 \((S_1 - S_2) \times (T_1 - T_2) < 0\)。

如果我们按起点编号从大到小考虑每条路径的终点,记终点的排列为 \(\{p_n\}\),则 \(\{p_n\}\) 中形成逆序对的 \((i, j)\) 有奇数个交点,其余有偶数个交点,也就是说 路径方案中交点个数的奇偶性与 \(\{p_n\}\) 中逆序对数的奇偶性相同

所以一个猜想就是直接把邻接矩阵乘起来求行列式,时间复杂度 \(\mathcal O(kn^3)\)。

然后你 AC 了。

再然后你开始疑惑:

现在小 L 要从这个图中选出 \(n_1\) 条路径,每条路径以第 \(1\) 层顶点为起点,第 \(k\) 层顶点为终点,并要求图中的每个顶点至多出现在一条路径中

“我没考虑这个要求啊?”

不考虑就对了。

如果某个方案里存在这样两条路径: \(S_1 \rightsquigarrow M \rightsquigarrow T_1\) 和 \(S_2 \rightsquigarrow M \rightsquigarrow T_2\),一定存在且仅存在一个除了这两条路径之外和它完全相同的方案,满足这两条路径是 \(S_1 \rightsquigarrow M \rightsquigarrow T_2\) 和 \(S_2 \rightsquigarrow M \rightsquigarrow T_1\),又因为排列中交换两个元素的位置后逆序对数的奇偶性一定会发生变化,所以这两种情况抵消了。

代码
#include <bits/stdc++.h>

using namespace std;

using uint = unsigned int;
using ll = long long;
using ull = unsigned long long;
using lll = __int128;
using ld = long double;

template<typename tp> inline void chkmax(tp &x, tp y) {x = max(x, y);}
template<typename tp> inline void chkmin(tp &x, tp y) {x = min(x, y);}

constexpr int N = 201, MOD = 998244353;

int n[N], m[N];

ll inv(ll base, int e = MOD - 2) {
    ll res = 1;
    while (e) {
        if (e & 1) res = res * base % MOD;
        base = base * base % MOD;
        e >>= 1;
    }
    return res;
}

struct Matrix {
    ll a[N][N];

    Matrix() {memset(a, 0, sizeof(a));}

    Matrix mul(int n2, int n3, Matrix rhs) { // (n1, n2) * (n2, n3) -> (n1, n3)
        Matrix res;
        for (int i = 1; i <= n[1]; i++) {
            for (int j = 1; j <= n3; j++) {
                for (int k = 1; k <= n2; k++) {
                    res.a[i][j] = (res.a[i][j] + a[i][k] * rhs.a[k][j]) % MOD;
                }
            }
        }
        return res;
    }

    ll det(int n) {
        ll res = 1, f = 1;
        for (int i = 1; i <= n; i++) {
            int p = i;
            while (p <= n && !a[p][i]) p++;
            if (p > n) return 0;
            if (p != i) swap(a[p], a[i]), f = -f;
            res = res * a[i][i] % MOD;
            ll t = inv(a[i][i]);
            for (int j = i; j <= n; j++) a[i][j] = a[i][j] * t % MOD;
            for (int j = i + 1; j <= n; j++) {
                ll coef = MOD - a[j][i];
                for (int k = i; k <= n; k++) a[j][k] = (a[j][k] + coef * a[i][k]) % MOD;
            }
        }
        return (res * f + MOD) % MOD;
    }
};

void solve() {
    int k; cin >> k;
    for (int i = 1; i <= k; i++) cin >> n[i];
    for (int i = 1; i < k; i++) cin >> m[i];
    Matrix E;
    for (int i = 1; i < k; i++) {
        Matrix now;
        for (int u, v; m[i]--;) cin >> u >> v, now.a[u][v] = 1;
        E = i > 1 ? E.mul(n[i], n[i + 1], now) : now;
    }
    cout << E.det(n[1]) << '\n';
}

int main() {
    ios_base::sync_with_stdio(0); cin.tie(nullptr), cout.tie(nullptr);
    int t; cin >> t;
    while (t--) solve();
    return 0;
}

庆典

先 tarjan 缩成一个 DAG。

考虑给定的条件:

  • 若 \(x \Rightarrow z\) 且 \(y \Rightarrow z\),则 \(x \Rightarrow y\) 或 \(y \Rightarrow x\)。

  • 把有向边视作无向边后,图强连通。

因为是 DAG,所以我们一定可以找到一个入度为 \(0\) 的点,记作 \(x_0\),那么对于剩下的所有点 \(z\),必然有 \(x_0 \Rightarrow z\) 或 \(x_0 \Rightarrow y\) 且 \(y \Rightarrow z\),对于后者显然不应该存在 \(z \Rightarrow x_0\),所以一定有 \(x_0 \Rightarrow z\),也即图弱连通。

对于每一个终点,我们只保留 起点拓扑序最大 的边,图的连通性仍不变,但变成了更简单的以 \(x_0\) 为根的外向树。

\(k = 0\) 时就是简单地查询 \(t\) 是否在 \(s\) 子树内,若是则查询 \(s\) 到 \(t\) 路径上的点权和。

\(k = 1,2\) 的情况都可以通过巨大分讨解决,但有一种更巧妙的办法:虚树

以 \(s\),\(t\) 及新增路径的起点和终点为关键点建立虚树,在虚树上两次 bfs 分别求 \(S = \{x | s \Rightarrow x\}\) 和 \(T = \{x | x \in S, x \Rightarrow t\}\),\(|T|\) 就是答案。

时间复杂度 \(\mathcal O(n \log n + qk \log k)\),并且可以拓展到 \(\sum k \le 6 \times 10^5\)。

卡常题,效果比较显著的几个点是:

  • 快读(快写可有可无,输出规模并不算特别大)。

  • 极限数据下图比较稀疏,链式前向星(邻接表)存图明显快于 vector

  • 用 \(\mathcal O(n \log n) - \mathcal O(1)\) 的 dfs 序 \(\text{LCA}\)。

代码
#include <bits/stdc++.h>

using namespace std;

using uint = unsigned int;
using ll = long long;
using ull = unsigned long long;
using lll = __int128;
using ld = long double;

template<typename tp> inline void chkmax(tp &x, tp y) {x = max(x, y);}
template<typename tp> inline void chkmin(tp &x, tp y) {x = min(x, y);}

template<typename _T> inline void read(_T &_x) {
    _x = 0;
    _T _f = 1;
    char _ch = getchar();
    while (_ch < '0' || '9' < _ch) {
        if (_ch == '-') _f = -1;
        _ch = getchar();
    }
    while ('0' <= _ch && _ch <= '9') {
        _x = (_x << 3) + (_x << 1) + (_ch & 15);
        _ch = getchar();
    }
    _x *= _f;
}

template<typename _T> void write(_T _x) {
    if (_x < 0) {
        putchar('-');
        _x = -_x;
    }
    if (_x > 9) write(_x / 10);
    putchar('0' + _x % 10);
}

constexpr int N = 3e5 + 10, K = 20;

int n, m, qn, k, p, rt, w[N];

struct Graph1 {
    int tot, head[N];
    struct Edge {int to, nxt;} e[N << 1];
    inline void add(int u, int v) {e[++tot] = Edge{v, head[u]}, head[u] = tot;}
} G0, G1, G;

namespace SCC {
    int cnt, dfn[N], low[N], top, stk[N], bel[N]; bool ins[N];

    void tarjan(int u) {
        dfn[u] = low[u] = ++cnt, ins[stk[++top] = u] = 1;
        for (int i = G0.head[u], v; v = G0.e[i].to, i; i = G0.e[i].nxt) {
            if (!dfn[v]) tarjan(v), low[u] = min(low[u], low[v]);
            else if (ins[v]) low[u] = min(low[u], dfn[v]);
        }
        if (dfn[u] == low[u]) {
            p++; int x;
            do ins[x = stk[top--]] = 0, w[p]++, bel[x] = p;
            while (x != u);
        }
    }
    
    int ind[N];

    queue<int> q;

    void work() {
        for (int i = 1; i <= n; i++) if (!dfn[i]) tarjan(i);
        for (int u = 1; u <= n; u++) for (int i = G0.head[u], v; v = G0.e[i].to, i; i = G0.e[i].nxt) if (bel[u] != bel[v]) G1.add(bel[u], bel[v]), ind[bel[v]]++;
        for (int i = 1; i <= p; i++) if (!ind[i]) q.emplace(rt = i);
        while (!q.empty()) {
            int u = q.front(); q.pop();
            for (int i = G1.head[u], v; v = G1.e[i].to, i; i = G1.e[i].nxt) if (!(--ind[v])) {
                G.add(u, v);
                q.emplace(v);
            }
        }
    }
}

int cnt, dfn[N], s[N], dep[N], fa[N], lg[N], st[K][N];

void predfs(int u) {
    dfn[u] = ++cnt, st[0][cnt] = fa[u], s[u] = w[u] + s[fa[u]];
    for (int i = G.head[u], v; v = G.e[i].to, i; i = G.e[i].nxt) if (v != fa[u]) {
        dep[v] = dep[fa[v] = u] + 1, predfs(v);
    }
}

inline int mindep(int x, int y) {return dep[x] < dep[y] ? x : y;}

int LCA(int u, int v) {
    if (u == v) return u;
    u = dfn[u], v = dfn[v];
    if (u > v) swap(u, v);
    return mindep(st[lg[v - u]][u + 1], st[lg[v - u]][v - (1 << lg[v - u]) + 1]);
}

int nkey, key[N];

unordered_map<int, int> pid;
inline int id(int x) {return pid[x] ? pid[x] : pid[x] = ++n;};

int val[N];

struct Graph2 {
    int tot, head[K << 1];
    struct Edge {int to, nxt, w;} e[K * K];

    inline void clear() {tot = 0; memset(head, 0, sizeof(head));}

    inline void add(int u, int v, int w) {e[++tot] = {v, head[u], w}, head[u] = tot;}
} H, iH;

inline void add(int u, int v, int w) {H.add(id(u), id(v), w), iH.add(id(v), id(u), w);}

inline bool cmp(const int &i, const int &j) {return dfn[i] < dfn[j];}

queue<int> q;

bool S[K], vis[K], vit[K];

int main() {
    read(n), read(m), read(qn), read(k);
    for (int i = 1, u, v; i <= m; i++) read(u), read(v), G0.add(u, v);
    SCC::work();
    dep[rt] = 1, predfs(rt);
    for (int i = 2; i <= n; i++) lg[i] = lg[i >> 1] + 1;
    for (int i = 1; (1 << i) <= n; i++) for (int j = 1; j + (1 << i) - 1 <= n; j++) st[i][j] = mindep(st[i - 1][j], st[i - 1][j + (1 << (i - 1))]);
    while (qn--) {
        // clear
        pid.clear(), H.clear(), iH.clear();
        n = 0, nkey = 2;
        // work
        int x, y; read(x), read(y), x = SCC::bel[x], y = SCC::bel[y];
        key[1] = x, key[2] = y;
        for (int i = 1, u, v; i <= k; i++) {
            read(u), read(v), u = SCC::bel[u], v = SCC::bel[v];
            if (u != v) {
                key[++nkey] = u, key[++nkey] = v;
                add(u, v, 0);
            }
        }
        sort(key + 1, key + nkey + 1, cmp), nkey = unique(key + 1, key + nkey + 1) - key - 1;
        int tmp = nkey;
        for (int i = 2; i <= tmp; i++) key[++nkey] = LCA(key[i - 1], key[i]);
        sort(key + 1, key + nkey + 1, cmp), nkey = unique(key + 1, key + nkey + 1) - key - 1;
        val[id(key[1])] = w[key[1]];
        for (int i = 2, lca; i <= nkey; i++) {
            lca = LCA(key[i - 1], key[i]);
            val[id(key[i])] = w[key[i]], val[id(lca)] = w[lca];
            add(lca, key[i], s[fa[key[i]]] - s[lca]);
        }
        memset(vis, 0, sizeof(vis)), memset(S, 0, sizeof(S));
        q.push(id(x));
        while (!q.empty()) {
            int u = q.front(); q.pop();
            S[u] = 1;
            for (int i = H.head[u], v; v = H.e[i].to, i; i = H.e[i].nxt) if (!vis[i]) {
                vis[i] = 1, q.emplace(v);
            }
        }
        int ans = 0;
        memset(vit, 0, sizeof(vit));
        q.push(id(y));
        while (!q.empty()) {
            int u = q.front(); q.pop();
            if (S[u]) ans += val[u], S[u] = 0;
            for (int i = iH.head[u], v; v = iH.e[i].to, i; i = iH.e[i].nxt) if (!vit[i]) {
                vit[i] = 1;
                if (vis[i]) ans += iH.e[i].w, vis[i] = 0;
                q.emplace(v);
            }
        }
        write(ans), putchar('\n');
    }
    return 0;
}

标签:int,top,tp,Day1,NOI2021,dfn,long,using
From: https://www.cnblogs.com/chy12321/p/18253777

相关文章

  • 机器学习day1
    机器学习day11.环境准备pythonPython是一种解释型、面向对象、动态数据类型的高级程序设计语言。Python由GuidovanRossum于1989年底发明,第一个公开发行版发行于1991年。像Perl语言一样,Python源代码同样遵循GPL(GNUGeneralPublicLicense)协议。pycharm......
  • 使用Jupyter(python+opencv)实现很难的脚本-Day1
    由于xx西游没办法自动挖图,于是懒狗的我只能自己写一段脚本来实现挖土自由。首先介绍几个比较重要的库都需要自行install。fromPILimportImage#用于计算图片大小的库importpyautogui#用于抓取目标位置的库importpygetwindowasgw#用于得到窗口大小的库......
  • 持续性学习-Day18(JavaWeb)
    JavaWeb1、基本概念web开发:web,表示可以从互联网上拿到一定的资源静态webhtml、css提供给所有人看的数据,始终不会发生变化动态web每个人在不同时间、不同地点,看到的信息各不相同技术栈:servlet/JSP、ASP、PHP在Java中,动态web资源开发的计数统称为Java......
  • 代码随想录 算法训练营 day10 leetcode232 用栈实现队列 Leetcode225 用队列实现栈 Le
    Leetcode232用栈实现队列题目链接讲解用两个栈实现队列每次需要出队列或者查看队头元素时,将输入栈的所有元素放到输出栈classMyQueue{Stack<Integer>stackIn;Stack<Integer>stackOut;publicMyQueue(){stackIn=newStack<>();//负责进......
  • Day18 | 513. 找树左下角的值 | 112.路径总和、113.路径总和ii
    513.找树左下角的值本题递归偏难,反而迭代简单属于模板题,两种方法掌握一下题目链接/文章讲解/视频讲解:https://programmercarl.com/0513.找树左下角的值.html思考层序遍历秒了#Definitionforabinarytreenode.#classTreeNode:#def__init__(self,val=0,left......
  • P10432 [JOISC 2024 Day1] 滑雪 2
    MyBlogsP10432[JOISC2024Day1]滑雪2感觉是挺好的观察性质题,vp的时候场切了。首先酒店一定是建在最低的某一个点。把高度离散化之后,把点扔到对应的位置。可以发现高度为\(i\)的层的所有点,如果能接上一层的连接器那一定会尽量接上(因为到了下一层它本身也可以贡献一个空......
  • day10
    今天是day10题目一:滑动窗口最大值,要求从一个大小为k的滑动窗口从数组的最左端移动到数组的最右侧最终返回窗口中的最大值fromcollectionsimportdequeclassMyQueue:  def__init__(self):    "双端队列"    self.queue=deque()  defpop(sel......
  • day11
    今日day11:三种二叉树的遍历方式1首先是递归遍历。需要首先搞懂前序中序后序遍历的意思,即以根root的位置为准前序即根左右中序即左根右后序即左右根递归则是指在函数中循环调用自身直至跳出递归条件python实现原理仅有遍历顺序的变化为区别,首先声明一个空res数组用以存放数......
  • day13
    day13一:层序遍历:即依据根左右继续左右依层遍历各节点classSolution:  deflevelOrder(self,root:Optional[TreeNode])->List[List[int]]:    ifnotroot:      return[]    queue=collections.deque([root])    result=......
  • day12
    今天是day12第一题为二叉树的层序遍历"遍历长度法""借用队列,将root加入其中"classSolution:deflevelOrder(self,root:Optional[TreeNode])->List[List[int]]:ifnotroot:return[]queue=collections.deque([root])resu......