A. Sticker Album

你想要得到\(n\)张贴纸,每包礼物中等概率出现 \([A,B]\)范围内数量的贴纸,求需要买多少包礼物才能至少获得\(n\)张贴纸的期望次数

\(1 \leq n \leq 10^6,0\leq A,B\leq 10^6\)

题解:期望DP

  • 我们考虑从后往前进行\(dp\)

  • 设计状态为\(dp[i]\)代表手上有\(i\)张贴纸时,至少获得\(n\)张贴纸的期望次数

  • 显然初始状态:\(dp[n]=dp[n + 1]...dp[∞] = 0\)

  • 容易得到状态转移方程:

\[ dp[i] = \sum_{j = i + a}^{j = i+b}(dp[j] + 1) \times \frac{1}{b - a + 1} , A \neq 0
\]
  • 但是该题存在\(A = 0\)的情况,即自己转移到自己,这是一个经典的问题,我们不妨通过移项来解决该问题
\[ dp[i] =(dp[i]+1)\times \frac{1}{b - a + 1} + \sum_{j = i + a + 1}^{j = i+b}(dp[j] + 1) \times \frac{1}{b - a + 1}, A = 0 \\
dp[i] = \frac{b - a + 1}{b - a} + \sum_{j = i + a + 1}^{j = i+b}(dp[j] \times \frac{1}{b - a}), A = 0
\]
const int N = 2e6 + 10, M = 4e5 + 10;

int n, a, b;
double dp[N];
// dp[i] 代表手上有 i 张牌时,拿牌到 n 张牌的期望次数
// dp[n-∞] = 0 void solve()
{
cin >> n >> a >> b;
double sum = 0;
if (a == 0)
{
for (int i = n - 1; i >= 0; --i)
{
sum += dp[i + a + 1];
dp[i] += 1.0 / (b - a) * sum + 1.0 * (b - a + 1) / (b - a);
sum -= dp[i + b];
}
}
else
{
for (int i = n - 1; i >= 0; --i)
{
sum += dp[i + a];
dp[i] += 1.0 / (b - a + 1) * sum + 1;
sum -= dp[i + b];
}
}
cout << fixed << setprecision(5) << dp[0] << endl;
}

E. Party Company

题解

  • 考虑离线,由于对于一条链来说年龄单调,考虑将询问的左端点\(L\)挂在距离查询点最远的且年龄不超过\(R\)的节点上
  • 因为单调,所以可以通过树上倍增来实现离线查询
  • 然后在\(dfs\)的过程中回答询问,最朴素的想法是每次将节点的询问下放
  • 实际上我们可以通过权值树状数组记录每个左端点的次数,对于某个节点\(u\)来说,它的答案为树状数组中所有比它小的左端点的个数
int n, q, w[N], fa[N][20], ans[N], c[N];
vector<int> g[N], vec[N]; int lowbit(int x) { return x & -x; }
void add(int x, int val)
{
while (x < N)
{
c[x] += val;
x += lowbit(x);
}
}
int get_sum(int x)
{
int res = 0;
while (x > 0)
{
res += c[x];
x -= lowbit(x);
}
return res;
} void dfs1(int u, int par)
{
if (u == 1)
fa[u][0] = 1;
else
fa[u][0] = par;
for (int i = 1; i <= 18; ++i)
fa[u][i] = fa[fa[u][i - 1]][i - 1];
vector<int> tmp;
for (auto v : g[u])
{
if (v == par)
continue;
tmp.push_back(v);
dfs1(v, u);
}
} void dfs2(int u, int par)
{
for (auto l : vec[u])
add(l, 1);
ans[u] = get_sum(w[u]);
for (auto v : g[u])
{
if (v == par)
continue;
dfs2(v, u);
}
for (auto l : vec[u])
add(l, -1);
} void solve()
{
cin >> n >> q;
for (int i = 1; i <= n; ++i)
{
int u;
cin >> w[i] >> u;
if (i == 1)
continue;
g[u].push_back(i);
g[i].push_back(u);
}
dfs1(1, 0);
for (int i = 1; i <= q; ++i)
{
int u, l, r;
cin >> u >> l >> r;
for (int j = 18; j >= 0; --j)
{
if (w[fa[u][j]] <= r)
u = fa[u][j];
}
vec[u].push_back(l);
}
dfs2(1, 0);
for (int i = 1; i <= n; ++i)
cout << ans[i] << "\n "[i < n];
}

H. SBC's Hangar

给定\(n,k\),给定每个商品的重量\(w[i]\),要求从\(n\)个商品中挑选\(k\)个商品,使得重量范围在\([L,R]\)内的方案数

\(1 \leq n,k \leq 50\)

题解:数位\(DP\)

  • 考虑到\(n \leq 50\)
  • 考虑将所有商品排序后进行状态压缩,状态\(i\)代表\(n\)个商品选择的二进制状态
  • 然后求出第一个大于等于\(L\)的二进制\(l\),最大的小于等于\(R\)的二进制\(r\)
  • 然后现在将问题转化为:在二进制\([l,r]\)的范围内存在多少个二进制中的\(1\)的个数为\(k\)
  • 考虑差分后数位\(dp\)即可
int n, k, a[N], L, R, dp[N][N][2][2], num[N];

int dfs(int pos, int sum, bool lead, bool limit)
{
if (pos == 0)
return (sum == k ? 1 : 0);
if (dp[pos][sum][lead][limit] != -1)
return dp[pos][sum][lead][limit];
int res = 0;
int up = (limit ? num[pos] : 1);
for (int i = 0; i <= up; ++i)
{
if (i == 0 && lead)
res += dfs(pos - 1, sum, true, limit && i == up);
else if (i == 0)
res += dfs(pos - 1, sum, false, limit && i == up);
else
res += dfs(pos - 1, sum + 1, false, limit && i == up);
}
return dp[pos][sum][lead][limit] = res;
} int cal(int x)
{
int len = 0;
while (x)
{
num[++len] = x % 2;
x /= 2;
}
memset(dp, -1, sizeof dp);
return dfs(len, 0, true, true);
} int check(int mid)
{
int res = 0;
for (int i = 0; i < n; ++i)
{
if (mid >> i & 1)
res += a[i];
}
return res;
} void solve()
{
cin >> n >> k;
for (int i = 0; i < n; ++i)
cin >> a[i];
sort(a, a + n);
cin >> L >> R;
int l = 0, r = (1ll << n) - 1;
while (l <= r)
{
int mid = l + r >> 1;
if (check(mid) >= L)
r = mid - 1;
else
l = mid + 1;
}
int x = l;
l = 0, r = (1ll << n) - 1;
while (l <= r)
{
int mid = l + r >> 1;
if (check(mid) <= R)
l = mid + 1;
else
r = mid - 1;
}
int y = r;
cout << cal(y) - cal(x - 1) << endl;
}

I. Interactivity

题解:树形\(DP\)

  • 因为每颗子树中最多只能有一个叶子节点的值未知

  • 所以考虑设计状态\(dp[u][0/1]\)代表以\(u\)为根的子树中,\(0\)代表所有叶子节点的值都已知,\(1\)代表还有\(1\)个叶子节点的值未知

  • 考虑状态转移:

\[ dp[u][1] = \sum_{v_1 \in u的子树} dp[v_1][1] \times \prod_{v_2 \in u \and v_2\neq v_1} dp[v_2][0]\\
dp[u][0] = \prod dp[v][0] + dp[u][1]
\]

对于\(dp[u][0]\)为什么可以从\(dp[u][1]\)转移过来,原因是如果有\(1\)个叶子节点的值位置,我可以通过查询\(u\)节点的值使得所有叶子节点的值已知,所以允许有\(1\)个叶子节点未知

  • \(tips\):在\(dp[u][1]\)转移的过程中,会出现需要在一个序列中刨除一个数的问题,这里直接乘逆元会出错,对于这个经典的问题,我们考虑预处理前后缀即可
int n, dp[N][2];
vector<int> g[N];
// dp[u][0] : 以u为根的子树中叶已知所有叶子节点的值的方案数
// dp[u][1] : 以u为根的子树中仍存在1个叶子节点的值未知的方案数 void dfs(int u)
{
if ((int)g[u].size() == 0)
{
dp[u][0] = dp[u][1] = 1;
return;
}
for (auto v : g[u])
dfs(v);
vector<int> pre((int)g[u].size() + 10, 1), suf((int)g[u].size() + 10, 1);
for (int i = 0; i < (int)g[u].size(); ++i)
{
int v = g[u][i];
pre[i] = (i == 0 ? dp[v][0] : pre[i - 1] * dp[v][0] % mod);
}
for (int i = (int)g[u].size() - 1; i >= 0; --i)
{
int v = g[u][i];
suf[i] = (i == (int)g[u].size() - 1 ? dp[v][0] : suf[i + 1] * dp[v][0] % mod);
}
for (int i = 0; i < (int)g[u].size(); ++i)
{
int v = g[u][i];
if (i == 0)
dp[u][1] = (dp[u][1] + dp[v][1] * suf[i + 1] % mod) % mod;
else if (i == (int)g[u].size() - 1)
dp[u][1] = (dp[u][1] + dp[v][1] * pre[i - 1] % mod) % mod;
else
dp[u][1] = (dp[u][1] + dp[v][1] * pre[i - 1] % mod * suf[i + 1] % mod) % mod;
}
dp[u][0] = (dp[u][0] + dp[u][1]) % mod;
dp[u][0] = (dp[u][0] + suf[0]) % mod;
} void solve()
{
cin >> n;
for (int i = 2, u; i <= n; ++i)
{
cin >> u;
g[u].push_back(i);
}
dfs(1);
cout << dp[1][0] << endl;
}

K. Between Us

给定\(P\)个人和\(F\)对关系,每对关系代表\(u\)和\(v\)是朋友,现在要求将所有的人最多分成两组,使得每个人在同一组中的朋友数量为奇数,询问是否有解

题解:高斯消元解异或方程组

  • 假设分成的组的类别为\(0, 1\)

  • 令\(a[i]\)为\(i\)的组别,即\(a[i]=0/1\)

  • 如果\(u\)的朋友数量为奇数:

  • 如果\(u\)在\(0\)中,那么他会有奇数个朋友也在\(0\)中,偶数个朋友会在\(1\)中

  • 如果\(u\)在\(1\)中,那么他会有奇数个朋友也在\(1\)中,偶数个朋友会在\(0\)中

容易发现:对于\(u\)来说,其所有朋友分组的异或和等于\(a[u]\),即\(a[u] \oplus a[i] \oplus a[j]...a[k] = 0\)

  • 如果\(u\)的朋友数量为偶数:

  • 如果\(u\)在\(0\)中,那么他会有奇数个朋友也在\(0\)中,奇数个朋友会在\(1\)中

  • 如果\(u\)在\(1\)中,那么他会有奇数个朋友也在\(1\)中,奇数个朋友会在\(0\)中

容易发现:对于\(u\)来说,其所有朋友分组的异或和等于\(1\),即$ a[i] \oplus a[j]...a[k] = 1$

  • 列出\(P\)个异或方程组后高斯消元即可
int n, m, deg[N];
vector<int> g[N]; bitset<N> a[N];
int ans[N], Free[N], cnt; // 自由变量
int Gauss(int equ, int var)
{
int row, col, MaxRow;
col = 0;
for (row = 0; row < equ && col < var; row++, col++)
{
MaxRow = row;
for (int i = row + 1; i < equ; i++)
if (abs(a[i][col]) > abs(a[MaxRow][col]))
MaxRow = i;
if (MaxRow != row)
swap(a[row], a[MaxRow]);
if (a[row][col] == 0)
{
row--;
Free[++cnt] = col;
continue;
}
for (int i = row + 1; i < equ; i++)
{
if (a[i][col])
a[i] ^= a[row];
}
}
for (int i = row; i < equ; i++)
if (a[i][col])
return -1; // 无解
if (row < var)
return var - row; // 无穷解
for (int i = var - 1; i >= 0; i--)
{
ans[i] = a[i][var];
for (int j = i + 1; j < var; j++)
if (a[i][j])
ans[i] ^= (a[i][j] && ans[j]);
}
return 0; // 唯一解
} void solve()
{
cin >> n >> m;
for (int i = 1; i <= m; ++i)
{
int u, v;
cin >> u >> v;
--u, --v;
g[u].push_back(v);
g[v].push_back(u);
++deg[u], ++deg[v];
}
// n * (n + 1) 的增广矩阵
for (int i = 0; i < n; ++i)
{
if (!deg[i])
{
cout << "N" << endl;
return;
}
for (auto v : g[i])
a[i].set(v);
if (deg[i] & 1)
a[i].set(i);
else
a[i].set(n);
}
int res = Gauss(n, n);
if (res == -1)
cout << "N" << endl;
else
cout << "Y" << endl;
}

M. Machine Gun

题解:二维偏序

  • 考虑将所有敌人也通过两条射线投影到\(y\)轴上,那么会在\(y\)轴上形成\(n\)段区间\([l_i,r_i]\)

  • 那么对于每个询问,我们也将其投影在\(y\)轴上的区间\([L,R]\)求出,然后就是求与该区间相交的所有区间的敌人编号

  • 我们考虑什么情况是相交的:\(r_i \geq L \and l_i \leq R\)

  • 显然是一个二位偏序问题,考虑将条件转化到二维平面上,横坐标为\(l\),纵坐标为\(r\)

  • 那么每个敌人的区间就是平面上一点,我所要求的就是一个向上无限延伸的矩形中的点的编号

  • 因为我们要的是一个前缀中所有纵坐标大于等于\(L\)的点的编号,所以我们考虑树状数组,对每个位置\(l\)开一个\(vector\),以二元组形式存放该位置上所有点的纵坐标和编号

  • 那么对于一次查询来说,我只需要查询树状数组前\(logR\)个\(vector\),然后在每个\(vector\)里面二分即可

  • 因为保证所有被查询到的敌人不超过\(1e6\),所以二分之后可以暴力将所有符合要求的敌人取出来

int n, q;
vector<int> vec;
array<int, 3> seg[N];
vector<array<int, 2>> c[N]; int lowbit(int x) { return x & -x; }
void add(int x, array<int, 2> val)
{
while (x < N)
{
c[x].push_back(val);
x += lowbit(x);
}
}
vector<int> query(int x, int y)
{
vector<int> res;
while (x > 0)
{
int p = lower_bound(all(c[x]), array<int, 2>{y, 0}) - c[x].begin();
for (int i = p; i < (int)c[x].size(); ++i)
{
if (c[x][i][0] >= y)
res.push_back(c[x][i][1]);
}
x -= lowbit(x);
}
return res;
} int find(int x) { return lower_bound(all(vec), x) - vec.begin() + 1; } void solve()
{
cin >> n >> q;
for (int i = 1; i <= n; ++i)
{
int x, y;
cin >> x >> y;
int l = 2 * y - x, r = 2 * y + x;
seg[i] = {r, l, i};
vec.push_back(l);
}
sort(all(vec));
vec.erase(unique(all(vec)), vec.end());
sort(seg + 1, seg + n + 1);
for (int i = 1; i <= n; ++i)
{
auto [r, l, id] = seg[i];
l = find(l);
add(l, array<int, 2>{r, id});
}
int ans = 0;
while (q--)
{
int a, b;
cin >> a >> b;
int x = (-1 - ((ans + a) % mod)), y = (ans + b) % mod;
int l = 2 * y + x, r = 2 * y - x;
r = upper_bound(all(vec), r) - vec.begin();
vector<int> vt = query(r, l);
sort(all(vt));
ans = 0;
int pow = 1;
for (int i = 0; i < (int)vt.size(); ++i)
{
ans = (ans + vt[i] * pow % mod) % mod;
pow = pow * 5782344 % mod;
}
cout << ans << endl;
}
}

N. Number Multiplication

题解

  • 直接上科技:大数质因子分解
  • 对每个数质因子分解后排序就好了
mt19937_64 rnd(time(0));
ll mx_fac;
// vector<int> fac;
map<int, int> mp; ll gcd(ll a, ll b) { return b ? gcd(b, a % b) : a; } ll qpow(ll a, ll b, ll p)
{ // 快速幂
ll res = 1;
while (b)
{
if (b & 1)
res = (__int128)res * a % p;
a = (__int128)a * a % p;
b >>= 1;
}
return res;
} bool Miller_Rabin(ll p)
{ // 判断素数
if (p < 2)
return 0;
if (p == 2)
return 1;
if (p == 3)
return 1;
ll d = p - 1, r = 0;
while (!(d & 1))
++r, d >>= 1; // 将d处理为奇数
for (ll k = 0; k < 10; ++k)
{
ll a = rnd() % (p - 2) + 2;
ll x = qpow(a, d, p);
if (x == 1 || x == p - 1)
continue;
for (int i = 0; i < r - 1; ++i)
{
x = (__int128)x * x % p;
if (x == p - 1)
break;
}
if (x != p - 1)
return 0;
}
return 1;
} ll Pollard_Rho(ll x)
{
ll s = 0, t = 0;
ll c = (ll)rnd() % (x - 1) + 1;
int step = 0, goal = 1;
ll val = 1;
for (goal = 1;; goal *= 2, s = t, val = 1)
{ // 倍增优化
for (step = 1; step <= goal; ++step)
{
t = ((__int128)t * t + c) % x;
val = (__int128)val * abs(t - s) % x;
if ((step % 127) == 0)
{
ll d = gcd(val, x);
if (d > 1)
return d;
}
}
ll d = gcd(val, x);
if (d > 1)
return d;
}
} void divide_fac(ll x)
{
if (x < 2)
return;
if (Miller_Rabin(x))
{ // 如果x为质数
mx_fac = max(mx_fac, x); // 更新答案
// fac.push_back(x);
mp[x]++;
return;
}
ll p = x;
while (p >= x)
p = Pollard_Rho(x);
// 不用求质因子幂次时使用
while ((x % p) == 0)
x /= p;
divide_fac(x), divide_fac(p); // 继续向下分解x和p
// divide_fac(x / p), divide_fac(p);
} int m, n, k; void solve()
{
cin >> m >> n >> k;
for (int i = 1; i <= n; ++i)
{
int x;
cin >> x;
divide_fac(x);
}
for (auto [x, y] : mp)
cout << x << " ";
cout << endl;
}

2020-2021 ACM-ICPC Brazil Subregional Programming Contest的更多相关文章

  1. (寒假GYM开黑)2018-2019 ACM-ICPC Brazil Subregional Programming Contest

    layout: post title: 2018-2019 ACM-ICPC Brazil Subregional Programming Contest author: "luowenta ...

  2. 2018-2019 ACM-ICPC Brazil Subregional Programming Contest PART (10/13)

    $$2018-2019 ACM-ICPC Brazil Subregional Programming Contest$$ \(A.Slackline\ Adventure\) \(B.Marbles ...

  3. 2019-2020 ACM-ICPC Brazil Subregional Programming Contest (11/13)

    \(2019-2020\ ACM-ICPC\ Brazil\ Subregional\ Programming\ Contest\) \(A.Artwork\) 并查集,把检测区域能在一起的检测器放在 ...

  4. 2018-2019 ACM-ICPC Brazil Subregional Programming Contest

    A:留坑 B:二维sg函数,特判边界情况 //#pragma GCC optimize(2) //#pragma GCC optimize(3) //#pragma GCC optimize(4) / ...

  5. Gym.101908 Brazil Subregional Programming Contest(寒假自训第六场)

    这几天睡眠时间都不太够,室友晚上太会折腾了,感觉有点累,所以昨天的题解也没写,看晚上能不能补起来. B . Marbles 题意:给定N组数(xi,yi),玩家轮流操作,每次玩家可以选择其中一组对其操 ...

  6. 2013-2014 ACM-ICPC Brazil Subregional Programming Contest 题解

    [题目链接] 这场比赛题面英文都好长... ... A - Zero or One 模拟. #include <bits/stdc++.h> using namespace std; in ...

  7. ACM ICPC, JUST Collegiate Programming Contest (2018) Solution

    A:Zero Array 题意:两种操作, 1 p v  将第p个位置的值改成v  2  查询最少的操作数使得所有数都变为0  操作为可以从原序列中选一个非0的数使得所有非0的数减去它,并且所有数不能 ...

  8. ACM ICPC, Amman Collegiate Programming Contest (2018) Solution

    Solution A:Careful Thief 题意:给出n个区间,每个区间的每个位置的权值都是v,然后找长度为k的区间,使得这个区间的所有位置的权值加起来最大,输出最大权值, 所有区间不重叠 思路 ...

  9. Codeforces Gym101473 A.Zero or One (2013-2014 ACM-ICPC Brazil Subregional Programming Contest)

    代码: #include<iostream> #include<cstring> #include<cstdio> #include<cmath> #i ...

  10. Codeforces Gym101473 F.Triangles-前缀和 (2013-2014 ACM-ICPC Brazil Subregional Programming Contest)

    前缀和. 代码: 1 #include<iostream> 2 #include<cstring> 3 #include<cstdio> 4 #include< ...

随机推荐

  1. Go 编程-mysql数据库操作

    一.环境准备 在Go语言中连接MySQL数据库通常使用database/sql包配合一个MySQL驱动,比如github.com/go-sql-driver/mysql 安装github.com/go ...

  2. DOM – Dimension & Coordinate (offset, client, computed, rect)

    前言 很多年前有记入过一篇 box-sizing 和 dom width. 想想也挺可悲的, 那年我是负责后端的, 却要帮着前端去学习这些知识来解决问题... 也好, 现在 full stack, 也 ...

  3. JavaScript – ECMAScript 版本

    参考 TC39.ECMA-262.ECMAScript 的一些事儿 简史 es3 千禧年后稳定版 es4 革命版本, 最后在 2008 年难产了 es5 2009 年推出, 拿了 es4 一些比较简单 ...

  4. ASP.NET Core – Filter

    介绍 Filter 类似 Middleware,只是它集中在处理 request 的前后, 站 MVC 角度看就是 before 和 after action, 站 Razor Pages 角度就是 ...

  5. 十七,Spring Boot 整合 MyBatis 的详细步骤(两种方式)

    十七,Spring Boot 整合 MyBatis 的详细步骤(两种方式) @ 目录 十七,Spring Boot 整合 MyBatis 的详细步骤(两种方式) 1. Spring Boot 配置 M ...

  6. MySQL 用户、权限管理,C/C++连接与使用

    目录 用户 用户管理 查询所有用户 查看当前用户 查看当前连接数 创建用户 删除用户 修改密码规则 查看规则/策略 规则说明 临时设置 持久设置 修改密码 权限 数据库提供的 权限列表 查看权限 给用 ...

  7. Git常用命令,分支合并与管理

    Git 自我感觉最大的优点是对版本库和分支的管理 各自的电脑都对项目保存一份版本,分支合并也极其方便. 而且Git的目录操作命令跟Linux一样,使用起来还是非常顺手的. Git详解 Git 常用命令 ...

  8. MySQL 切换 Oracle 问题整理

    MySQL 通常小写,Oracle 默认大写 ,查询过程中需加双引号,或者直接将MySQL 字段转换成大写 Springboot 配置 oracle连接 spring: datasource: url ...

  9. Python 潮流周刊#71:PyPI 应该摆脱掉它的赞助依赖(摘要)

    本周刊由 Python猫 出品,精心筛选国内外的 250+ 信息源,为你挑选最值得分享的文章.教程.开源项目.软件工具.播客和视频.热门话题等内容.愿景:帮助所有读者精进 Python 技术,并增长职 ...

  10. 29. GIL全局解释器锁、信号量、线程池进程池

    1. GIL全局解释器锁 1.1 概念 '''In CPython, the global interpreter lock, or GIL, is a mutex that prevents mul ...