AtCoder Beginner Contest 243
ABC 怎么这么难。赛后补的,\(\rm G\) 瞅了题解。\(\rm Ex\) 我知道,这题大毒瘤,就没打算写,是最小割和最小割计数,后者跟计算几何有关系。
给出 \(v,a,b,c\),轮流给 \(v\) 减去 \(a,b,c\),求当 \(v\) 第一次为负数时,减去的数是哪个。(\(1\le v,a,b,c\le 10^5\))
可以直接模拟。不过也可以取余数找到剩下的然后判断。时间复杂度 \(\mathcal{O}(1)\)
。
#include <cstdio>
int main()
{
int v, a, b, c; scanf("%d%d%d%d", &v, &a, &b, &c);
int res = v % (a + b + c);
if (res < a) puts("F");
else
{
res -= a;
if (res < b) puts("M");
else puts("T");
}
return 0;
}
给出两个长为 \(n\) 的互不相同序列 \(A,B\),分别求满足 \(i=j,A_i=B_j\) 和 \(i\ne j,A_i=B_j\) 的有序数对 \((i,j)\) 的数量。(\(1\le n\le 10^3,1\le A_i,B_i\le 10^9\))
前者可以直接判,二者的和可以用 std::map
解决。后者作差即可得到。时间复杂度 \(\mathcal{O}(n\log n)\)。
#include <map>
#include <cstdio>
const int N = 1e3 + 10; std::map<int, int> mp; int a[N];
int main()
{
int n, cnt = 0, tot = 0; scanf("%d", &n);
for (int i = 1; i <= n; ++i) scanf("%d", a + i), mp[a[i]] = 1;
for (int i = 1, x; i <= n; ++i)
scanf("%d", &x), cnt += (x == a[i]), tot += mp[x];
printf("%d\n%d\n", cnt, tot - cnt); return 0;
}
给出 \(n\) 个二维平面上的人,第 \(i\) 个人位于 \((x_i,y_i)\),坐标互不相同,一开始面朝 \(s_i\in\{\mathtt{L,R}\}\),分别表示左和右。求如果所有人按照相同速度一直走下去会不会相撞。(\(2\le n\le 2\times 10^5,0\le x_i,y_i\le 10^9\))
注意到每种 \(y\) 坐标是独立的。所以考虑记录每个 \(y\) 坐标对应的,\(s_i=\tt R\) 的人,\(x_i\) 的最小值。这样,所有 \(s_i=\tt L\) 的人,如果 \(x_i\) 坐标大于 \(y_i\) 坐标对应的最小值,则就会相撞,反之如果小于或 \(y_i\) 坐标上没有 \(\tt R\) 的人,就不会相撞。上述过程可以用 std::map
轻松实现,时间复杂度 \(\mathcal{O}(n\log n)\)。
#include <map>
#include <cstdio>
const int N = 2e5 + 10; int x[N], y[N]; char s[N]; std::map<int, int> mp;
int main()
{
int n; scanf("%d", &n);
for (int i = 1; i <= n; ++i) scanf("%d%d", x + i, y + i);
scanf("%s", s + 1);
for (int i = 1; i <= n; ++i)
if (s[i] == 'R')
{
if (!mp.count(y[i])) mp[y[i]] = x[i];
else mp[y[i]] = std::min(mp[y[i]], x[i]);
}
for (int i = 1; i <= n; ++i)
if (s[i] == 'L')
if (mp.count(y[i]) && mp[y[i]] < x[i]) return puts("Yes"), 0;
puts("No"); return 0;
}
给出一棵结点可看做无穷多的满二叉树,\(i\) 号结点的左右儿子分别为 \(2i,2i+1\)。初始时在结点 \(x\),有长为 \(n\) 的操作序列 \(s\),其中 \(s_i\in\{\tt U,L,R\}\),分别表示向父亲,左儿子,右儿子走。求最终位于哪个结点,保证答案 \(\le 10^{18}\)。(\(1\le n\le 10^6,1\le x\le 10^{18}\))
直接模拟肯定会爆 long long
。但注意到题目的限制,所以我们只需要求出最终有用的操作序列,然后模拟就不会爆 long long
了。具体来讲,维护一个栈,如果 \(s_i\in\{\tt L,R\}\),就把它压到栈里。如果 \(s_i=\tt U\),则如果栈为空,就令 \(x\leftarrow \lfloor\frac{x}{2}\rfloor\),否则弹出栈顶。将最终得到的操作序列施加到最终的 \(x\) 上即可,时间复杂度 \(\mathcal{O}(n)\)。
#include <cstdio>
const int N = 1e6 + 10; typedef long long ll; char s[N]; int c[N], tp;
int main()
{
int n; ll x; scanf("%d%lld%s", &n, &x, s + 1);
for (int i = 1; i <= n; ++i)
if (s[i] == 'U') { if (!tp) x /= 2; else --tp; }
else if (s[i] == 'L') c[++tp] = 0;
else c[++tp] = 1;
if (tp < 0)
for (int i = 1; i <= -tp; ++i) x /= 2;
else
for (int i = 1; i <= tp; ++i)
if (!c[i]) x *= 2; else x = x * 2 + 1;
printf("%lld\n", x); return 0;
}
给出一张 \(n\) 个点 \(m\) 条边的简单连通无向图,一条边能被删去,当且仅当:
- 删去后原图依然连通。
- 删去后,原图任意两点间最短路不变。
求最多删去多少条边。(\(2\le n\le 300,n-1\le m\le \frac{n(n-1)}{2},1\le w\le 10^9\),\(w\) 表示边权)
考虑转化原题目给出的条件。什么样的边能被删去呢?看起来这个问题似乎不太好像,那我们换一边,什么样的边不能被删去呢?这个问题就比较好解决了,首先,一条边 \((x,y,z)\) 不能被删去的必要条件是 \(dis_{x,y}=z\),其中 \(dis\) 表示最短路。首先肯定有 \(dis_{x,y}\le z\),而如果 \(dis_{x,y}<z\),那最短路上一定没有 \((x,y,z)\),删掉啥都不影响。而另一个必要条件是,\(x,y\) 之间,不存在长度大于 \(1\) 的,权值为 \(z\) 的路径。原因比较显然,因为这样删掉后原图就不连通了。可以 证明,这两个条件加起来就是充要条件了。
必要性已经证明完了,考虑充分性。其实也比较显然,因为这俩条件满足后,删掉这条边会对最短路和连通性造成影响。所以我们现在的问题变为判断两点间最短路,和是否存在长度大于 \(1\),权值为 \(z\) 的路径。这些需要的信息都可以用一遍 \(\rm floyd\) 求出,时间复杂度 \(\mathcal{O}(n^3)\)。
#include <cstdio>
#include <cstring>
const int N = 310; typedef long long ll;
ll dis[N][N]; int E[N][N], id[N][N];
int main()
{
int n, m; scanf("%d%d", &n, &m);
memset(dis, 0x3f, sizeof (dis));
for (int i = 1; i <= n; ++i) dis[i][i] = 0;
for (int i = 1, x, y, z; i <= m; ++i)
scanf("%d%d%d", &x, &y, &z), E[x][y] = E[y][x] = z,
id[x][y] = id[y][x] = 1, dis[x][y] = dis[y][x] = z;
for (int k = 1; k <= n; ++k)
for (int i = 1; i <= n; ++i)
for (int j = 1; j <= n; ++j)
if (dis[i][k] + dis[k][j] <= dis[i][j])
dis[i][j] = dis[i][k] + dis[k][j];
int cnt = 0;
for (int i = 1, flg; i <= n; ++i)
for (int j = 1; j <= n; ++j)
{
if (i == j || dis[i][j] != E[i][j]) continue;
flg = 1;
for (int k = 1; k <= n; ++k)
if (k != i && k != j && dis[i][k] + dis[k][j] == E[i][j]) { flg = 0; break; }
cnt += flg;
}
printf("%d\n", m - cnt / 2); return 0;
}
有 \(n\) 种奖品,每种奖品有一个权值 \(w_i\),每次抽奖抽到第 \(i\) 个奖品的概率是:
\[\dfrac{w_i}{\sum\limits_{j=1}^nw_j} \]求 \(K\) 次抽奖,恰好抽到 \(m\) 种奖品的概率。(\(1\le n,m,K\le 50,m\le n\))
考虑描述一个抽奖的状态,注意到我们关心的是种数,所以考虑用一个序列 \(\left<c\right>\) 来描述:
\(c_i\) 表示 \(i\) 种物品出现的次数。则这个状态出现的概率是:
其中 \(p_i=\frac{w_i}{\sum\limits_{j=1}^nw_j}\),意义就是可重集排列数(一共有几种情况)和朴素的概率。
而注意到,这东西乘起来,每个 \(i\) 的贡献是独立的,可以考虑对每种类型分别考虑,做 \(\rm dp\)。
设 \(f_{i,j,k}\) 表示前 \(i\) 种物品,选了 \(j\) 种,共选了 \(k\) 个物品的,所有方案权值和。其中一种方案 \(\left<c\right>\) 对应的权值:
最终答案即为 \(K!f_{n,m,K}\)。转移就是枚举 \(c_i\) 的值:
统计答案即可。时间复杂度 \(\mathcal{O}(nmK^2)\)。
#include <cstdio>
const int N = 60, mod = 998244353; typedef long long ll;
int W[N], p[N], fac[N], ifac[N], f[N][N][N];
inline int ksm(int a, int b)
{
int ret = 1;
while (b)
{
if (b & 1) ret = (ll)ret * a % mod;
a = (ll)a * a % mod; b >>= 1;
}
return ret;
}
int main()
{
int n, m, K, sum = 0; scanf("%d%d%d", &n, &m, &K); fac[0] = 1;
for (int i = 1; i < N; ++i) fac[i] = (ll)fac[i - 1] * i % mod;
ifac[N - 1] = ksm(fac[N - 1], mod - 2);
for (int i = N - 2; i; --i) ifac[i] = (ll)ifac[i + 1] * (i + 1) % mod;
for (int i = 1; i <= n; ++i) scanf("%d", W + i), (sum += W[i]) %= mod;
for (int i = 1; i <= n; ++i) p[i] = (ll)W[i] * ksm(sum, mod - 2) % mod;
f[0][0][0] = 1;
for (int i = 1; i <= n; ++i)
for (int j = 0; j <= m; ++j)
for (int k = 0; k <= K; ++k)
{
(f[i][j][k] += f[i - 1][j][k]) %= mod;
if (!j) continue;
for (int c = 1; c <= k; ++c)
(f[i][j][k] += (ll)f[i - 1][j - 1][k - c] * ifac[c] % mod * ksm(p[i], c) % mod) %= mod;
}
int ans = (ll)fac[K] * f[n][m][K] % mod;
printf("%d\n", ans); return 0;
}
\(t\) 组数据,每组给出一个序列 \(A=(x)\),执行一下操作无穷次:
- 设末尾的数为 \(y\),向末尾加入一个 \([1,\lfloor\sqrt{y}\rfloor]\) 之间的整数。
求最终能得到多少种不同的最终序列。(\(1\le t\le 20,1\le x\le 9\times10^{18}\))
考虑朴素的 \(\rm dp\),设 \(f_x\) 表示以 \(x\) 开头的序列个数,枚举下一次放哪个即可得到朴素转移:
处理出所有 \(\le \sqrt{x}\) 的 \(f\),顺便维护个前缀和,即可在 \(\mathcal{O}(t\sqrt{x})\) 的时间复杂度内求解。
当然,这么做是不能通过的,考虑优化。注意到只要我们知道 \(A_i,A_{i+2}\),其实 \(A_{i+1}\) 的范围我们是能知道的。考虑从 \(A_i\) 直接转移到 \(A_{i+2}\),并算上所有 \(A_{i+1}\) 可能的取值。即如果我们假设 \(A_{i+1}\in[l,r]\),则:
容易发现,如果 \(A_i=x,A_{i+2}=i\),则 \(l=i^2,r=\sqrt{x}\)。这样,我们分别维护 \(i^2f_i,f_i\) 的前缀和,即可做到 \(\mathcal{O}(t\sqrt[4]{x})\) 的时间复杂度。
顺便,提前预处理即可做到 \(\mathcal{O}(\sqrt[4]{x})\)。注意,cmath
的 sqrt()
会掉精度,需要处理一下。
#include <cmath>
#include <cstdio>
#include <climits>
const int N = 6e4 + 10; typedef long long ll;
ll f[N], pref[N], p[N]; typedef long double ld;
inline ll Sqrt(ll x)
{
ll ret = sqrt(x) + 1;
while (ret * ret > x) --ret;
return ret;
}
inline void pre(int n)
{
f[1] = pref[1] = p[1] = 1; ll t;
for (ll i = 2; i <= n; ++i)
{
t = Sqrt(Sqrt(i));
f[i] = pref[t] * ((ll)Sqrt(i) + 1) - p[t];
pref[i] = pref[i - 1] + f[i];
p[i] = p[i - 1] + f[i] * i * i;
}
}
int main()
{
int T; scanf("%d", &T); pre(N - 1);
while (T--)
{
ll x, n; scanf("%lld", &x); n = Sqrt(Sqrt(x));
printf("%lld\n", pref[n] * ((ll)Sqrt(x) + 1) - p[n]);
}
return 0;
}