分组 题解

4 篇文章 0 订阅
3 篇文章 0 订阅
题面

在这里插入图片描述

样例
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
分析

可以说是一个套路/思维模式吧

不难想到 O ( n 3 / k ) \mathcal{O}(n^3/k) O(n3/k) k k k 为常数)的暴力,二分优化一下 O ( n 2 l o g 2 ( n ) ) \mathcal{O}(n^2log_2(n)) O(n2log2(n))

考虑时间复杂度为什么会这么高,发现是二分时进行了太多冗余的操作。

考虑缩短二分上下界。不妨设现在要取的边的数量为 t t t,要找到一个 q q q,使 t ∈ [ 2 q , 2 q + 1 ) t\in[2^q,2^{q+1}) t[2q,2q+1)(可以直接找(log)或二分(log(log))),这是 O ( l o g 2 ( t ) × t ( × 2 ) ) \mathcal{O}(log_2(t)\times t(\times2)) O(log2(t)×t(×2)) 的,再在这里面二分,这是 O ( l o g 2 ( t ) × t ( × 2 ) ) \mathcal{O}(log_2(t)\times t(\times 2)) O(log2(t)×t(×2)) 的,总时间复杂度: O ( ∑ t × l o g 2 ( t ) ) ≤ O ( ∑ t × l o g 2 ( m ) ) = O ( m × l o g 2 ( m ) ) ( ( ∑ t ) = = m ) \mathcal{O}(\sum t\times log_2(t))\leq O(\sum t\times log_2(m))=O(m\times log_2(m))((\sum t) == m) O(t×log2(t))O(t×log2(m))=O(m×log2(m))((t)==m)

这优化就很妙。。。

Code

#include <cstdio>
#include <algorithm>
#include <cmath>
#include <cstring>
#include <climits>
#define LL long long
using namespace std;
const int MAXN = 1e5 + 5, MAXM = 1e6 + 5;
int n, m, X[MAXM], Y[MAXM], tot, cnt, now = 1, col[MAXN], dfn[MAXN], low[MAXN];
int s[MAXN], stot, num, sl, sr, nk, clock, val;
LL ans, k;
int Head[MAXN], Ver[MAXM << 1], Next[MAXM << 1];
void add(int x, int y) { Ver[++ tot] = y; Next[tot] = Head[x]; Head[x] = tot; }
void read(int &x) {
	x = 0; int f = 1; char c = getchar();
	for(; c < '0' || c > '9'; c = getchar()) if(c == '-') f = 0;
	for(; c >= '0' && c <= '9'; c = getchar()) x = (x << 1) + (x << 3) + (c ^ 48);
	x = f ? x : -x;
}
int Max(int x, int y) { return x > y ? x : y; }
int Min(int x, int y) { return x < y ? x : y; }
void dfs(int x) {
	dfn[x] = low[x] = ++ cnt; s[++ stot] = x; num ++;
	for(int i = Head[x]; i; i = Next[i]) {
		if(i < sl || i > sr) continue;
		int Y = Ver[i];
		if(!dfn[Y]) dfs(Y), low[x] = Min(low[x], low[Y]);
		else if(!col[Y]) low[x] = Min(low[x], dfn[Y]);
	}
	if(dfn[x] == low[x]) {
		int t; nk ++; clock = 0;
		do {
			t = s[stot --]; col[t] = nk; clock ++;
		} while(t != x);
		ans += (LL)clock * clock;
	}
}
bool Check(int l, int r) {
	num = 0; cnt = 0; ans = 0; sl = l; sr = r; stot = 0; nk = 0;
	for(int i = l; i <= r; i ++) dfn[X[i]] = dfn[Y[i]] = low[X[i]] = low[Y[i]] = col[X[i]] = col[Y[i]] = 0;
	for(int i = l; i <= r; i ++) {
		if(!dfn[X[i]]) dfs(X[i]);
		if(!dfn[Y[i]]) dfs(Y[i]);
	}
	ans += n - num;
//	printf("|%d %d %lld|\n", l, r, ans);
	return (ans <= k);
}
int main() {
	read(n); read(m); scanf("%lld", &k);
	for(int i = 1; i <= m; i ++) {
		read(X[i]); read(Y[i]); add(X[i], Y[i]);
	}
	while(now <= m) {
		int l = now, r = 0, len = 0, mid, res;
		while(1) {
			if(r < m && Check(l, Min(m, l + (1 << len)))) r = Min(m, l + (1 << len)), len ++;
			else break; 
		}
		if(r == m) { val ++; break; }
		l = r; r = Min(m, now + (1 << len) - 1);
		while(l <= r) {
			mid = (l + r) >> 1;
			if(Check(now, mid)) res = mid, l = mid + 1;
			else r = mid - 1;
		}
		now = res + 1; val ++;// printf("|%d|", now);
	}
	printf("%d", val);
	return 0;
}

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值