UVA11600 Masud Rana

题目描述

PDF

输入格式

输出格式

输入输出样例

输入 #1复制

2
3 1
2 3
4 1
2 3

输出 #1复制

Case 1: 1.0
Case 2: 3.5

一看网上的题解,为什么都是\mathcal{O}(2^n)O(2n)的啊,为什么暴力都能过啊。

先声明一下,下面说的复杂度多不太准确,比如说\mathcal{O}(2^n)O(2n),实际上可能说的是\mathcal{O}(2^n\times n)O(2n×n)或是\mathcal{O}(2^n\times n^2)O(2n×n2),但考虑到这道题对于这种算法的复杂度数据范围放的很宽,且nn很小,这里就忽略不计了,反正这是实现的问题。

然后开始自闭。

结果发现udebug上那份std好像是可以过30 0这组数据的。

于是就大概搞了个复杂度似乎有点真的算法。

先说一下那个\mathcal{O}(2^n)O(2n)的做法吧,我们先考虑把联通的点缩起来,我们姑且称之为团,这样问题就转化成了期望需要走多少次才能遍历所有的团。

然后考虑令f_{i,j}fi,j​表示现在在第ii个团,选取团的状态为jj,直接dp即可,如果直接计搜然后用hash或是map存状态,这道题就直接过掉了。

然后我们发现其实我们只关心每个团的大小而不关心每个团的具体标号,于是我们可以用f_{i,j}fi,j​表示当前在第ii个点,还有jj(jj是一个multiset,当然也可以把这个multiset哈希掉)这些团的期望,然后转移。

转移的话大概就是枚举下一步到那个团,直接计搜下去即可,具体可以看代码。

复杂度?我们发现状态大概是把nn划分成多个数字之和的方案数,也就是划分数,n=30n=30时才50005000。

大概跑的是比较快的,极限数据(100100个30 0)大概只需要跑0.30.3秒(本机)。

#include <bits/stdc++.h>

using namespace std;

typedef long long LL;

// read/write的代码略去

const int maxn = 305;

int n, m;

struct ZT {
	static const int mod = 19260817;

	multiset<int> st;
	int hsh, xx;
	
	inline void init() {
		hsh = xx;
		for (auto x : st) {
			hsh = ((LL) hsh * hsh % mod * hsh % mod + x) % mod;
		}
	}

	ZT () {
		hsh = 0, xx = 0;
		st.clear();
	}
};

int siz[maxn];
int fa[maxn];

inline int getfa(int x) {
	return fa[x] == x ? x : fa[x] = getfa(fa[x]);
}

inline void merge(int x, int y) {
	int fax = getfa(x);
	int fay = getfa(y);
	if (fax != fay) {
		if (fax > fay) {
			swap(fax, fay);
		}
		siz[fax] += siz[fay];
		fa[fay] = fax;
	}
}

map<int, double> mp;

inline double dfs(const ZT& now) {
	if (now.st.empty()) {
		return 0;
	}
	if (mp.count(now.hsh)) {
		return mp[now.hsh];
	}
	double ans = 0;
	for (auto x : now.st) {
		ZT tmp = now;
		auto it = tmp.st.find(x);
		tmp.xx += x;
		tmp.st.erase(it);
		tmp.init();
		ans += dfs(tmp) * x;
	}
	ans /= n - 1;
	ans++;
	ans /= 1. - (double) (now.xx - 1) / (double) (n - 1);
	return mp[now.hsh] = ans;
}

inline double solve() {
	mp.clear();
	read(n), read(m);
	for (int i = 1; i <= n; ++i) {
		fa[i] = i;
		siz[i] = 1;
	}
	for (int i = 1; i <= m; ++i) {
		int x, y;
		read(x), read(y);
		merge(x, y);
	}
	ZT fir;
	fir.xx = siz[1];
	for (int i = 2; i <= n; ++i) {
		if (fa[i] == i) {
			fir.st.insert(siz[i]);
		}
	}
	fir.init();
	return dfs(fir);
}

int main() {
	int T;
	read(T);
	for (int i = 1; i <= T; ++i) {
		printf("Case %d: %.10f\n", i, solve());
	}
	return 0;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值