BZOJ 3648|寝室管理|点分治|树状数组|平衡树

27 篇文章 0 订阅
10 篇文章 0 订阅

题意

给出一个至多有1个环的图,求长度不超过K的路径数。
GDOI 2016 Day2 T3原题在此

题解

考虑树的情况,显然用点分治就可以搞出来了。
对于过根的路径,先处理出各深度有多少的点,再依次枚举各子树,对于各点,有其深度,问题变成了在其他子树中,与现有深度之和不超过K的有多少个,而枚举的点到根的距离是已知的t,那么就变成在之前的子树中,深度不超过K-t的点有多少个,平衡树、树状数组都可以轻易地维护。 O(nlog2n)

环呢?随便删除一条边就变成树了,然后我们再考虑被删除的这条边对答案的贡献,考虑接在环上的所有子树,统计各子树后,我们就只需要扫一遍环。
我们在枚举子树的时候,每当移动一次,到之前扫过的树的距离就少了1(我们必须得经过那条被删除的边),这样看来,之前扫过的每棵树允许的深度都不一样。发现相邻的两棵子树,允许的深度差总是1,因此我们在处理树状数组的时候,把深度+1即可,每扫依次,深度就再+1,然后就可以在 O(nlogn) 时间内解决了。

比如对于环1-2-3-4-5-6-1,假设切断1-6这条边,从1->6的方向枚举。K=3
枚举1,把1插入到bit[1]。我们令环上的点的深度为0。
……
枚举6,考虑之前枚举过的,到达j=1经过了2个点,那么层数超过K-dis=1的,都对答案有贡献,而且显然dis=cir-i+j+1,表示环上过被删除边的距离,i为枚举的点在环中的位置,也就是枚举到第i次的点。到达j=2经过了3个点,那么层数超过0的都计入,j=3经过4个点,层数超过-1的都计入……,j=5经过了6个点,层数超过-3的都计入。

但总不可能一一枚举j,我们发现,对于任意的i,我们枚举的j要统计的层数总是一个公差为-1的等差数列,与数列{5,4,3,2,1}的距离(这里指对应元素的差)为ERROR,我们可以利用这个性质。

对于枚举的6,到1~5的层数为{1,0,-1,-2,-3}(实际上负数和0没有区别)。那么5插入的深度全部+5,4插入的深度全部+4,……,层数就变成了{2,2,2,2,2}。那么我们在查询BIT的时候,只要查询>=2的总和就可以了。
对于枚举的5,到1~4的层数为{0,-1,-2,-3},此时只要查询>=1的总和就可以了。
对于枚举的4,到1~3的层数为{-1,-2,-3},只要查询>=0的总和即可。
假设4有个孩子(即深度为1),那么到1~3的层数就为{-2,-3,-4},只要查询>=-1的总和即可。

总结一下,对于枚举的点,查询的即为>=K-(dis<x,1>-1+level)。level是深度。dis<x,1>=cir-i+2

直接飞到Rank 4了。。。
不知道怎么优化常数了QwQ

树状数组版本(1.6s)

#include <cstdio>
#include <algorithm>
using namespace std;
#define adj(i,j) for(i=h[j];i;i=p[i])
#define FOR(i,j,k) for(i=j;i<=k;++i)

inline int read() {
    int s=0,f=1;char ch=getchar();
    for(;ch<'0'||ch>'9';ch=getchar())if(ch=='-')f=-1;
    for(;'0'<=ch&&ch<='9';ch=getchar())s=s*10+ch-'0';
    return s*f;
}

const int N = 200005, M = N * 2, inf = 2147483647;
long long ans;
int bit[N], c[N], cir, n, K, sk[N], top, no[N];
int h[N], p[M], v[M], cnt = 1;
int vis[N], f[N], rt, sum, sz[N];

inline void add(int i, int x) {
    for (; i; i -= i & -i) bit[i] += x;
}

inline int ask(int i) {
    if (i < 1) i = 1;
    int s = 0;
    for (; i <= n + cir; i += i & -i) s += bit[i];
    return s;
}

void getroot_impl(int x, int fa) {
    int i;
    sz[x] = 1; f[x] = 0;
    adj(i, x) if (!vis[v[i]] && v[i] != fa && !no[i]) {
        getroot_impl(v[i], x);
        sz[x] += sz[v[i]];
        f[x] = max(f[x], sz[v[i]]);
    }
    f[x] = max(f[x], sum - sz[x]);
    if (f[x] < f[rt]) rt = x;
}

inline int getroot(int x, int fa, int sz) {
    sum = sz; f[rt = 0] = inf;
    getroot_impl(x, fa); return rt;
}

void dfs_depth(int x, int fa, int d) {
    int i; sk[++top] = d;
    adj(i, x) if (v[i] != fa && !vis[v[i]] && !no[i])
        dfs_depth(v[i], x, d + 1);
}

void divide(int x) {
    int l = 0, i, j;
    vis[x] = 1;
    adj(i, x) if (!vis[v[i]] && !no[i]) {
        l = top; dfs_depth(v[i], x, 1);
        FOR(j,l+1,top) ans += ask(K - sk[j] - 1);
        FOR(j,l+1,top) add(sk[j], 1);
    }
    ans += ask(K - 1);
    while (top) add(sk[top--], -1);
    adj(i, x) if (!vis[v[i]] && !no[i] && sz[v[i]] >= K)
        divide(getroot(v[i], x, sz[v[i]]));
}

void dfs_circle(int x, int fa) {
    if (cir) return;
    int i;
    if (vis[x]) {
        c[cir = 1] = x;
        for (i = top; sk[i] != x; c[++cir] = sk[i--]);
    }
    vis[x] = 1; sk[++top] = x;
    adj(i, x) if (v[i] != fa) dfs_circle(v[i], x);
    vis[x] = 0; --top;
}

inline void work() {
    int i, j;
    dfs_circle(1, 0);
    adj(i, c[1]) if (v[i] == c[cir]) {
        no[i] = no[i ^ 1] = 1;
        break;
    }
    divide(getroot(1, 0, n));
    FOR(i,1,n) vis[i] = 0;
    FOR(i,1,cir+n) bit[i] = 0;
    FOR(i,1,cir) vis[c[i]] = 1;
    FOR(i,1,cir) {
        vis[c[i]] = 0;
        dfs_depth(c[i], 0, 0);
        vis[c[i]] = 1;
        FOR(j,1,top) ans += ask(K - (cir - i + 1) - sk[j]);
        while (top) add(sk[top--] + i, 1);
    }
}

int main() {
    int m, a, b, i;
    n = read(); m = read(); K = read();
    FOR(i,1,m) {
        a = read(); b = read();
        p[++cnt] = h[a]; v[cnt] = b; h[a] = cnt;
        p[++cnt] = h[b]; v[cnt] = a; h[b] = cnt;
    }
    if (n == m) work();
    else divide(getroot(1, 0, n));
    printf("%lld", ans);
    return 0;
}

平衡树版本(21s)

#include <cstdio>
#include <algorithm>
using namespace std;
#define adj(i,j) for(int i=h[j];i;i=p[i])
#define FOR(i,j,k) for(int i=j;i<=k;++i)

const int N = 200005, M = N * 2, inf = 2147483647;
long long ans;
int bit[N], c[N], cir, n, K, sk[N], top, no[N];
int h[N], p[M], v[M], cnt = 1;
int vis[N], f[N], rt, sum, sz[N];

void edge(int a, int b) {
    p[++cnt] = h[a]; v[cnt] = b; h[a] = cnt;
}

namespace SBT {
    int cnt, root;
    struct Node { int l, r, s, key; } tree[N];
#define l(x) tree[x].l
#define r(x) tree[x].r
#define s(x) tree[x].s
#define key(x) tree[x].key
    void reset() { cnt = root = 0; }
    void r_rotate(int &t) {
        int k = l(t); l(t) = r(k); r(k) = t; s(k) = s(t);
        s(t) = s(l(t)) + s(r(t)) + 1;
        t = k;
    }
    void l_rotate(int &t) {
        int k = r(t); r(t) = l(k); l(k) = t; s(k) = s(t);
        s(t) = s(l(t)) + s(r(t)) + 1;
        t = k;
    }
    void maintain(int &t, bool flag) {
        if (!flag) {
            if(s(l(l(t))) > s(r(t)))
                r_rotate(t);
            else if(s(r(l(t))) > s(r(t))) {
                l_rotate(l(t));
                r_rotate(t);
            } else return;
        } else {
            if(s(r(r(t))) > s(l(t)))
                l_rotate(t);
            else if(s(l(r(t))) > s(l(t))) {
                r_rotate(r(t));
                l_rotate(t);
            } else return;
        }
        maintain(l(t), false);
        maintain(r(t), true);
        maintain(t, false);
        maintain(t, true);
    }
    void insert(int &t, int v, bool flag = 1) {
        if (!t) {
            t = ++cnt;
            key(t) = v; l(t) = r(t) = 0;
            s(t) = 1;
        } else {
            ++s(t);
            insert(v < key(t) ? l(t) : r(t), v, 0);
        }
        if (flag) maintain(t, v >= key(t));
    }
    int greaterthan(int t, int w) {
        if (!t) return 0;
        if (key(t) >= w) return s(r(t)) + 1 + greaterthan(l(t), w);
        else return greaterthan(r(t), w);
    }
}

void getroot_impl(int x, int fa) {
    sz[x] = 1; f[x] = 0;
    adj(i, x) if (!vis[v[i]] && v[i] != fa && !no[i]) {
        getroot_impl(v[i], x);
        sz[x] += sz[v[i]];
        f[x] = max(f[x], sz[v[i]]);
    }
    f[x] = max(f[x], sum - sz[x]);
    if (f[x] < f[rt]) rt = x;
}

int getroot(int x, int fa, int sz) {
    sum = sz; f[rt = 0] = inf;
    getroot_impl(x, fa); return rt;
}

void dfs_depth(int x, int fa, int d) {
    sk[++top] = d;
    adj(i, x) if (v[i] != fa && !vis[v[i]] && !no[i])
        dfs_depth(v[i], x, d + 1);
}

void divide(int x) {
    using namespace SBT;
    vis[x] = 1;
    reset();
    adj(i, x) if (!vis[v[i]] && !no[i]) {
        dfs_depth(v[i], x, 1);
        FOR(j,1,top)
            ans += greaterthan(root, K - sk[j] - 1);
        while (top) insert(root, sk[top--]);
    }
    ans += greaterthan(root, K - 1);
    adj(i, x) if (!vis[v[i]] && !no[i] && sz[v[i]] >= K)
        divide(getroot(v[i], x, sz[v[i]]));
}

void dfs_circle(int x, int fa) {
    if (cir) return;
    if (vis[x]) {
        c[cir = 1] = x;
        int j = top;
        while (sk[j] != x) c[++cir] = sk[j--];
        return;
    }
    vis[x] = 1; sk[++top] = x;
    adj(i, x) if (v[i] != fa) dfs_circle(v[i], x);
    vis[x] = 0; --top;
}

void work() {
    using namespace SBT;
    dfs_circle(1, 0);
    adj(i, c[1]) if (v[i] == c[cir]) {
        no[i] = no[i ^ 1] = 1;
        break;
    }
    divide(getroot(1, 0, n));
    FOR(i,1,n) vis[i] = 0;
    reset();
    FOR(i,1,cir) vis[c[i]] = 1;
    FOR(i,1,cir) {
        vis[c[i]] = 0;
        dfs_depth(c[i], 0, 0);
        vis[c[i]] = 1;
        FOR(j,1,top)
            ans += greaterthan(root, K - (cir - i + 1) - sk[j]);
        while (top)
            insert(root, sk[top--] + i, 1);
    }
}

int main() {
    int m, a, b;
    scanf("%d%d%d", &n, &m, &K);
    FOR(i,1,m) {
        scanf("%d%d", &a, &b);
        edge(a, b), edge(b, a);
    }
    if (n == m) work();
    else divide(getroot(1, 0, n));
    printf("%lld\n", ans);
    return 0;
}

3648: 寝室管理

Description

T64有一个好朋友,叫T128。T128是寄宿生,并且最近被老师叫过去当宿管了。宿管可不是一件很好做的工作,碰巧T128有一个工作上的问题想请T64帮忙解决。
T128的寝室条件不是很好,所以没有很多钱来装修。礼间寝室仅由n-1条双向道路连接,而且任意两间寝室之间都可以互达。最近,T128被要求对一条路径上的所有寝室进行管理,这条路径不会重复经过某个点或某条边。但他不记得是哪条路径了。他只记得这条路径上有不少于k个寝室。于是,他想请T64帮忙数一下,有多少条这样的路径满足条件。
嗯…还有一个问题。由于最近有一些熊孩子不准晚上讲话很不爽,他们决定修筑一条“情报通道”,如果通道建成,寝室就变成了一个N个点N条边的无向图。并且,经过“情报通道”的路径也是合法的。T128心想:通道建成之前,T64还有一个高效的算法帮我数路径条数,但是通道建成之后,他还有办法吗?对,T64手忙脚乱,根本数不清有多少条路径。于是他找到了你。

Input

第一行为三个正整数N,M,K(2 ≤ K ≤ N),代表有n间寝室,m条边连接它们n-1 ≤ m ≤ N;m= n-1意味着“情报遁道”未被修好;m=n意味着“情报通道”已被修好),以及题目描述中的K。
接下来m行,每行两个正整数z,y,代表第x间寝室与第y间寝室之间有一条双向边。

Output

仅包含一个整数,代表经过至少K间寝室的路径条数。

Sample Input

5 5 2
1 3
2 4
3 5
4 1
5 2

Sample Output

20

HINT

N100000,KN,M=N

orz 黄学长的Program 唉?!404了?!

#include <cstdio>
#include <cstring>
#include <algorithm>
#define FOR(i,j,k) for(i=j;i<=k;++i)
typedef long long ll;
using namespace std;
const int N = 100005, M = N * 2;
ll ans;
int h[N], p[M], v[M], cnt, n, K;
int f[N], sz[N], dep[N], sum, rt;
int stk[N], top, c[N * 3], tot, cir[M], clen;
bool instk[N], vis[N];
void add(int a, int b) {
    v[++cnt] = b; p[cnt] = h[a]; h[a] = cnt;
}
int query(int i) {
    if (i < 1) return 0;
    int s = 0;
    for (; i; i -= i&-i) s += c[i];
    return s;
}
void modify(int i, int x) {
    tot += x;
    for (; i <= 2 * clen + n; i += i & -i) c[i] += x;
}
void root(int x, int fa) {
    f[x] = 0; sz[x] = 1;
    for (int i = h[x]; i; i = p[i])
        if (v[i] != fa && !vis[v[i]]) {
            root(v[i], x);
            sz[x] += sz[v[i]];
            f[x] = max(f[x], sz[v[i]]);
        }
    f[x] = max(f[x], sum - sz[x]);
    if (f[x] < f[rt]) rt = x;
}
int get_root(int x, int fa, int sz) {
    rt = 0; sum = sz; f[0] = sz + 1;
    root(x, fa); return rt;
}
void depth(int x, int fa) {
    stk[++top] = dep[x];
    for (int i = h[x]; i; i = p[i])
        if (v[i] != fa && !vis[v[i]]) {
            dep[v[i]] = dep[x] + 1;
            depth(v[i], x);
        }
}
void solve(int x) {
    int i, j;
    vis[x] = 1;
    for (i = h[x]; i; i = p[i])
        if (!vis[v[i]]) {
            dep[v[i]] = 1;
            depth(v[i], x);
            FOR(j,1,top) {
                if (stk[j] + 1 >= K) ++ans;
                ans += tot - query(K - stk[j] - 2);
            }
            while (top) modify(stk[top--], 1);
        }
    for (i = h[x]; i; i = p[i])
        if (!vis[v[i]]) {
            depth(v[i], x);
            while (top) modify(stk[top--], -1);
        }
    for (i = h[x]; i; i = p[i])
        if (!vis[v[i]]) {
            if (sz[v[i]] < K) continue;
            solve(get_root(v[i], x, sz[v[i]]));
        }
}
void circle(int x,int fa) {
    if (clen) return;
    stk[++top] = x; instk[x] = 1;
    for (int i = h[x]; i; i = p[i])
        if (v[i] != fa) {
            if (instk[v[i]]) {
                while (stk[top] != v[i]) cir[++clen] = stk[top--];
                cir[++clen] = v[i];
            } else circle(v[i], x);
        }
    top--; instk[x] = 0;
}
void solvecir() {
    int i, j;
    circle(1, 0); top = 0;
    int dis = 2 * clen;
    FOR(i,1,clen) vis[cir[i + clen] = cir[i]] = 1;
    FOR(i,1,clen) {
        vis[cir[i]] = 0;
        solve(get_root(cir[i], 0, n))
        vis[cir[i]] = 1;
    }
    FOR(i,1,n) vis[i] = 0;
    FOR(i,1,clen) vis[cir[i]] = 1;
    FOR(i,1,2 * clen) {
        if (i > clen) {
            vis[cir[i - clen]] = 0;
            depth(cir[i - clen], 0);
            vis[cir[i - clen]] = 1;
            FOR(j,1,top) modify(stk[j] + dis + clen, -1);
            FOR(j,1,top) ans += tot - query(K + dis - stk[j]);
            top = 0;
        }
        dep[cir[i]] = 1;
        vis[cir[i]] = 0;
        depth(cir[i], 0);
        vis[cir[i]] = 1;
        while (top) modify(stk[top--] + dis, 1);
        --dis;
    }
}
int main() {
    int i, a, b, m;
    scanf("%d%d%d", &n, &m, &K);
    FOR(i,1,m) {
        scanf("%d%d", &a, &b);
        add(a, b); add(b, a);
    }
    if (n == m) solvecir();
    else solve(get_root(1, 0, n))
    printf("%lld\n", ans);
    return 0;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值