前段时间没有写日报,而是写专题了
概率/期望dp专题_Alex Su (*^▽^*)的博客-CSDN博客
博弈论专题_Alex Su (*^▽^*)的博客-CSDN博客
现在觉得写日报才每天有收获感,有动力,于是又开始写日报
这题就是手算,算出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;
- }
这题的期望比较特殊,它是比较独立的,概率不需要连乘
也就是说期望体现在两个相邻时间段之间的贡献。这个时候就不是那种比较经典的期望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;
- }
令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;
- }
这题比较考数学
令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;
- }
和上一题类似,只是平方改成了三次方,要维护的东西多了一些而已
- #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;
- }
这题和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;
- }
看作一个长度为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;
- }
和前面一道题的思路类似
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;
- }
之前做过差不多一样的题,用dp做的。还有一种纯数学的方法
如果现在有k个图案,拿到一个新的图案的概率为(n - k) / n
有一个结论是,概率为p的事件发生所需要的期望次数是1/p
因此此时要n / (n / k)期望次数才能发生
这样枚举k,就可得到期望发生了多少次才得到全部答案
做这种期望题除了用期望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;
- }
考虑一个数的贡献,即当前数在多长的区间中可以作为最小值。这个可以用单调栈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;
- }
关键是发现点的顺序是不影响的,所以直接排序,然后贪心即可
- #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;
- }
中间放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;
- }
首先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;
- }
可以得到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;
- }
我一开始的思路不是正解的思路,是直接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;
- }
首先非1的球随便放,每种是独立的,它放就是n个球放到k个盒子中,盒子不相同,可以有空,这就是典型的插板法了,即C(n + k - 1, k - 1)
放完之后,先在每个盒子中,有多少球就放多少1,然后即剩下的1为n,那么就是将n个球放到k个盒子中,盒子不相同,每个至少有一个,即C(n - 1, k - 1)
无解的话,组合数那里n 关于组合数的计算,由于C(n, m)的m很小,所以可以直接暴力计算。 这题的难点是想到把一个作为体积,一个作为价值跑背包 还是做的题太少D. Round Subset(背包)