HDU 4921 Map(状态压缩)

题意看这篇博客

思路参考的这篇博客

补充:面对这种问题有一个常见的套路。比如计算若干个区间对答案的贡献这种问题,直接暴力可能复杂度到O(n ^ 2), 而我们可以计算出每个元素在多少个合法区间中,然后计算贡献,这样可以降到O(n)。对于此题,计算第一类贡献时就是这种方法。计算有多少种情况包含了这个元素,这样就算出了这种元素对第一种答案的贡献。有一个坑点需要注意,在读入每条边x, y时,必选立刻标记y已经访问过,这样最后没被标记过的就是链头。如果不提前标记,从前往后扫描时第一次遇到的没标记的点不一定是链头。

代码:

#include <bits/stdc++.h>
using namespace std;
const int maxn = 10010;
int tot;
int Next[maxn], now[15], len[15];
bool v[maxn];
int val[maxn];
int main() {
	int T, x, y, n, m;
	scanf("%d", &T);
	double res, ans, sum, method;
	bool flag;
	int num;
	while(T--) {
		scanf("%d%d", &n, &m);
		memset(v, 0, sizeof(v));
		memset(Next, 0, sizeof(Next));
		memset(len, 0, sizeof(len));
		memset(now, 0, sizeof(now));
		tot = 0;
		res = 1;
		ans = 0;
		for (int i = 1; i <= n; i++) {
			scanf("%d", &val[i]);
		}
		for (int i = 1; i <= m; i++) {
			scanf("%d%d", &x, &y);
			x++, y++;
			Next[x] = y;
			v[y] = 1;
		}
		for (int i = 1; i <= n; i++) {
			if(v[i]) continue;
			now[tot] = i;
			for (int j = i; j; j = Next[j]) {
				len[tot]++;
			}
			res *= len[tot] + 1;
			tot++;
		}
		for (int dep = 1;; dep++) {
			int cnt = 0;
			for (int i = 0; i < tot; i++) {
				if(now[i])
					cnt++;
			}
			if(cnt == 0) break;
			for (int i = 1; i < (1 << tot); i++) {
				num = 0;
				sum = 0, method = 1; 
				flag = 0;
				for (int j = 0; j < tot; j++) {
					if((i >> j) & 1) {
						if(!now[j]) {
							flag = 1;
							break;
						}
						num++;
						sum += val[now[j]];
						method *= (len[j] - dep + 1);
					} else {
						method *= min(dep, len[j] + 1);
					}
				}
				if(!flag) {
					ans += method * sum;
					if(num > 1)
						ans += method * sum * num / cnt;
				}
			}
			for (int i = 0; i < tot; i++)
				now[i] = Next[now[i]];
		}
		printf("%.3f\n", ans / (res - 1));
	}
} 

  

posted @ 2019-03-18 17:36  维和战艇机  阅读(212)  评论(0编辑  收藏  举报