文章目录
P3802 小魔女帕琪(简单排列组合)
所有的排列情况为
(
S
=
∑
a
i
)
!
(S=\sum{a_i})!
(S=∑ai)!。因此接下来考虑可以形成一个大招的情况数量即可。
大招是1234567的排列。即为
7
!
7!
7!,每个数字
i
i
i有
a
i
a_i
ai个选择,为
∏
i
=
1
7
a
i
\prod_{i = 1}^{7}{a_i}
∏i=17ai,这个大招的结束位置可以在
S
−
6
S-6
S−6个位置,即为
C
S
−
6
1
=
s
−
6
C_{S-6}^1 = s-6
CS−61=s−6,最后剩下的位置要咋放就咋放,因此是
(
S
−
7
)
!
(S-7)!
(S−7)!,因此所有排列产生的大招一共有
7
!
∏
i
=
1
7
a
i
C
S
−
6
1
(
S
−
7
)
!
7!\prod_{i = 1}^{7}{a_i}C_{S-6}^{1}(S-7)!
7!∏i=17aiCS−61(S−7)!个,最后除以
S
!
S!
S!即可。
int a[7], s = 0;
int main()
{
double ans = 1;
for(int i = 0; i < 7; i ++)
cin >> a[i], s += a[i], ans *= a[i] * (1 + i);
if(!ans) printf("0.000\n");
else
{
for(int i = 0; i < 6; i ++) ans /= s - i;
printf("%.3f\n", ans);
}
return 0;
}
P1365 WJMZBMR打osu! / Easy(简单期望)
期望题先判断题目是否具有方向性,如果有方向,一般从终止状态向起始状态推。
此题没有方向性,从前到后和从后到前没有什么本质的区别,因此直接从前向后推导即可。
设
f
[
i
]
f[i]
f[i]表示从1到
i
i
i的分数期望,推导到
f
[
i
]
f[i]
f[i],可以将
f
[
i
−
1
]
f[i-1]
f[i−1]的期望当作一个已知值,即直接理解成从1到
i
i
i的分数就是
f
[
i
−
1
]
f[i-1]
f[i−1]。到
i
i
i的时候分3种情况:
- s t r [ i ] = str[i]= str[i]=‘o’,此时改变的是后面连续’o’的长度,我们只能确定最后一个是’o’,并不知道前面的连续长度,因此还需要设另一个变量 l e n [ i ] len[i] len[i],表示到 f [ i ] f[i] f[i]时末尾’o’的连续长度期望。因此可转移: l e n [ i ] = l e n [ i − 1 ] + 1 , f [ i ] = f [ i − 1 ] − ( l e n [ i − 1 ] ∗ l e n [ i − 1 ] ) 2 + ( l e n [ i ] ∗ l e n [ i ] ) 2 len[i]=len[i-1]+1, f[i]=f[i-1]-(len[i-1]*len[i-1])^2+(len[i]*len[i])^2 len[i]=len[i−1]+1,f[i]=f[i−1]−(len[i−1]∗len[i−1])2+(len[i]∗len[i])2 简化一下得到 l e n [ i ] = l e n [ i − 1 ] + 1 , [ i ] = f [ i − 1 ] + 2 ∗ l e n [ i − 1 ] + 1 len[i]=len[i-1]+1 ,[i]=f[i - 1]+2*len[i-1]+1 len[i]=len[i−1]+1,[i]=f[i−1]+2∗len[i−1]+1
- s t r [ i ] = str[i]= str[i]='x’并不会对答案有贡献: l e n [ i ] = 0 , f [ i ] = f [ i − 1 ] len[i]=0, f[i]=f[i-1] len[i]=0,f[i]=f[i−1]
- s t r [ i ] ] str[i]] str[i]]‘?’,为’o’和为’x’的概率相同,因此直接是1,2两种情况结合。
const int N = 3e5 + 10;
int n;
long double f, len;
char s[N];
int main()
{
scanf("%d", &n);
scanf("%s", s + 1);
for(int i = 1; i <= n; i ++)
{
if(s[i] == 'o') f += 2 * len + 1, len ++;
else if(s[i] == 'x') len = 0;
else f += (2 * len + 1) / 2, len = (len + 1) / 2;
}
printf("%.4Lf", f);
return 0;
}
P2634 [国家集训队]聪聪可可(树上期望)
有
n
n
n个点,聪聪任取1个点,即
n
n
n种情况,可可任取1个点,即
n
n
n种情况,根据乘法原理,最终一共有
n
2
n^2
n2条路径。最后要求和为3的倍数的路径条数。很明显要是3的倍数,所以所有边权都直接%3,最后树上权值和也%3,将和都控制在0,1,2,更容易表示,转移和存储。
因为是树的题,所以应该利用树形结构来计算路径条数,或者可以说,是将路径按照和为0,1,2分成3类。
对于某个点 u u u,只考虑【仅经过 u u u与子树组成的路径】,而不考虑其他的路径,对于每个节点都这样考虑,从而划分成小问题。
现在思考:【仅经过
u
u
u与子树组成且路径和为0】的路径有多少条?
{ 最暴力的枚举思路为:设
u
u
u的子树大小为
s
z
sz
sz,那么它到每个
s
z
sz
sz个点一共有
s
z
sz
sz条路径,枚举这
s
z
sz
sz条路径,记作
i
i
i,再枚举剩下的
s
z
−
1
sz-1
sz−1条路径,记作
j
j
j,记录将路径
i
,
j
i,j
i,j合并起来的新路径和为0的条数。但是这样枚举并不正确,因为
i
i
i和
j
j
j两条路径可能会经过同一条边。并且这种枚举的复杂度太高,为
m
2
m^2
m2。因此得解决经过同一条边的问题,并且降低复杂度。}
降低复杂度很容易想到可以将以
u
u
u为一端,以
u
u
u的子树为另一端的路径和为0,1,2的数量进行计算。只需要枚举
u
u
u的每条字数边,
O
(
m
)
O(m)
O(m)即可算出。为了解决不同路径经过同一条子树边的问题,可以在统计
0
,
1
,
2
0,1,2
0,1,2路径和的数量时,在将
u
u
u的当前子节点
v
v
v,相应边为
w
w
w,进行统计之前,先【计算从第1个子树点到第
v
−
1
v-1
v−1个子树点】与【第v个子树点】的路径进行结合的新路径和为0的路径条数,因为还未统计【以
u
u
u为一段,以
v
v
v的子树节点为另一端的路径和为0,1,2】的数量,因此不会统计到经过同一条
u
u
u的子树边的情况。
#include<bits/stdc++.h>
using namespace std;
const int N = 2e4 + 10;
struct E{int to, w, nxt;}e[N << 1];
int head[N], cnt;
void add(int u, int v, int w)
{
e[++ cnt] = {v, w, head[u]};
head[u] = cnt;
}
int gcd(int a, int b) {return !b ? a : gcd(b, a % b);}
int n;
//f[i][j]表示以i为根的子树,以i为路径一端,以i的子树为路径另一端的,和为j的路径数量的前缀和
int f[N][3];
int sub(int a, int b){return (a - b + 3) % 3;}
int ans;
void dfs(int u, int fa)
{
f[u][0] = 1;
for(int i = head[u]; i; i = e[i].nxt)
{
int v = e[i].to; if(v == fa) continue;
dfs(v, u);
for(int j = 0; j < 3; j ++)
ans += f[u][sub(sub(0, j), e[i].w)] * f[v][j] * 2;
for(int j = 0; j < 3; j ++)
f[u][j] += f[v][sub(j, e[i].w)];
}
}
int main()
{
scanf("%d\n", &n);
for(int i = 1; i < n; i ++)
{
int u, v, w; scanf("%d%d%d", &u, &v, &w);
add(u, v, w % 3), add(v, u, w % 3);
}
dfs(1, 0);
ans += n;
int tmp = gcd(ans, n * n);
printf("%d/%d\n", ans / tmp, n * n / tmp);
return 0;
}
CF804D Expected diameter of a tree(树的直径+期望)
#include<bits/stdc++.h>
using namespace std;
const int N = 2e4 + 10;
struct E{int to, w, nxt;}e[N << 1];
int head[N], cnt;
void add(int u, int v, int w)
{
e[++ cnt] = {v, w, head[u]};
head[u] = cnt;
}
int gcd(int a, int b) {return !b ? a : gcd(b, a % b);}
int n;
//f[i][j]表示以i为根的子树,以i为路径一端,以i的子树为路径另一端的,和为j的路径数量的前缀和
int f[N][3];
int sub(int a, int b){return (a - b + 3) % 3;}
int ans;
void dfs(int u, int fa)
{
f[u][0] = 1;
for(int i = head[u]; i; i = e[i].nxt)
{
int v = e[i].to; if(v == fa) continue;
dfs(v, u);
for(int j = 0; j < 3; j ++)
ans += f[u][sub(sub(0, j), e[i].w)] * f[v][j] * 2;
for(int j = 0; j < 3; j ++)
f[u][j] += f[v][sub(j, e[i].w)];
}
}
int main()
{
scanf("%d\n", &n);
for(int i = 1; i < n; i ++)
{
int u, v, w; scanf("%d%d%d", &u, &v, &w);
add(u, v, w % 3), add(v, u, w % 3);
}
dfs(1, 0);
ans += n;
int tmp = gcd(ans, n * n);
printf("%d/%d\n", ans / tmp, n * n / tmp);
return 0;
}
CF16E Fish(简单状压+概率)
double f[1 << 20];
double a[20][20];
int n;
int main()
{
scanf("%d", &n);
for(int i = 0; i < n; i ++) for(int j = 0; j < n; j ++) scanf("%lf", &a[i][j]);
int mx = (1 << n) - 1;
f[mx] = 1;
for(int i = mx; i; i --)
{
bitset<20> b(i); int cnt = b.count();
for(int j = 0; j < n; j ++)
{
if((i >> j) & 1) continue;
int pre = i + (1 << j);
for(int k = 0; k < n; k ++)
{
if(((i >> k) & 1) == 0) continue;
f[i] += f[pre] * a[k][j] * 2 / ((cnt + 1) * cnt);
}
}
}
for(int i = 0; i < n; i ++) printf("%.6f ", f[1 << i]);
return 0;
}
P4562 [JXOI2018]游戏(质因数分解+组合数)
每个排列都有一个最小值t,求所有排列的t的和。很明显排列有
n
!
n!
n!,而t最多只有n种,因此可以看对于每个t对多少个排列有贡献。
对于每个排列,其中有一些必须选的数,这些数在l~r中没有将其作为倍数的数,设一共有s个,因此每个排列至少选s个。
从s到n枚举t的大小,表示检查到第i个所有办公室都认真工作了。
答案为
∑
i
=
s
n
i
∗
f
[
i
]
\sum^{n}_{i=s}{i*f[i]}
∑i=sni∗f[i]
接下来计算f[i],f[i]表示t=i的排列数量。因此第i个必然是s个必选的一个,否则在前
t
−
1
t-1
t−1个已经包含了s个必选,第t个位置不是必选,这个位置的非必选数也可以不选。前
i
i
i确定要s个必选数,剩下
i
−
s
i-s
i−s个,在
n
−
s
n-s
n−s个选,因此有
C
(
n
−
s
,
i
−
s
)
C(n-s,i-s)
C(n−s,i−s),前i-1个位置的数排列为
(
i
−
1
)
!
(i-1)!
(i−1)!,后n-i个数的排列为
n
−
i
n-i
n−i,因此
f
[
i
]
=
s
∗
C
(
n
−
s
,
i
−
s
)
∗
(
i
−
1
)
!
∗
(
n
−
i
)
!
f[i]=s*C(n-s,i-s)*(i-1)!*(n-i)!
f[i]=s∗C(n−s,i−s)∗(i−1)!∗(n−i)!
const int N = 1e7 + 10, mod = 1e9 + 7;
bool vis[N];
int p[N]; //p[0]表示素数的个数 vis表示此数字是否非素数
int mxf[N];
void gp()
{
for(int i = 1; i < N; i ++)
{
if(!vis[i]) p[++ p[0]] = i;
for(int j = 2; j <= p[0] && 1ll * p[j] * i < N; j ++)
{
vis[p[j] * i] = true;
mxf[p[j] * i] = i;
if(i % p[j] == 0) break;
}
}
}
int qpow(int a, int b)
{
int ans = 1;
while(b)
{
if(b & 1) ans = 1ll * ans * a % mod;
a = 1ll * a * a % mod;
b >>= 1;
}
return ans;
}
int fact[N], inv[N];
int C(int n, int m)
{
return 1ll * fact[n] * inv[n - m] % mod * inv[m] % mod;
}
void pre()
{
fact[0] = 1;
for(int i = 1; i < N; i ++) fact[i] = 1ll * fact[i - 1] * i % mod;
inv[N - 1] = qpow(fact[N - 1], mod - 2);
for(int i = N - 2; i >= 0; i --) inv[i] = 1ll * inv[i + 1] * (i + 1) % mod;
}
int main()
{
int l, r; cin >> l >> r;
int s = r - l + 1, n = s;
gp();
pre();
if(l == 1) s = 1;
else
for(int i = l; i <= r; i ++)
{
//最大因数是否在l-r之间
if(vis[i] && mxf[i] >= l) s --;
}
int ans = 0;
for(int i = s; i <= n; i ++)
ans = (ans + 1ll * s * C(n - s, i - s) % mod * fact[i] % mod * fact[n - i] % mod) % mod;
cout << ans << endl;
return 0;
}