2019杭电多校第三场

2019杭电多校第三场

1002. Blow up the city

upsloved

有一个DAG,出度为\(0\)的点是指挥中心,\(q\)次询问,每次给出两个点,这两个点存有关键物资,你可以炸掉一个点和它的邻接边,使得这两个点中任意一个点的物资不能到达指挥中心(有一个点不能到达任意一个指挥中心即可),求方案数,询问独立

并不会支配树,比赛的时候想到了这个东西的概念,但是不会实现,赛后学习一波

把图反过了,建一个额外的起点指向指挥中心,求出支配树,每次询问u,v就只要计算u,v以及它们的LCA的深度就好了

#include <bits/stdc++.h>
using LL = long long;
using namespace std;

const int N = 200010;
struct Node{int to,next;};
int T, n, m, q, u, v, dfn[N], clo, rev[N], f[N], semi[N], idom[N], deg[N], dep[N], fa[N][18];

struct Graph{
    Node E[N << 1]; int head[N], tot;
    inline void clear(){
        tot = 0;
        for(int i = 0; i <= n; ++i) head[i] = -1;
    }
    inline void link(int u, int v){
        E[tot] = (Node){v, head[u]}; head[u] = tot++;
    }
}pre, nxt, dom;

struct uset{
    int fa[N], Mi[N];
    inline void init(){
        for(int i = 1; i <= n; ++i)
            fa[i] = Mi[i] = semi[i] = i;
    }
    inline int find(int x){
        if(fa[x] == x) return x;
        int fx = fa[x], y = find(fa[x]);
        if(dfn[semi[Mi[fx]]] < dfn[semi[Mi[x]]]) Mi[x] = Mi[fx];
        return fa[x] = y;
    }
}uset;

inline void tarjan(int x) {
    dfn[x] = ++clo; rev[clo] = x;
    for(int e = nxt.head[x]; ~e; e = nxt.E[e].next){
        if(!dfn[nxt.E[e].to])
            f[nxt.E[e].to] = x, tarjan(nxt.E[e].to);
    }
}

inline void dfs(int x, int pre, int depth) {
    dep[x] = depth;
    fa[x][0] = pre;
    for(int i = 1; i < 18; ++i)
        fa[x][i] = fa[fa[x][i - 1]][i - 1];
    for(int e = dom.head[x]; ~e; e = dom.E[e].next)
        dfs(dom.E[e].to, x, depth + 1);
}

inline int LCA(int u, int v) {
    if(dep[u] < dep[v]) swap(u, v);
    for(int i = 17; ~i; --i) {
        if(dep[fa[u][i]] >= dep[v])
            u = fa[u][i];
    }
    if(u == v) return u;
    for(int i = 17; ~i; --i) {
        if(fa[u][i] != fa[v][i])
            u = fa[u][i], v = fa[v][i];
    }
    return fa[u][0];
}

inline void calc(){
    for(int i = n; i >= 2; --i){
        int y = rev[i], tmp = n;
        for(int e = pre.head[y]; ~e; e = pre.E[e].next){
            int x = pre.E[e].to; if(!dfn[x]) continue;
            if(dfn[x] < dfn[y]) tmp = min(tmp, dfn[x]);
            else uset.find(x), tmp = min(tmp, dfn[semi[uset.Mi[x]]]);
        }
        semi[y] = rev[tmp]; uset.fa[y] = f[y];
        dom.link(semi[y], y);
        y = rev[i - 1];
        for(int e = dom.head[y]; ~e; e = dom.E[e].next){
            int x = dom.E[e].to; uset.find(x);
            if(semi[uset.Mi[x]] == y) idom[x] = y;
            else idom[x] = uset.Mi[x];
        }
    }
    for(int i = 2; i <= n; ++i){
        int x = rev[i];
        if(idom[x] != semi[x])
            idom[x] = idom[idom[x]];
    }
    dom.clear();
    for(int i = 1; i < n; ++i)
        dom.link(idom[i], i);
    dfs(n, 0, 0);
}

int main() {
    scanf("%d", &T);
    while(T--) {
        scanf("%d%d", &n, &m);
        clo = 0; n++; dep[0] = -1;
        for(int i = 1; i <= n; ++i)
            deg[i] = dfn[i] = rev[i] = semi[i] = idom[i] = f[i] = 0;
        pre.clear(); nxt.clear(); dom.clear(); uset.init();
        for(int i = 1; i <= m; ++i){
            scanf("%d%d", &u, &v);
            pre.link(u, v);
            nxt.link(v, u);
            deg[u]++;
        }
        for(int i = 1; i < n; ++i) {
            if(deg[i] == 0) {
                nxt.link(n, i);
                pre.link(i, n);
            }
        }
        tarjan(n);
        calc();
        scanf("%d", &q);
        while(q--) {
            scanf("%d%d", &u, &v);
            printf("%d\n", dep[u] + dep[v] - dep[LCA(u, v)]);
        }
    }
    return 0;
}

1004. Distribution of books

upsloved

有一个长为\(n\)的数列\(A\), 你需要将找\(k\)个飞空不相交区间,这些区间并起来是\(A\)的一个前缀区间,使得这\(k\)个区间每个区间的和的最大值最小

二分答案,线段树优化dp套路,代码不放了

1006. Fansblog

solved at 00:54

队友做的,素数分布,威尔逊定理,int128

1007. Find the answer

solved at 01:24(+3)

离散化之后线段树维护区间和以及区间数的个数

#include <bits/stdc++.h>
using namespace std;

const int N = 2e5 + 10;

struct node {
    int val, id;
    bool operator<(const node &rhs) const {
        return val < rhs.val;
    }
}a[N];

int rnk[N], T, n, ans[N], m, sum, mn, b[N];

long long tree[N << 2];
int cnt[N << 2];

void pushup(int rt) {
    tree[rt] = tree[rt << 1] + tree[rt << 1 | 1];
    cnt[rt] = cnt[rt << 1] + cnt[rt << 1 | 1];
}

void build(int rt, int l, int r) {
    tree[rt] = 0; cnt[rt] = 0;
    if(l == r) return;
    int mid = l + r >> 1;
    build(rt << 1, l, mid);
    build(rt << 1 | 1, mid + 1, r);
}

void update(int rt, int l, int r, int pos, int val) {
    if(l == r) {tree[rt] += val, cnt[rt]++; return;}
    int mid = l + r >> 1;
    if(pos <= mid) update(rt << 1, l, mid, pos, val);
    else update(rt << 1 | 1, mid + 1, r, pos, val);
    pushup(rt);
}

int query(int rt, int l, int r, long long val) {
    if(l == r) return tree[rt] <= val ? cnt[rt] : 0;
    int mid = l + r >> 1;
    if(tree[rt << 1] <= val) return cnt[rt << 1] + query(rt << 1 | 1, mid + 1, r, val - tree[rt << 1]);
    else return query(rt << 1, l, mid, val); 
}

int main() {
    read(T);
    while(T--) {
        sum = ans[0] = 0;
        read(n); read(m);
        for(int i = 1; i <= n; ++i) {
            read(a[i].val);
            b[i] = a[i].val;
            a[i].id = i;
        }
        sort(a + 1, a + n + 1);
        for(int i = 1; i <= n; ++i)
            rnk[a[i].id] = i;
        build(1, 1, n);
        for(int i = 1; i <= n; ++i) {
            ans[i] = i - 1 - query(1, 1, n, m - b[i]);
            update(1, 1, n, rnk[i], b[i]);
        }
        for(int i = 1; i <= n; ++i)
            printf("%d ", ans[i]);
        puts("");
    }
    return 0;
}

1009. K subsequence

solved at 04:42(+14)

有一个长为\(n\)的数列,你需要找出\(k\)个不相交的非降子序列使得选出的数和最大\(1<=n<=2000, 1<=k<=10\)

最小费用最大流经典题,拆点建图,每个点\(i\)拆成\(i_1\)\(i_2\)\(i_1\)\(i_2\)建容量为\(1\),费用为\(-a_i\)的边,\(s\)\(i_1\)建容量为\(1\),费用为\(0\)的边,\(i_2\)\(t\)建容量为\(1\),费用为\(0\)的边,如果\(a[i]<=a[j]\)\(i<j\)\(i_2\)\(j_1\)建容量为\(1\),费用为\(0\)的边,最后\(ss\)\(s\)建容量为\(k\),费用为\(0\)的边,\(ss\)\(t\)\(MCMF\),最小费用取相反数就是答案

1011. Squrirrel

树型dp,待补

转载于:https://www.cnblogs.com/tusikalanse/p/11273174.html

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值