周三
前段时间没有写日报,而是写专题了
概率/期望dp专题_Alex Su (*^▽^*)的博客-CSDN博客
博弈论专题_Alex Su (*^▽^*)的博客-CSDN博客
现在觉得写日报才每天有收获感,有动力,于是又开始写日报
P3802 小魔女帕琪(手推概率)
这题就是手算,算出1~7触发的概率,再算一下2~8触发的概率,发现是一样的,然后就可以推出答案了
#include<bits/stdc++.h>
#define rep(i, a, b) for(int i = (a); i < (b); i++)
#define _for(i, a, b) for(int i = (a); i <= (b); i++)
using namespace std;
int main()
{
double a[10], N;
_for(i, 1, 7) scanf("%lf", &a[i]), N += a[i];
double ans = (N - 6) * 5040;
_for(i, 1, 7) ans *= a[i] / max(N - i + 1, 1.0);
printf("%.3f\n", ans);
return 0;
}
P1850 [NOIP2016 提高组] 换教室(期望dp+floyed)
这题的期望比较特殊,它是比较独立的,概率不需要连乘
也就是说期望体现在两个相邻时间段之间的贡献。这个时候就不是那种比较经典的期望dp了。
细节上,注意初始化和边界问题。
floyed的时候注意自己自己到自己为0,我忽略了这个卡了很久。
#include<bits/stdc++.h>
#define rep(i, a, b) for(int i = (a); i < (b); i++)
#define _for(i, a, b) for(int i = (a); i <= (b); i++)
using namespace std;
const int N = 2e3 + 10;
const int M = 300 + 10;
int n, m, v, e, c[N], d[N], g[M][M];
double k[N], dp[N][N][2];
int main()
{
scanf("%d%d%d%d", &n, &m, &v, &e);
_for(i, 1, n) scanf("%d", &c[i]);
_for(i, 1, n) scanf("%d", &d[i]);
_for(i, 1, n) scanf("%lf", &k[i]);
memset(g, 0x3f, sizeof g);
while(e--)
{
int a, b, w;
scanf("%d%d%d", &a, &b, &w);
g[a][b] = g[b][a] = min(g[a][b], w);
}
_for(K, 1, v)
_for(i, 1, v)
_for(j, 1, v)
g[i][j] = min(g[i][j], g[i][K] + g[K][j]);
_for(i, 1, v) g[i][i] = 0; //写弗洛伊德时记得要加上自己和自己为0
_for(i, 0, n)
_for(j, 0, m)
dp[i][j][0] = dp[i][j][1] = 1e9;
dp[1][1][1] = dp[1][0][0] = 0;
_for(i, 2, n)
_for(j, 0, m)
{
int c1 = c[i - 1], c2 = c[i], d1 = d[i - 1], d2 = d[i];
dp[i][j][0] = min(dp[i - 1][j][0] + g[c1][c2], dp[i - 1][j][1]
+ k[i - 1] * g[d1][c2] + (1 - k[i - 1]) * g[c1][c2]);
if(j > 0)
dp[i][j][1] = min(dp[i - 1][j - 1][0] + k[i] * g[c1][d2] + (1 - k[i]) * g[c1][c2],
dp[i - 1][j - 1][1] + k[i - 1] * k[i] * g[d1][d2] + (1 - k[i - 1]) * k[i] * g[c1][d2] +
k[i - 1] * (1 - k[i]) * g[d1][c2] + (1 - k[i - 1]) * (1 - k[i]) * g[c1][c2]);
}
double ans = 1e9;
_for(i, 0, m) ans = min(ans, min(dp[n][i][0], dp[n][i][1]));
printf("%.2f\n", ans);
return 0;
}
E. Vasya and Magic Matrix(dp+前缀和优化)
令N=nm,从数据范围来看,是一个O(N)的算法
直接dp显然要O(N^2)
把式子拆开后发现有很多前缀和,所以可以预处理前缀和来优化
#include<bits/stdc++.h>
#define rep(i, a, b) for(int i = (a); i < (b); i++)
#define _for(i, a, b) for(int i = (a); i <= (b); i++)
using namespace std;
typedef long long ll;
const int N = 1e6 + 10;
const int mod = 998244353;
ll dp[N], xx[N], x[N], y[N], yy[N], f[N];
struct node
{
int val, x, y;
}ve[N];
int n, m, r, c, cnt;
ll binpow(ll a, ll b)
{
ll res = 1;
for(; b; b >>= 1)
{
if(b & 1) res = res * a % mod;
a = a * a % mod;
}
return res;
}
ll inv(ll x) { return binpow(x, mod - 2); }
bool cmp(node a, node b)
{
return a.val < b.val;
}
int main()
{
scanf("%d%d", &n, &m);
_for(i, 1, n)
_for(j, 1, m)
{
int x; scanf("%d", &x);
ve[++cnt] = {x, i, j};
}
scanf("%d%d", &r, &c);
sort(ve + 1, ve + cnt + 1, cmp);
_for(i, 1, cnt)
{
x[i] = (x[i - 1] + ve[i].x) % mod; xx[i] = (xx[i - 1] + ve[i].x * ve[i].x) % mod;
y[i] = (y[i - 1] + ve[i].y) % mod; yy[i] = (yy[i - 1] + ve[i].y * ve[i].y) % mod;
}
ll tot = 0;
_for(i, 1, cnt)
{
if(i == 1 || ve[i].val != ve[i - 1].val) tot = i - 1;
dp[i] = (tot * ve[i].x * ve[i].x % mod + xx[tot] - 2 * ve[i].x * x[tot] % mod +
tot * ve[i].y * ve[i].y % mod + yy[tot] - 2 * ve[i].y * y[tot] % mod + f[tot]) % mod;
dp[i] = dp[i] * inv(tot) % mod;
f[i] = (f[i - 1] + dp[i]) % mod;
if(ve[i].x == r && ve[i].y == c)
{
printf("%lld\n", dp[i]);
break;
}
}
return 0;
}
P1365 WJMZBMR打osu! / Easy(期望dp)
这题比较考数学
令Fi表示以i为结尾分数的期望。发现要用到以i-1为结尾的长度的期望,注意是它的期望。
因此还要维护Li。
#include<bits/stdc++.h>
#define rep(i, a, b) for(int i = (a); i < (b); i++)
#define _for(i, a, b) for(int i = (a); i <= (b); i++)
using namespace std;
const int N = 3e5 + 10;
long double F[N], L[N];
char s[N];
int n;
int main()
{
scanf("%d%s", &n, s + 1);
_for(i, 1, n)
{
if(s[i] == 'o')
{
F[i] = F[i - 1] + 2 * L[i - 1] + 1;
L[i] = L[i - 1] + 1;
}
else if(s[i] == 'x')
{
F[i] = F[i - 1];
L[i] = 0;
}
else
{
F[i] = F[i - 1] + L[i - 1] + 0.5;
L[i] = (L[i - 1] + 1) / 2;
}
}
printf("%.4Lf\n", F[n]);
return 0;
}
P1654 OSU!(期望dp)
和上一题类似,只是平方改成了三次方,要维护的东西多了一些而已
#include<bits/stdc++.h>
#define rep(i, a, b) for(int i = (a); i < (b); i++)
#define _for(i, a, b) for(int i = (a); i <= (b); i++)
using namespace std;
const int N = 1e5 + 10;
double F[N], L[N], T[N], p[N];
int n;
int main()
{
scanf("%d", &n);
_for(i, 1, n) scanf("%lf", &p[i]);
_for(i, 1, n)
{
F[i] = (F[i - 1] + 3 * T[i - 1] + 3 * L[i - 1] + 1) * p[i] + F[i - 1] * (1 - p[i]);
L[i] = (L[i - 1] + 1) * p[i];
T[i] = (T[i - 1] + 2 * L[i - 1] + 1) * p[i];
}
printf("%.1f\n", F[n]);
return 0;
}
P6046 纯粹容器(期望+组合数学)
这题和dp没啥关系,很数学的一道题
显然对于一个容器,答案是
fi表示第i轮被干掉的概率 但是这个不好求,转化为前缀和
即
pi即前i轮被干掉的概率,这个好求,用古典概型求。
分两种情况求,一种是被左边第一个大于它的干掉,一个是右边。
把操作看作是在一条链上选择边,这样就有n-1条边去选,如果被左边的干掉,那么l~i的边一定要全选。
#include<bits/stdc++.h>
#define rep(i, a, b) for(int i = (a); i < (b); i++)
#define _for(i, a, b) for(int i = (a); i <= (b); i++)
using namespace std;
typedef long long ll;
const int N = 50 + 10;
const int mod = 998244353;
ll c[N][N], p[N];
int n, a[N];
ll binpow(ll a, ll b)
{
ll res = 1;
for(; b; b >>= 1)
{
if(b & 1) res = res * a % mod;
a = a * a % mod;
}
return res;
}
ll inv(ll x) { return binpow(x, mod - 2); }
int main()
{
_for(i, 0, 50) c[i][0] = 1;
_for(i, 1, 50)
_for(j, 1, i)
c[i][j] = (c[i - 1][j] + c[i - 1][j - 1]) % mod;
scanf("%d", &n);
_for(i, 1, n) scanf("%d", &a[i]);
_for(i, 1, n)
{
int l = -1, r = -1;
for(int j = i - 1; j >= 1; j--)
if(a[j] > a[i])
{
l = j;
break;
}
_for(j, i + 1, n)
if(a[j] > a[i])
{
r = j;
break;
}
if(l == -1 && r == -1)
{
printf("%d ", n - 1);
continue;
}
_for(j, 1, n - 1)
{
ll pa = 0, pb = 0, pab = 0;
if(l != -1 && j >= (i - l)) pa = c[n - 1 - (i - l)][j - (i - l)] * inv(c[n - 1][j]) % mod;
if(r != -1 && j >= (r - i)) pb = c[n - 1 - (r - i)][j - (r - i)] * inv(c[n - 1][j]) % mod;
if(l != -1 && r != -1 && j >= (i - l) + (r - i)) pab = c[n - 1 - (r - i) - (i - l)][j - (r - i) - (i - l)] * inv(c[n - 1][j]) % mod;
p[j] = (pa + pb - pab + mod) % mod;
}
ll ans = 0;
_for(j, 1, n - 1)
ans = (ans + (p[j] - p[j - 1]) * (j - 1)) % mod;
printf("%lld ", (ans + mod) % mod);
}
return 0;
}
周四
CF280C Game on Tree(期望)
看作一个长度为n的操作序列
操作看作染黑,一个点一染黑,就把它的子孙都染黑
那么一个点被操作的前提是它的祖先都在它后面
因此概率为 1 / dep[u]
操作次数的期望等于每个点操作的期望之和
每个点操作的期望就等于概率
#include<bits/stdc++.h>
#define rep(i, a, b) for(int i = (a); i < (b); i++)
#define _for(i, a, b) for(int i = (a); i <= (b); i++)
using namespace std;
const int N = 1e5 + 10;
vector<int> g[N];
int d[N], n;
void dfs(int u, int fa)
{
d[u] = d[fa] + 1;
for(int v: g[u])
{
if(v == fa) continue;
dfs(v, u);
}
}
int main()
{
scanf("%d", &n);
_for(i, 1, n - 1)
{
int u, v;
scanf("%d%d", &u, &v);
g[u].push_back(v);
g[v].push_back(u);
}
dfs(1, 0);
double ans = 0;
_for(i, 1, n)
ans += 1.0 / d[i];
printf("%.10f\n", ans);
return 0;
}
A. Little Pony and Expected Maximum(期望)
和前面一道题的思路类似
fi表示最大值为i的概率
等于某一值的概率不好算,要转化成小于等于某一值的概率,这样就可以用古典概型
即
#include<bits/stdc++.h>
#define rep(i, a, b) for(int i = (a); i < (b); i++)
#define _for(i, a, b) for(int i = (a); i <= (b); i++)
using namespace std;
const int N = 1e5 + 10;
long double p[N];
int n, m;
long double binpow(long double a, int b)
{
long double res = 1;
for(; b; b >>= 1)
{
if(b & 1) res *= a;
a *= a;
}
return res;
}
int main()
{
scanf("%d%d", &m, &n);
_for(i, 1, m) p[i] = binpow(1.0 * i / m, n);
long double ans = 0;
_for(i, 1, m)
ans += (p[i] - p[i - 1]) * i;
printf("%.10Lf\n", ans);
return 0;
}
SP1026 FAVDICE - Favorite Dice(期望)
之前做过差不多一样的题,用dp做的。还有一种纯数学的方法
如果现在有k个图案,拿到一个新的图案的概率为(n - k) / n
有一个结论是,概率为p的事件发生所需要的期望次数是1/p
因此此时要n / (n / k)期望次数才能发生
这样枚举k,就可得到期望发生了多少次才得到全部答案
周五
P4550 收集邮票(期望)
做这种期望题除了用期望dp,即设一个dp状态表示已经……还需要多少的期望。这题同样可以用这种思路。
但还一种思路就是考虑每一次对答案的贡献,对于这道题这种思路特别简单
如果已经有了k种,再增加一种的概率是(n - k) / n
那么此时需要购买的期望次数为n / (n - k)
那么枚举k从0到n-1,考虑每一次对答案的贡献
对于次数就是当前的期望次数n / (n - k)
对于价格,就是期望次数的前缀和,也就是当前是第几次购买
#include<bits/stdc++.h>
#define rep(i, a, b) for(int i = (a); i < (b); i++)
#define _for(i, a, b) for(int i = (a); i <= (b); i++)
using namespace std;
const int N = 1e5 + 10;
double p[N];
int n;
int main()
{
scanf("%d", &n);
_for(i, 0, n - 1) p[i] = 1.0 * n / (n - i);
double ans = 0, sum = 0;
_for(i, 0, n - 1)
{
sum += p[i];
ans += p[i] * sum;
}
printf("%.2f\n", ans);
return 0;
}
CodeForces 547B(单调栈)
考虑一个数的贡献,即当前数在多长的区间中可以作为最小值。这个可以用单调栈O(n)来实现。
然后再把每个数的贡献合并起来即可。
#include <bits/stdc++.h>
#define rep(i, a, b) for(int i = (a); i < (b); i++)
#define _for(i, a, b) for(int i = (a); i <= (b); i++)
using namespace std;
const int N = 2e5 + 10;
int l[N], r[N], s[N], a[N], top, n;
int t[N], ans[N];
int main()
{
scanf("%d", &n);
_for(i, 1, n) scanf("%d", &a[i]);
for(int i = n; i >= 1; i--)
{
while(top && a[s[top]] >= a[i]) top--;
r[i] = top ? s[top] : n + 1;
s[++top] = i;
}
top = 0;
_for(i, 1, n)
{
while(top && a[s[top]] >= a[i]) top--;
l[i] = top ? s[top] : 0;
s[++top] = i;
}
_for(i, 1, n)
{
int len = r[i] - l[i] - 1;
t[len] = max(t[len], a[i]);
}
ans[n] = t[n];
for(int i = n - 1; i >= 1; i--)
ans[i] = max(ans[i + 1], t[i]);
_for(i, 1, n) printf("%d ", ans[i]);
return 0;
}
CodeForces 1540A(思维)
关键是发现点的顺序是不影响的,所以直接排序,然后贪心即可
#include <bits/stdc++.h>
#define rep(i, a, b) for(int i = (a); i < (b); i++)
#define _for(i, a, b) for(int i = (a); i <= (b); i++)
using namespace std;
typedef long long ll;
const int N = 1e5 + 10;
int a[N], n;
int main()
{
int T; scanf("%d", &T);
while(T--)
{
scanf("%d", &n);
_for(i, 1, n) scanf("%d", &a[i]);
sort(a + 1, a + n + 1);
ll ans = 0, sum = 0;
_for(i, 2, n) ans += a[i] - a[i - 1];
sum = a[n];
for(int i = n - 1; i >= 1; i--)
{
ans += 1LL * (n - i) * a[i] - sum;
sum += a[i];
}
printf("%lld\n", ans);
}
return 0;
}
AtCoder - agc006_b (思维)
中间放x-1 x x+1 剩下随便放
思路很巧妙
#include<bits/stdc++.h>
#define REP(i, a, b) for(int i = (a); i < (b); i++)
#define _for(i, a, b) for(int i = (a); i <= (b); i++)
using namespace std;
const int N = 2e5 + 10;
int ans[N];
int main()
{
int n, x;
scanf("%d%d", &n, &x);
if(x == 1 || x == 2 * n - 1)
{
puts("No");
return 0;
}
ans[n - 1] = x - 1;
ans[n] = x;
ans[n + 1] = x + 1;
int now = 0;
_for(i, 1, 2 * n - 1)
{
if(ans[i]) continue;
now++;
if(now == x - 1) now = x + 2;
ans[i] = now;
}
puts("Yes");
_for(i, 1, 2 * n - 1) printf("%d\n", ans[i]);
return 0;
}
周六
hdu 2460(tarjan+lca)
首先tarjan可以得到一颗生成树,割边一定是生成树上的边,因为这道题只考虑割边,所以得到割边后,非生成树上的边就不用考虑了。
每次加边显然会把路径上的割边去掉,直接暴力的话计算一下是1e8,是可以过的,因此直接暴力就行。怎么保存割边呢,用k[u]表示u到u的父亲的这条边是否是割边
#include<cstdio>
#include<vector>
#include<map>
#include<algorithm>
#define rep(i, a, b) for(int i = (a); i < (b); i++)
#define _for(i, a, b) for(int i = (a); i <= (b); i++)
using namespace std;
const int N = 1e5 + 10;
int dfn[N], low[N], k[N], d[N], f[N], cnt, n, m, sum;
vector<int> g[N];
void dfs(int u, int fa)
{
d[u] = d[fa] + 1;
f[u] = fa;
dfn[u] = low[u] = ++cnt;
bool vis = false;
for(int v: g[u])
{
if(!dfn[v])
{
dfs(v, u);
low[u] = min(low[u], low[v]);
if(low[v] > dfn[u]) k[v] = 1, sum++;
}
else
{
if(v == fa && !vis) vis = true;
else low[u] = min(low[u], dfn[v]);
}
}
}
int main()
{
int kase = 0;
while(scanf("%d%d", &n, &m))
{
if(n == 0 && m == 0) break;
printf("Case %d:\n", ++kase);
_for(i, 1, n) g[i].clear(), dfn[i] = low[i] = k[i] = f[i] = 0;
cnt = sum = 0;
while(m--)
{
int u, v;
scanf("%d%d", &u, &v);
g[u].push_back(v);
g[v].push_back(u);
}
dfs(1, 0);
// printf("sum: %d\n", sum);
int q; scanf("%d", &q);
while(q--)
{
int u, v;
scanf("%d%d", &u, &v);
if(d[u] < d[v]) swap(u, v);
while(d[u] > d[v])
{
if(k[u]) k[u] = 0, sum--;
u = f[u];
}
while(u != v)
{
if(k[u]) k[u] = 0, sum--;
u = f[u];
if(k[v]) k[v] = 0, sum--;
v = f[v];
}
printf("%d\n", sum);
}
puts("");
}
return 0;
}
hdu 7158(同余)
可以得到M是pq-1的因子,暴力枚举因子找到满足条件的即
#include<bits/stdc++.h>
#define rep(i, a, b) for(int i = (a); i < (b); i++)
#define _for(i, a, b) for(int i = (a); i <= (b); i++)
using namespace std;
typedef long long ll;
ll p, q, e;
bool check(ll x)
{
if(x <= max(p, q)) return false;
for(int i = 2; 1LL * i * i <= x; i++)
if(x % i == 0)
return false;
return true;
}
int main()
{
int T; scanf("%d", &T);
while(T--)
{
scanf("%lld%lld%lld", &p, &q, &e);
ll t = p * q - 1, m = -1;
for(int i = 1; 1LL * i * i <= t; i++)
if(t % i == 0)
{
if(check(i))
{
m = i;
break;
}
if(check(t / i))
{
m = t / i;
break;
}
}
if(m == -1) puts("shuanQ");
else printf("%lld\n", e * q % m);
}
return 0;
}
周日
hdu5222(拓扑排序+并查集)
我一开始的思路不是正解的思路,是直接dfs,但是不知道为啥mle了,算一下空间是没超的,之前有一道hdu的题也是这样……
正解是先考虑无向边,用并查集,如果没有成环的话,那么接下来就缩点,就是一个联通分量缩成一个点,然后图就是有向图了,那么由向图判断有无环就用拓扑排序即可
#include<cstdio>
#include<vector>
#include<algorithm>
#include<map>
#include<queue>
#define rep(i, a, b) for(int i = (a); i < (b); i++)
#define _for(i, a, b) for(int i = (a); i <= (b); i++)
using namespace std;
#pragma comment(linker, "/STACK:102400000,102400000")
const int N = 1e6 + 10;
int f[N], d[N], n, m1, m2, ans;
vector<int> g[N];
int find(int x) { return x == f[x] ? x : f[x] = find(f[x]); }
int main()
{
int T; scanf("%d", &T);
while(T--)
{
scanf("%d%d%d", &n, &m1, &m2);
_for(i, 1, n) f[i] = i, d[i] = 0, g[i].clear();
ans = 0;
while(m1--)
{
int u, v;
scanf("%d%d", &u, &v);
int fu = find(u), fv = find(v);
if(fu == fv) ans = 1;
else f[fu] = fv;
}
while(m2--)
{
int u, v;
scanf("%d%d", &u, &v);
u = find(u); v = find(v);
g[u].push_back(v);
d[v]++;
}
queue<int> q;
vector<int> topo;
_for(i, 1, n)
if(!d[i])
q.push(i);
while(!q.empty())
{
int u = q.front(); q.pop();
topo.push_back(u);
for(int v: g[u])
if(--d[v] == 0)
q.push(v);
}
if(topo.size() != n) ans = 1;
puts(ans ? "YES" : "NO");
}
return 0;
}
AtCoder - arc134_c(插板法)
首先非1的球随便放,每种是独立的,它放就是n个球放到k个盒子中,盒子不相同,可以有空,这就是典型的插板法了,即C(n + k - 1, k - 1)
放完之后,先在每个盒子中,有多少球就放多少1,然后即剩下的1为n,那么就是将n个球放到k个盒子中,盒子不相同,每个至少有一个,即C(n - 1, k - 1)
无解的话,组合数那里n<m的时候返回0即可
关于组合数的计算,由于C(n, m)的m很小,所以可以直接暴力计算。
#include<bits/stdc++.h>
#define rep(i, a, b) for(int i = (a); i < (b); i++)
#define _for(i, a, b) for(int i = (a); i <= (b); i++)
using namespace std;
typedef long long ll;
const int mod = 998244353;
const int N = 1e5 + 10;
ll f[N], a[N];
int n, k;
ll binpow(ll a, ll b)
{
ll res = 1;
for(; b; b >>= 1)
{
if(b & 1) res = res * a % mod;
a = a * a % mod;
}
return res;
}
ll inv(ll x) { return binpow(x, mod - 2); }
ll C(ll n, ll m)
{
if(n < m) return 0;
ll res = 1;
_for(i, n - m + 1, n) res = res * i % mod;
return res * inv(f[m]) % mod;
}
int main()
{
f[0] = 1;
_for(i, 1, 200) f[i] = f[i - 1] * i % mod;
scanf("%d%d", &n, &k);
_for(i, 1, n) scanf("%lld", &a[i]);
ll ans = 1, sum = 0;
_for(i, 2, n)
{
ans = ans * C(a[i] + k - 1, k - 1) % mod;
sum += a[i];
}
ans = ans * C(a[1] - sum - 1, k - 1) % mod;
printf("%lld\n", ans);
return 0;
}
D. Round Subset(背包)
这题的难点是想到把一个作为体积,一个作为价值跑背包
还是做的题太少
#include<bits/stdc++.h>
#define rep(i, a, b) for(int i = (a); i < (b); i++)
#define _for(i, a, b) for(int i = (a); i <= (b); i++)
using namespace std;
typedef long long ll;
const int N = 200 + 10;
int f[N * 30][N], w[N], v[N], sum, n, K;
int main()
{
scanf("%d%d", &n, &K);
_for(i, 1, n)
{
ll x; scanf("%lld", &x);
while(x % 5 == 0)
{
x /= 5;
w[i]++;
}
while(x % 2 == 0)
{
x /= 2;
v[i]++;
}
sum += w[i];
}
//注意不合法的状态初始化为最小值
memset(f, -0x3f, sizeof f);
f[0][0] = 0;
_for(i, 1, n)
for(int j = sum; j >= w[i]; j--)
for(int k = K; k >= 1; k--)
f[j][k] = max(f[j][k], f[j - w[i]][k - 1] + v[i]);
int ans = 0;
_for(i, 0, sum)
ans = max(ans, min(f[i][K], i));
printf("%d\n", ans);
return 0;
}