牛客练习赛80 D 分组 倍增 + 二分 + 强连通分量

D 分组

题目描述

你有一张 n 个点的有向图,我们定义一个有向图的重量是该图内所有强连通分量大小(即其节点数量)的平方和。
有 m 条边,依次编号为 1,2,…,m。现在需要将其分成若干组,满足:

1.每一组内边的编号连续;
2.对于每一组边,将其加入原图内得到 G,需要满足 G 的重量不能超过 k,其中 k 是给定的阈值。

请求出最小分组的数量。

输入描述:

一行三个整数 n,m,k,分别表示图中节点数,给出的边数和阈值的大小;
接下来 m 行,每行两个整数 u,v,表示一条从 u 到 v 的有向边。

输出描述:

一行一个整数,表示最小分组的数量。

输入

6 12 8
1 2
2 3
3 4
3 2
3 1
4 1
4 5
1 5
6 5
5 6
3 6
6 3

输出

3

备注:
对于100%的数据,保证 1 ≤ n ≤ 1 0 5 , 1 ≤ m ≤ 1 0 6 , n < k ≤ n 2 1≤n≤10^5,1≤m≤10^6,n<k≤n^2 1n105,1m106,n<kn2,图中可能存在重边和自环。


题目中的原图是指只有点没有边的一个图,不然理解错了就发现样例都不对劲了。

那么首先我们可以找到一个暴力的最优做法,我们这样去思考:首先一号边肯定得开一个组装他,将这个组编号为 1 1 1 ,那么对于后面的边来说,现在有一个现成的组可能可以装,那么自然不会再开新的组,所以 1 1 1 组能装多少条就装多少条边。那么选完一组之后用同样的思想重复这个过程就可得最小分组。因为题目要求分组其下标要连续,那么这个过程就可以用二分来优化。但是这样平均下来一次 c h e c k check check 需要 O ( n ) O(n) O(n) 的时间, 整体下来要将近 O ( n 2 l o g n ) O(n^2 logn) O(n2logn)

所以此处我们可以用倍增来优化这个过程,对于某个组的起点来说,我们每次加 2 k 2^{k} 2k 条边, k k k 0 0 0 开始,加到不符合条件位置,那么我们二分的范围就降到了 [ 2 k − 1 , 2 k ) [2{^{k - 1}}, 2{^k}) [2k1,2k) ,然后再对这个范围去暴力二分判断即可。

我们来讨论一下这个的复杂度,我们假设我们正在判断的分组长度为 L L L 2 k 2^k 2k 最大也就到 2 ( L − 1 ) 2(L-1) 2(L1) ,所以倍增查找到二分区间的复杂度就是 O ( L l o g L ) O(LlogL) O(LlogL) 的,而且 2 k − 2 k − 1 < L 2{^k}-2{^{k - 1}} < L 2k2k1<L (画个线段即可证得),所以之后暴力二分的复杂度也是大约 O ( L l o g L ) O(LlogL) O(LlogL) ,所以分出一个长度为 L L L 的区间复杂度就是 O ( L l o g L ) O(LlogL) O(LlogL) 。所以假设我们一共有 x x x 个分组,整体的复杂度为 ∑ i = 0 x L i   l o g L \sum_{i = 0}^x L_i\ logL i=0xLi logL ,那么我们把这个复杂度放大为 ∑ i = 1 x L i   l o g n = n l o g n \sum_{i = 1}^x L_i\ logn = {nlogn} i=1xLi logn=nlogn

Tarjan实现

#include <bits/stdc++.h>

typedef long long ll;

const int N = 1e5 + 5;

using namespace std;

struct Edge {
    int u, v;
}edge[10 * N];
int n, m;
ll k, ans;
int dfn[N], low[N], instack[N], d_time, num;
vector<int> g[N];
stack<int> s;
vector<int> dot;

void tarjan(int u) {
    num++;
    s.push(u);
    dfn[u] = low[u] = ++d_time;
    instack[u] = 1;
    for (int i = 0; i < (int)g[u].size(); ++i) {
        int v = g[u][i];
        if (!dfn[v]) {
            tarjan(v);
            low[u] = min(low[u], low[v]);
        }
        else if (instack[v]) {
            low[u] =  min(low[u], dfn[v]);
        }
    }
    if (dfn[u] == low[u]) {
        int size = 0;
        while(true) {
            size++;
            int temp = s.top();
            s.pop();
            instack[temp] = 0;
            if (temp == u) break;
        }
        ans += 1ll * size * size;
    }
}

bool check(int l, int r) {
    for (int i = l; i <= r; ++i) {
        int u = edge[i].u, v = edge[i].v;
        g[u].push_back(v);
        dot.push_back(u);
        dfn[u] = dfn[v] = low[u] = low[v] = 0;
    }
    num = 0;
    ans = 0;
    for (int i = 0; i < (int)dot.size(); ++i) {
        if (!dfn[dot[i]]) {
            tarjan(dot[i]);
        }
    }
    ans += n - num;
    for (int i = 0; i < (int)dot.size(); ++i) g[dot[i]].clear();
    dot.clear();
    //cout << l << ' ' << r << ' ' << ans << endl;
    return ans <= k;
}

int main() {
#ifndef ONLINE_JUDGE
    freopen("in.txt", "r", stdin);
    freopen("out.txt", "w", stdout);
#endif
    scanf("%d%d%lld", &n, &m, &k);
    for (int i = 1; i <= m; ++i) {
        int u, v;
        scanf("%d%d", &u, &v);
        edge[i] = {u, v};
    }
    int s = 1, l, r, temp, cnt = 0;
    while(s <= m) {
        l = s, temp = 1;
        while(true) {
            if (!(l + temp <= m && check(l, l + temp))) {
                r = min(l + temp, m);
                break;
            }
            temp <<= 1;
        }
        while(l <= r) {
            int mid = l + r >> 1;
            if (check(s, mid)) l = mid + 1;
            else r = mid - 1;
        }
        s = l;
        cnt++;
    }
    printf("%d", cnt);
}

Gabow实现 PS:虽然没比Tarjan快太多,但是好歹还是快那么一点点[doge]

#include <bits/stdc++.h>

typedef long long ll;

const int N = 1e5 + 5;

using namespace std;

struct Edge {
    int u, v;
}edge[10 * N];
int n, m;
ll k, ans;
int dfn[N], instack[N], d_time, num;
vector<int> g[N], op;
stack<int> stk1, stk2;

void gabow(int u) {
    num++;
    stk1.push(u), stk2.push(u);
    instack[u] = 1;
    dfn[u] = ++d_time;
    for (int i = 0; i < (int)g[u].size(); ++i) {
        int v = g[u][i];
        if (!dfn[v]) {
            gabow(v);
        }
        else if (instack[v]) {
            while(dfn[stk2.top()] > dfn[v]) stk2.pop();
        }
    }
    if (u == stk2.top()) {
        int size = 1;
        while(u ^ stk1.top()) {
            int v = stk1.top();
            stk1.pop();
            instack[v] = 0;
            size++;
        }
        instack[u] = 0;
        stk1.pop(), stk2.pop();
        ans += 1ll * size * size;
    }
}

bool check(int l, int r) {
    for (int i = l; i <= r; ++i) {
        int u = edge[i].u, v = edge[i].v;
        g[u].push_back(v);
        dfn[u] = dfn[v] = 0;
        op.push_back(u);
    }
    ans = num = 0;
    for (int i = 0; i < (int)op.size(); ++i) {
        if (!dfn[op[i]]) {
            gabow(op[i]);
        }
    }
    for (int i = 0; i < (int)op.size(); ++i) g[op[i]].clear();
    op.clear();
    ans += n - num;
    //cout << l << ' ' << r << ' ' << ans << ' ' << endl;
    return ans <= k;
}

int main() {
#ifndef ONLINE_JUDGE
    freopen("in.txt", "r", stdin);
    freopen("out.txt", "w", stdout);
#endif
    scanf("%d%d%lld", &n, &m, &k);
    for (int i = 1; i <= m; ++i) {
        int u, v;
        scanf("%d%d", &u, &v);
        edge[i] = {u, v};
    }
    int s = 1, l, r, temp, cnt = 0;
    while(s <= m) {
        l = s, temp = 1;
        while(true) {
            if (!(l + temp <= m && check(l, l + temp))) {
                r = min(l + temp, m);
                break;
            }
            temp <<= 1;
        }
        while(l <= r) {
            int mid = l + r >> 1;
            if (check(s, mid)) l = mid + 1;
            else r = mid - 1;
        }
        s = l;
        cnt++;
    }
    printf("%d", cnt);
}
  • 1
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值