[HNOI2008]GT考试 题解
题目地址:洛谷:【P3193】[HNOI2008]GT考试 – 洛谷、BZOJ …
May all the beauty be blessed.
题目地址:洛谷:【P3343】[ZJOI2015]地震后的幻想乡 – 洛谷、BZOJ:Problem 3925. — [Zjoi2015]地震后的幻想乡
傲娇少女幽香是一个很萌很萌的妹子,而且她非常非常地有爱心,很喜欢为幻想乡的人们做一些自己力所能及的事情来帮助他们。 这不,幻想乡突然发生了地震,所有的道路都崩塌了。现在的首要任务是尽快让幻想乡的交通体系重新建立起来。
幻想乡一共有n个地方,那么最快的方法当然是修复n-1条道路将这n个地方都连接起来。 幻想乡这n个地方本来是连通的,一共有m条边。现在这m条边由于地震的关系,全部都毁坏掉了。每条边都有一个修复它需要花费的时间,第i条边所需要的时间为ei。地震发生以后,由于幽香是一位人生经验丰富,见得多了的长者,她根据以前的经验,知道每次地震以后,每个ei会是一个0到1之间均匀分布的随机实数。并且所有ei都是完全独立的。
现在幽香要出发去帮忙修复道路了,她可以使用一个神奇的大魔法,能够选择需要的那n-1条边,同时开始修复,那么修复完成的时间就是这n-1条边的ei的最大值。当然幽香会先使用一个更加神奇的大魔法来观察出每条边ei的值,然后再选择完成时间最小的方案。 幽香在走之前,她想知道修复完成的时间的期望是多少呢?
输入格式:
第一行两个数n,m,表示地方的数量和边的数量。其中点从1到n标号。 接下来m行,每行两个数a,b,表示点a和点b之间原来有一条边。 这个图不会有重边和自环。
输出格式:
一行输出答案,四舍五入保留6位小数。
输入样例#1:
5 4 1 2 1 5 4 3 5 3
输出样例#1:
0.800000
提示:
(以下内容与题意无关,对于解题也不是必要的。)
对于n个[0,1]之间的随机变量x1,x2,…,xn,第k小的那个的期望值是k/(n+1)。
样例解释:
对于第一个样例,由于只有4条边,幽香显然只能选择这4条,那么答案就是4条边的ei中最大的数的期望,由提示中的内容,可知答案为0.8。
数据范围:
对于所有数据:n<=10, m<=n(n-1)/2, n,m>=1。
对于15%的数据:n<=3。
另有15%的数据:n<=10, m=n。
另有10%的数据:n<=10, m=n(n-1)/2。
另有20%的数据:n<=5。
另有20%的数据:n<=8。
题目要求最小生成树上的最大边权Y的期望,因此我们只需要关心这个最大边权在所有边权中的排名,我们发现有下面的式子
\mathrm{E}(Y) = \sum_{i=1}^n \mathrm{E}(i)\mathrm{P}(i) = \sum_{i=1}^n \frac{i}{m+1} \cdot \mathrm{P}(i) = \frac{\sum_{i=1}^n i\mathrm{P}(i)}{m+1} = \frac{\mathrm{E}(Z)}{m+1}
最后我们发现要求的就是最小生成树上最大边权排名的期望值Z。考虑怎么求这个Z,设L为同定义的随机变量,有
\mathrm{E}(Z) = \sum_{i=1}^m i\mathrm{P}(L=i) = \sum_{i=1}^m \mathrm{P}(L \geq i)
如果说最大边排名大于等于i不好求,我们就正难则反,求排名小于i的边集无法构成生成树的概率,由于所有的边权都是随机分布的变量,这个概率可以转化为从边集中随机选择i条边无法构成生成树的概率,我们考虑计算出选择i条边无法构成生成树的方案数,进而计算概率。
通过观察题目数据范围,我们发现n的范围很适合状压。考虑状压DP,用f[S][i]表示在S点集构成的子图中,选i条边使得该点集不连通的方案数,这个状态似乎没法转移,但是我们考虑与其意义互补的量:g[S][i]表示在S点集构成的子图中,选i条边使得该点集连通的方案数,显然有
f[S][i] + g[S][i] = \mathrm{C}_{ecnt[S]}^i
ecnt表示该点集构成子图内的边数。如果知道了其中一个,我们就可以知道另外一个。那么怎么转移成了问题,我们考虑固定S中的某个点,对于S这个点集的一个包含定点的真子集T,只要使T连通但\complement_S T与T之间没有连边就能保证S不连通了,而枚举每一个T可以实现对S不连通情况的遍历。最后我们的转移式就是
f[S][i] = \sum_{T \subsetneqq S, U \in T} \sum_{j=0}^{ecnt[T]} g[T][j] \cdot \mathrm{C}_{ecnt[\complement_S T]}^{i-j}
最后,我们获得了 \mathrm{E}(Z) = \sum_{i=0}^{n-1} f[\mathbb{U}][i] ,直接计算答案即可。
注意爆int。
需要证明的内容:对于n个[0,1]之间的随机变量x1,x2,…,xn,第k小的那个的期望值是k/(n+1)。
我们首先令x_1为第k小值,我们现在来求它的期望。
对于这种情况,我们显然有其中k-1个值比它小,n-k个值比它大,我们分别计算概率,乘起来,就得到了概率密度,对其积分就是x_1为第k小值时x_1的期望值,即\mathrm{E}(x_1, x_1是第k小数)
\begin{aligned} & \int_0^1 x_1 \cdot x_1^{k-1} (1-x_1)^{n-k} \mathrm{d}x_1 \\ = & \int_0^1 x_1^k (1-x_1)^{n-k} \mathrm{d}x_1 \\ = & \mathrm{B}(k+1, n-k+1) \\ = & \frac{k!(n-k)!}{(n+1)!} \end{aligned}
我们知道了这个,但是要求的是E(第k小数),即\mathrm{E}(x_1|x_1是第k小数)。我们记x_1是第k小数这一事件为A,根据下面的关系
\mathrm{E}(x_1|A) = \frac{\mathrm{E}(x_1, A)}{\mathrm{P}(A)}
我们又容易知道
\mathrm{P}(A) = \frac{1}{n \cdot \mathrm{C}_{n-1}^{k-1}}
直接就可以算出来了,结果就是
\mathrm{E}(x_1|A) = \frac{k}{n+1}
// Code by KSkun, 2018/3
#include <cstdio>
typedef long long LL;
inline char fgc() {
static char buf[100000], *p1 = buf, *p2 = buf;
return p1 == p2 && (p2 = (p1 = buf) + fread(buf, 1, 100000, stdin), p1 == p2) ? EOF : *p1++;
}
inline LL readint() {
register LL res = 0, neg = 1;
char c = fgc();
while(c < '0' || c > '9') {
if(c == '-') neg = -1;
c = fgc();
}
while(c >= '0' && c <= '9') {
res = res * 10 + c - '0';
c = fgc();
}
return res * neg;
}
const int MAXN = 13, MAXM = 50;
int n, m, ut, vt;
LL gra[MAXN], cnt[1 << MAXN], ecnt[1 << MAXN], f[1 << MAXN][MAXM], g[1 << MAXN][MAXM];
LL C[MAXM][MAXM];
inline void calc() {
for(int i = 0; i <= m; i++) {
C[i][0] = 1;
for(int j = 1; j <= i; j++) {
C[i][j] = C[i - 1][j - 1] + C[i - 1][j];
}
}
}
int main() {
n = readint(); m = readint();
calc();
for(int i = 0; i < m; i++) {
ut = readint(); vt = readint();
gra[ut] |= (1 << (vt - 1));
gra[vt] |= (1 << (ut - 1));
}
for(int i = 0; i < 1 << n; i++) {
cnt[i] = cnt[i >> 1] + (i & 1);
}
for(int i = 0; i < 1 << n; i++) {
for(int j = 1; j <= n; j++) {
if(i & (1 << (j - 1))) {
ecnt[i] += cnt[gra[j] & i];
}
}
ecnt[i] >>= 1;
}
for(int i = 0; i < 1 << n; i++) {
if(cnt[i] == 1) {
g[i][0] = 1;
continue;
}
int t = i & (-i);
for(int j = (i - 1) & i; j; j = (j - 1) & i) {
if(j & t) {
for(int k1 = 0; k1 <= ecnt[j]; k1++) {
for(int k2 = 0; k2 <= ecnt[i ^ j]; k2++) {
f[i][k1 + k2] += g[j][k1] * C[ecnt[i ^ j]][k2];
}
}
}
}
for(int j = 0; j <= ecnt[i]; j++) {
g[i][j] = C[ecnt[i]][j] - f[i][j];
}
}
double ans = 0;
for(int i = 0; i <= m; i++) {
ans += double(f[(1 << n) - 1][i]) / C[m][i];
}
printf("%.6lf", ans / (m + 1));
return 0;
}
题目地址:Codeforces:Problem – 295B – Codeforces、洛谷:【CF295B】Greg and Graph – 洛谷
Greg has a weighed directed graph, consisting of n vertices. In this graph any pair of distinct vertices has an edge between them in both directions. Greg loves playing with the graph and now he has invented a new game:
Help Greg, print the value of the required sum before each step.
给一个完全图的邻接矩阵,按顺序删点,求每一次删点前剩下的点两两最短路长度的和。
输入格式:
The first line contains integer n (1 ≤ n ≤ 500) — the number of vertices in the graph.
Next n lines contain n integers each — the graph adjacency matrix: the j-th number in the i-th line aij (1 ≤ aij ≤ 105, aii = 0) represents the weight of the edge that goes from vertex i to vertex j.
The next line contains n distinct integers: x1, x2, …, xn (1 ≤ xi ≤ n) — the vertices that Greg deletes.
输出格式:
Print n integers — the i-th number equals the required sum before the i-th step.
Please, do not use the %lld specifier to read or write 64-bit integers in C++. It is preferred to use the cin, cout streams of the %I64d specifier.
输入样例#1:
1 0 1
输出样例#1:
0
输入样例#2:
2 0 5 4 0 1 2
输出样例#2:
9 0
输入样例#3:
4 0 3 1 1 6 0 400 1 2 4 0 1 1 1 1 0 4 1 2 3
输出样例#3:
17 23 404 0
n的范围很小,用O(n^2)的枚举求和是可行的,但是删点这个就不怎么好办了。
正难则反,如果把删点改成加点是否可以呢?
多源最短路让我们想到了Floyd,其实Floyd的实质是一个DP,由于压过维,所以有点看不出,实际上这个式子可以写成这样
dp[k][i][j]= \min (dp[k-1][i][j], dp[k-1][i][k] + dp[k-1][k][j])
如果在这个意义下,我们发现第一维维k的时候指的是只有1~k的图中的最短路。我们考虑把给出的排列反着来跑,计算出x[k]~x[n]的图的最短路,再把这些点的最短路每个算一遍加起来,加进答案序列。
// Code by KSkun, 2018/3
#include <cstdio>
#include <algorithm>
typedef long long LL;
inline char fgc() {
static char buf[100000], *p1 = buf, *p2 = buf;
return p1 == p2 && (p2 = (p1 = buf) + fread(buf, 1, 100000, stdin), p1 == p2) ? EOF : *p1++;
}
inline LL readint() {
register LL res = 0, neg = 1;
char c = fgc();
while(c < '0' || c > '9') {
if(c == '-') neg = -1;
c = fgc();
}
while(c >= '0' && c <= '9') {
res = res * 10 + c - '0';
c = fgc();
}
return res * neg;
}
const int MAXN = 505;
int n, dis[MAXN][MAXN], x[MAXN];
LL ans[MAXN];
int main() {
n = readint();
for(int i = 1; i <= n; i++) {
for(int j = 1; j <= n; j++) {
dis[i][j] = readint();
}
}
for(int i = 1; i <= n; i++) {
x[i] = readint();
}
for(int k = n; k >= 1; k--) {
for(int i = 1; i <= n; i++) {
for(int j = 1; j <= n; j++) {
dis[i][j] = std::min(dis[i][j], dis[i][x[k]] + dis[x[k]][j]);
}
}
for(int i = n; i >= k; i--) {
for(int j = n; j >= k; j--) {
ans[k] += dis[x[i]][x[j]];
}
}
}
for(int i = 1; i <= n; i++) {
printf("%I64d ", ans[i]);
}
return 0;
}
题目地址:洛谷:【P3646】[APIO2015]巴厘岛的雕塑 – 洛谷、BZOJ:Problem 4069. — [Apio2015]巴厘岛的雕塑
印尼巴厘岛的公路上有许多的雕塑,我们来关注它的一条主干道。
在这条主干道上一共有 N 座雕塑,为方便起见,我们把这些雕塑从 1 到 N 连续地进行标号,其中第 i 座雕塑的年龄是 Yi 年。为了使这条路的环境更加优美,政府想把这些雕塑分成若干组,并通过在组与组之间种上一些树,来吸引更多的游客来巴厘岛。
下面是将雕塑分组的规则:
这些雕塑必须被分为恰好 X 组,其中 A< = X< = B,每组必须含有至少一个雕塑,每个雕塑也必须属于且只属于一个组。同一组中的所有雕塑必须位于这条路的连续一段上。
当雕塑被分好组后,对于每个组,我们首先计算出该组所有雕塑的年龄和。
计算所有年龄和按位取或的结果。我们这个值把称为这一分组的最终优美度。
请问政府能得到的最小的最终优美度是多少?
输入格式:
输入的第一行包含三个用空格分开的整数 N,A,B。
第二行包含 N 个用空格分开的整数 Y1,Y2,…,YN。
输出格式:
输出一行一个数,表示最小的最终优美度。
输入样例#1:
6 1 3 8 1 2 1 5 4
输出样例#1:
11
子任务 1 (9 分),1< = N< = 20,1< = A< = B< = N,0< = Yi< = 1000000000
子任务 2 (16 分),1< = N< = 50,1< = A< = B< = min{20,N},0< = Yi< = 10
子任务 3 (21 分),1< = N< = 100,A=1,1< = B< = N,0< = Yi< = 20
子任务 4 (25 分),1< = N< = 100,1< = A< = B< = N,0< = Yi< = 1000000000
子任务 5 (29 分),1< = N< = 2000,A=1,1< = B< = N,0< = Yi< = 1000000000
首先既然是最小,又涉及位运算,应该是对二进制位进行DP没跑了。我们从高位往低位来做DP。
其实要解决的问题是“这一位上能不能填0”,那么我们设计DP状态dp[i][j]表示前i个划分成j段,这一位之前的高位与目前最优解相同,这一位能不能填0。枚举k,从dp[k][j – 1]这个状态来进行转移,如果j~i这一段的和能确保目前最优解(即((sum >> pos) | ans) == ans
)且这种情况下当前位能填0(即!(sum & (1ll << (pos - 1)))
),那么可以转移。
每一位的DP结束后,我们从dp[n][A]到dp[n][B]找是否有DP状态表示解可行,有的话这一位就可以为0,否则必须为1。
那么我们可以知道这个的复杂度是O(n^3 \log (\sum y_i))的,对于子任务5的n范围有些吃紧。
接下来,为了能过子任务5,我们得有一点面向子任务编程的想法。子任务5的特点是A=1,也就是说上面所说的下界限制是没有了的。那么我们考虑直接降低DP状态维度来优化复杂度。我们让dp[i]表示前i个符合目前最优解且最后一位能填0最少的划分段数。我们可以枚举k,从dp[k]像上面类似地往后转移,最后判断的依据是dp[n]是否超过B,超过这一位就必须得为1了。因为不需要枚举j了,复杂度降为O(n^2 \log (\sum y_i))。
// Code by KSkun, 2018/3
#include <cstdio>
#include <cstring>
#include <algorithm>
typedef long long LL;
inline char fgc() {
static char buf[100000], *p1 = buf, *p2 = buf;
return p1 == p2 && (p2 = (p1 = buf) + fread(buf, 1, 100000, stdin), p1 == p2) ? EOF : *p1++;
}
inline LL readint() {
register LL res = 0, neg = 1;
char c = fgc();
while(c < '0' || c > '9') {
if(c == '-') neg = -1;
c = fgc();
}
while(c >= '0' && c <= '9') {
res = res * 10 + c - '0';
c = fgc();
}
return res * neg;
}
const int MAXN = 2005;
int n, a, b;
LL y[MAXN];
int f[MAXN][MAXN], g[MAXN];
int main() {
n = readint();
a = readint();
b = readint();
for(int i = 1; i <= n; i++) {
y[i] = y[i - 1] + readint();
}
int len = 0;
LL t = y[n];
while(t) {
len++;
t >>= 1;
}
LL ans = 0;
if(a == 1) {
for(int pos = len; pos; pos--) {
memset(g, 0x3f, sizeof(g));
g[0] = 0;
for(int i = 1; i <= n; i++) {
for(int j = 0; j < i; j++) {
if(g[j] < b) {
LL sum = y[i] - y[j];
if(((sum >> pos) | ans) == ans && !(sum & (1ll << (pos - 1)))) {
g[i] = std::min(g[i], g[j] + 1);
}
}
}
}
ans <<= 1;
if(g[n] > b) ans++;
}
} else {
for(int pos = len; pos; pos--) {
memset(f, 0, sizeof(f));
f[0][0] = 1;
for(int i = 1; i <= n; i++) {
for(int j = 1; j <= i; j++) {
for(int k = 0; k < i; k++) {
if(f[k][j - 1]) {
LL sum = y[i] - y[k];
if(((sum >> pos) | ans) == ans && !(sum & (1ll << (pos - 1)))) {
f[i][j] = 1;
}
}
}
}
}
ans <<= 1;
bool success = false;
for(int i = a; i <= b; i++) {
if(f[n][i]) {
success = true;
break;
}
}
if(!success) ans++;
}
}
printf("%lld", ans);
return 0;
}