D - Moves on Binary Tree
https://atcoder.jp/contests/abc243/tasks/abc243_d
题意: 有一颗以1为根的二叉树,节点个数无穷大,按照字符串S对节点x进行操作,跳到当前的父节点(U),跳到左儿子(L),跳到右儿子®。
题解: 因为在过程中的数据可能会很大很大,不能直接模拟。我们能够发现一次向儿子跳和一次向父亲跳相当于没有进行操作,然后就可以用栈进行模拟啦~
代码:
#include <bits/stdc++.h>
using namespace std;
#define int long long
#define yes "Yes\n"
#define no "No\n"
const int N = 2e5 + 10;
const int mod = 1e9 + 7;
#define pii pair<int, int>
typedef long double db;
int n, m, a[N], x;
void solve() {
cin >> n >> x;
stack<char> st;
string s;
cin >> s;
for (int i = 0; i < n; i++) {
if (s[i] == 'U') {
if (!st.empty() && st.top() != 'U') // 向儿子走和上一个向父亲走抵消掉了
st.pop();
else
st.push(s[i]);
} else {
st.push(s[i]);
}
}
vector<char> v;
while (st.size()) {
v.push_back(st.top());
st.pop();
}
reverse(v.begin(), v.end()); // 翻转下
for (auto it : v) { // 答案保证在1e18内,然后就可以直接模拟了
if (it == 'U')
x /= 2;
else if (it == 'L')
x *= 2;
else
x = x * 2 + 1;
}
cout << x;
}
signed main() {
ios::sync_with_stdio(false), cin.tie(nullptr), cout.tie(nullptr);
int T = 1;
// cin >> T;
for (int i = 1; i <= T; i++) {
solve();
}
}
也可以用栈模拟二进制,碰到U时相当于右移,将栈顶弹出;碰到L时在栈顶加入0;碰到R在栈顶加入1.
E - Edge Deletion
https://atcoder.jp/contests/abc243/tasks/abc243_e
题意: 有一个n个点m条边的无向连通图,在保证联通并且任意两点的最短路距离不变的情况下,最多能够删掉多少条边。
N
<
=
300
N<=300
N<=300
题解: 看到这个N的数据范围,又是多源最短路径相关的问题,大概都能想到
f
l
o
y
d
floyd
floyd吧https://zhuanlan.zhihu.com/p/405188391 floyd参考文章
我们先通过
f
l
o
y
d
floyd
floyd求出任意两点间的最短路,然后再判断每条边能否删掉:
易知当一条边的边长比两点的最短路还要长肯定是能删掉的,因为它对最短路不会产生任何贡献。
当边长等于最短路径长度的时候,就需要判断这条边是不是这两个点唯一的最短路径,我们可以枚举中转点是否存在通过中转点得到最短路径
代码:
struct node {
int x, y, z;
};
vector<node> ed;
int dis[N][N];
void solve() {
cin >> n >> m;
memset(dis, 0x3f, sizeof dis); // 初始化
for (int i = 1; i <= n; i++)
dis[i][i] = 0;
for (int i = 1; i <= m; i++) {
int x, y, z;
cin >> x >> y >> z;
dis[x][y] = dis[y][x] = z;
ed.push_back({x, y, z}); // 存边
}
for (int k = 1; k <= n; k++) {// flody(一定要先枚举中转点,因为floyd本质是dp)
for (int i = 1; i <= n; i++) {
for (int j = 1; j <= n; j++) {
dis[i][j] = min(dis[i][j], dis[i][k] + dis[k][j]);
}
}
}
int ans = 0;
for (auto it : ed) { // 枚举边
int x = it.x, y = it.y, z = it.z;
if (dis[x][y] < z) { // z比x-y的最短路径长
ans++;
} else if (dis[x][y] == z) {
for (int i = 1; i <= n; i++) { // 枚举中转点
if (i != x && i != y && dis[x][i] + dis[i][y] == z) { // 存在除x-y这条边的最短路径
ans++;
break;
}
}
}
}
cout << ans << endl;
}
F - Lottery
https://atcoder.jp/contests/abc243/tasks/abc243_f
题意: 每画一幅画能够得到一种奖品,得到第i种奖品的概率是
W
i
∑
j
=
1
N
W
j
\frac{W_{i}}{\sum_{j=1}^{N} W_{j}}
∑j=1NWjWi,画K幅画得到准确的m种奖的概率是多少。
1
<
=
N
,
M
,
K
<
=
50
1<=N,M,K<=50
1<=N,M,K<=50
题解
概率dp+组合数学
假设K次选到的奖依次是的概率
a
[
1
]
,
a
[
2
]
,
.
.
.
.
.
.
a
[
k
]
a[1],a[2],......a[k]
a[1],a[2],......a[k],那它的概率是
∏
i
=
1
K
a
[
i
]
\prod_{i=1}^{K}a[i]
∏i=1Ka[i]
那假设选的M种概率分别为
b
[
1
]
,
b
[
2
]
,
.
.
.
,
b
[
M
]
b[1],b[2],...,b[M]
b[1],b[2],...,b[M],选的个数依次为
c
[
1
]
,
c
[
2
]
,
.
.
.
,
c
[
M
]
c[1],c[2],...,c[M]
c[1],c[2],...,c[M]
它的概率为
K
!
∏
i
=
1
M
(
c
[
i
]
!
)
∏
i
=
1
M
b
[
i
]
c
[
i
]
\frac{K!}{\prod_{i=1}^{M} (c[i]!)} \prod_{i=1}^{M}b[i]^{c[i]}
∏i=1M(c[i]!)K!∏i=1Mb[i]c[i],也即
K
!
∏
i
=
1
M
b
[
i
]
c
[
i
]
c
[
i
]
!
K!\prod_{i=1}^{M}\frac{b[i]^{c[i]}}{c[i]!}
K!∏i=1Mc[i]!b[i]c[i]
定义
d
p
[
i
]
[
j
]
[
l
]
:
dp[i][j][l]:
dp[i][j][l]: 前i个,有j种不同的奖,总共化了l张的概率
转移方程:
通过枚举r(第i种选的个数)来进行转移
如果取了第i种:
d
p
[
i
]
[
j
]
[
l
]
+
=
d
p
[
i
−
1
]
[
j
−
1
]
[
l
−
r
]
∗
a
[
i
]
r
r
!
dp[i][j][l] += dp[i - 1][j - 1][l - r] * \frac{a[i]^r} {r!}
dp[i][j][l]+=dp[i−1][j−1][l−r]∗r!a[i]r
如果没取第i种:
d
p
[
i
]
[
j
]
[
l
]
+
=
d
p
[
i
−
1
]
[
j
]
[
l
−
r
]
∗
a
[
i
]
r
r
!
dp[i][j][l] += dp[i - 1][j][l - r] * \frac{a[i]^r} {r!}
dp[i][j][l]+=dp[i−1][j][l−r]∗r!a[i]r
代码:
int n, m, a[N], k, dp[N][N][N]; // 前i个,有j个不同,总共拿了l个
int qpow(int a, int n) {
int ans = 1;
while (n) {
if (n & 1) {
ans = ans * a % mod;
}
a = a * a % mod;
n >>= 1;
}
return ans;
}
int fac[N], ifac[N];
void add(int& x) { // 取模
x = (x % mod + mod) % mod;
}
void init() {
fac[0] = ifac[0] = 1;
for (int i = 1; i < N; ++i)
fac[i] = fac[i - 1] * i % mod; // 阶乘
ifac[N - 1] = qpow(fac[N - 1], mod - 2);
for (int i = N - 2; i; --i) // 阶乘的逆元
ifac[i] = ifac[i + 1] * (i + 1) % mod;
}
void solve() {
cin >> n >> m >> k;
int sum = 0;
for (int i = 1; i <= n; i++) {
cin >> a[i];
sum += a[i];
}
int inv = qpow(sum, mod - 2);
for (int i = 1; i <= n; i++)
a[i] = a[i] * inv % mod; // 拿到第i种的概率
dp[0][0][0] = 1;
for (int i = 1; i <= n; i++) {
for (int j = 0; j <= m; j++) {
for (int l = 0; l <= k; l++) {
for (int r = 0; r <= l; r++) { // 第i种拿多少个
if (j == 0 && r > 0) { // 不能取i,不合法
continue;
}
if (r >= 1)
dp[i][j][l] += dp[i - 1][j - 1][l - r] * qpow(a[i], r) % mod * ifac[r] % mod;
else
dp[i][j][l] += dp[i - 1][j][l - r] * qpow(a[i], r) % mod * ifac[r] % mod;
add(dp[i][j][l]);
}
}
}
}
cout << dp[n][m][k] * fac[k] % mod;
}
G - Sqrt
https://atcoder.jp/contests/abc243/tasks/abc243_g
题意:
有一个序列A,初始只有X一个数,每次可以将A末尾的数(记为Y)
[
1
,
Y
]
[1,\sqrt{Y}]
[1,Y]中任意一个数加到A的末尾,问最后的不同序列的种类是多少
题解:
很显然,当序列末尾的数为1之后后面都是1了,也就序列之后的不会再不同了。
先考虑一个数x能产生的不同序列种类(记为f[x]),易知
f
[
x
]
=
∑
i
=
1
x
f
[
i
]
f[x]=\sum_{i=1}^{\sqrt{x}}f[i]
f[x]=∑i=1xf[i]
问题就转化成了求
∑
i
=
1
x
f
[
x
]
\sum_{i=1}^{\sqrt{x}}f[x]
∑i=1xf[x],暴力求解显然是根号级别,是不够的。
通过计算前几个数的
f
[
i
]
f[i]
f[i],我们能够发现一些连续的数的f是相等的,因为根号是向下取整,一个范围内的根号是相等的,f的值自然也是相等的。那么我们就可以将
X
\sqrt{X}
X分成一个个
[
i
∗
i
,
(
i
+
1
)
∗
(
i
+
1
)
−
1
]
[i*i,(i+1)*(i+1)-1]
[i∗i,(i+1)∗(i+1)−1]的区间,复杂度就变成了
O
(
X
1
4
)
O(X^{\frac{1}{4}})
O(X41)
代码:
#include <bits/stdc++.h>
using namespace std;
#define int unsigned long long
#define yes "Yes\n"
#define no "No\n"
const int N = 2e5 + 10;
const int mod = 1e9 + 7;
#define pii pair<int, int>
typedef long double db;
int n, m, a[N], f[N], g[N];
// f[i]:数i产生的不同序列个数,g[i]:f的前缀和[1,i]产生的不同序列的个数
void solve() {
db n;//因为题目范围是9e18,所以用long double了
cin >> n;
int t = (int)sqrt(n), ans = 0;
for (int i = 1; i * i <= t; i++) {
ans += (min(t, (i + 1) * (i + 1) - 1) - i * i + 1) * g[i];
//[i*i,(i+1)*(i+1)-1]区间内的根号值都相同且等于i,产生的不同序列也就是g[i]
}
cout << ans << "\n";
}
signed main() {
ios::sync_with_stdio(false), cin.tie(nullptr), cout.tie(nullptr);
f[1] = 1;
g[1] = 1;
for (int i = 1; i < N; i++) {
int t = sqrt(i);
g[t] = g[t - 1] + f[t]; // 更新前缀和,为后面f的计算
f[i] = g[t]; // f[i]为[1,sqrt(i)]的f的和,也即g[sqrt(i)]
}
g[1] = 1;
for (int i = 2; i < N; i++) { // 计算前缀和,有些前缀并没有计算完
g[i] = g[i - 1] + f[i];
}
int T = 1;
cin >> T;
for (int i = 1; i <= T; i++) {
solve();
}
}