题意看这篇博客。
思路参考的这篇博客。
补充:面对这种问题有一个常见的套路。比如计算若干个区间对答案的贡献这种问题,直接暴力可能复杂度到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 ", ans / (res - 1)); } }