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
1≤n≤105,1≤m≤106,n<k≤n2,图中可能存在重边和自环。
解:
题目中的原图是指只有点没有边的一个图,不然理解错了就发现样例都不对劲了。
那么首先我们可以找到一个暴力的最优做法,我们这样去思考:首先一号边肯定得开一个组装他,将这个组编号为 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}) [2k−1,2k) ,然后再对这个范围去暴力二分判断即可。
我们来讨论一下这个的复杂度,我们假设我们正在判断的分组长度为 L L L , 2 k 2^k 2k 最大也就到 2 ( L − 1 ) 2(L-1) 2(L−1) ,所以倍增查找到二分区间的复杂度就是 O ( L l o g L ) O(LlogL) O(LlogL) 的,而且 2 k − 2 k − 1 < L 2{^k}-2{^{k - 1}} < L 2k−2k−1<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);
}