比赛链接

A

题解

知识点:模拟。

如题。

代码

#include <bits/stdc++.h>
using namespace std;
using ll = long long; int main() {
std::ios::sync_with_stdio(0), cin.tie(0), cout.tie(0);
int x;
cin >> x;
if (x <= 7) cout << "very easy" << '\n';
else if (x <= 233) cout << "easy" << '\n';
else if (x <= 10032) cout << "medium" << '\n';
else if (x <= 114514) cout << "hard" << '\n';
else if (x <= 1919810) cout << "very hard" << '\n';
else cout << "can not imagine" << '\n';
return 0;
}

B

题解

知识点:因数集合,枚举,二分。

预处理 \([1,2 \times 10^5]\) 所有数的因数,设 \(pos_i\) 为因数 \(i\) 出现的位置,对每个数处理即可。

查询 \(x\) 时,只需在 \(pox_{a_x}\) 内二分查找大于 \(x\) 的位置,然后就可以得到 \(a_x\) 作为因数出现的位置个数。

时间复杂度 \(O((2 \times 10^5) \log ({2 \times 10^5})+(n+q) \sqrt {a_i})\)

空间复杂度 \(O((2 \times 10^5) \log ({2 \times 10^5})+(n+q) \sqrt {a_i})\)

代码

#include <bits/stdc++.h>
using namespace std;
using ll = long long; int a[400007];
vector<int> pos[200007];
vector<int> factor[200007];
void get_factor(int n) {
for (int i = 1;i <= n;i++)
for (int j = i;j <= n;j += i)
factor[j].push_back(i);
} int main() {
std::ios::sync_with_stdio(0), cin.tie(0), cout.tie(0);
get_factor(2e5);
int n, q;
cin >> n >> q;
for (int i = 1;i <= n;i++) cin >> a[i];
for (int i = 1;i <= n;i++) for (auto f : factor[a[i]]) pos[f].push_back(i); while (q--) {
int op, x;
cin >> op >> x;
if (op == 1) {
a[++n] = x;
for (auto f : factor[a[n]]) pos[f].push_back(n);
}
else {
int id = lower_bound(pos[a[x]].begin(), pos[a[x]].end(), x) - pos[a[x]].begin();
cout << pos[a[x]].size() - id - 1 << '\n';
}
}
return 0;
}

C

题解

知识点:贪心。

手动模拟一下发现系数是二项式系数,大的数放中间小的放外边即可。

时间复杂度 \(O(n^2)\)

空间复杂度 \(O(n^2)\)

代码

#include <bits/stdc++.h>
using namespace std;
using ll = long long; const int mod = 1e9 + 7; int a[1007];
int s[1007];
int main() {
std::ios::sync_with_stdio(0), cin.tie(0), cout.tie(0);
int n;
cin >> n;
for (int i = 1;i <= (n + 1) / 2;i++) a[i] = 2 * i - 1;
for (int i = 1;i <= n / 2;i++) a[n - i + 1] = 2 * i; for (int i = 1;i <= n;i++) s[i] = a[i];
for (int i = 1;i <= n - 1;i++) {
for (int j = 1;j <= n - i;j++) {
(s[j] += s[j + 1]) %= mod;
}
}
cout << s[1] << '\n';
for (int i = 1;i <= n;i++) cout << a[i] << " \n"[i == n];
return 0;
}

D

题解

知识点:字符串,线性dp。

为了容易求出去掉一个字母后的子序列个数,考虑分别dp前缀和后缀子序列 t = "udu" 个数。

设 \(f_{i,j}\) 为 \(s[1,i]\) 中子序列 \(t[1,j]\) 的个数, \(g_{i,j}\) 为 \(s[n-i+1,n]\) 中子序列 \(t[3-j+1,3]\) 的个数,特别地 \(j = 0\) 时为空串。转移方程很显然,详见代码。

最后枚举 \(n\) 个位置,第 \(i\) 个位置删去后子序列总数为 \(\sum_{j = 0}^3 (f_{i - 1,j} \cdot g_{n - i,3 - j})\) 取最小值的位置即可。

时间复杂度 \(O(n)\)

空间复杂度 \(O(n)\)

代码

#include <bits/stdc++.h>
using namespace std;
using ll = long long; ll f[200007][4];
ll g[200007][4];
int main() {
std::ios::sync_with_stdio(0), cin.tie(0), cout.tie(0);
string s;
cin >> s;
int n = s.size();
s = "?" + s;
string t = "?udu";
f[0][0] = 1;
for (int i = 1;i <= n;i++) {
for (int j = 0;j <= 3;j++) {
f[i][j] = f[i - 1][j];
if (s[i] == t[j]) f[i][j] += f[i - 1][j - 1];
}
}
reverse(s.begin() + 1, s.end());
reverse(t.begin() + 1, t.end());
g[0][0] = 1;
for (int i = 1;i <= n;i++) {
for (int j = 0;j <= 3;j++) {
g[i][j] = g[i - 1][j];
if (s[i] == t[j]) g[i][j] += g[i - 1][j - 1];
}
}
reverse(s.begin() + 1, s.end());
reverse(t.begin() + 1, t.end());
int pos = -1;
ll mn = 1e18;
for (int i = 1;i <= n;i++) {
ll tmp = 0;
for (int j = 0;j <= 3;j++) tmp += f[i - 1][j] * g[n - i][3 - j];
if (tmp < mn) mn = tmp, pos = i;
}
s[pos] = 'a';
cout << s.substr(1, n) << '\n';
return 0;
}

E

题解

知识点:数论,最小生成树。

根据Kruskal最小生成树,我们贪心选权小的边即可。接下来对每个数 \(i\) 分类讨论:

  1. 当 \(1 + k < i\) 时,我们有 \(\gcd(1,i) = 1\) 权值是最小的。
  2. 当 \(i+k\geq n\) 时, \(i\) 不存在 \(\gcd\) 的边,因此我们选 \(\text{lcm}\) 最小的边 \(\text{lcm}(1,i) = i\) 。
  3. 当 \(i+k<n\) 时,我们枚举数 \(j \in [i+k+1,n]\) ,取 \(\gcd(i,j)\) 的最小值。注意到, \(2\times 10^5\) 内素数间距不会很大,因此不用枚举多少个数即可得到 \(\gcd(i,j) = 1\) ,此时跳出即可。

时间复杂度 \(O(n)\)

空间复杂度 \(O(1)\)

代码

#include <bits/stdc++.h>
using namespace std;
using ll = long long; int main() {
std::ios::sync_with_stdio(0), cin.tie(0), cout.tie(0);
int n, k;
cin >> n >> k;
ll ans = 0;
for (int i = 2;i <= n;i++) {
if (1 + k < i) {
ans++;
continue;
}
int mi = i;
for (int j = i + k + 1;j <= n;j++) {
mi = min(mi, gcd(i, j));
if (mi == 1) break;
}
ans += mi;
}
cout << ans << '\n';
return 0;
}

F

题解

方法一

知识点:枚举,优先队列,离线,贪心。

离线预处理每次操作后的答案,每次对最大的数操作。

当所有数都为 \(1\) 时就不用继续操作了,一个数最多操作 \(4\) 次,因此复杂度是线性的。

时间复杂度 \(O(n \log n + q)\)

空间复杂度 \(O(n)\)

方法二

知识点:枚举,优先队列,离线,贪心,二分。

注意到每次操作后数字必然 \(\leq 31\) ,我们预处理答案为 \([1,31]\) 所需的操作次数,每次操作都对最大的数取。

对于一个询问 \(k\) ,只需要二分找到小于等于 \(k\) 的第一个答案即可。

若 \(k\) 小于答案 \(31\) 的操作次数,说明有数没有被操作到,我们对 \(a_i\) 从大到小排序取第 \(k+1\) 大的数即答案。

时间复杂度 \(O(n\log n + q)\)

空间复杂度 \(O(n)\)

代码

方法一

#include <bits/stdc++.h>
using namespace std;
using ll = long long; int f(int x) { return __builtin_popcount(x); } int ans[200007 * 4];
int main() {
std::ios::sync_with_stdio(0), cin.tie(0), cout.tie(0);
int n, q;
cin >> n >> q;
priority_queue<int> pq;
for (int i = 1, x;i <= n;i++) {
cin >> x;
pq.push(x);
}
int cnt = 0;
while (pq.size() && pq.top() > 1) {
int mx = f(pq.top());
pq.pop();
pq.push(mx);
ans[++cnt] = pq.top();
}
while (q--) {
int k;
cin >> k;
cout << ans[min(cnt, k)] << '\n';
}
return 0;
}

方法二

#include <bits/stdc++.h>
using namespace std;
using ll = long long; int f(int x) { return __builtin_popcount(x); }
int a[200007];
int ans[40]; int main() {
std::ios::sync_with_stdio(0), cin.tie(0), cout.tie(0);
int n, q;
cin >> n >> q;
priority_queue<int> pq;
for (int i = 1, x;i <= n;i++) {
cin >> a[i];
pq.push(a[i]);
}
int cnt = 0;
for (int i = 31;i >= 1;i--) {
while (pq.size() && pq.top() > i) {
int mx = f(pq.top());
pq.pop();
pq.push(mx);
cnt++;
}
ans[i] = cnt;
}
sort(a + 1, a + n + 1, greater<int>());
while (q--) {
int k;
cin >> k;
if (k < ans[31]) cout << a[k + 1] << '\n';
else cout << lower_bound(ans + 1, ans + 31 + 1, k, greater<int>()) - ans << '\n';
}
return 0;
}

G

题解

知识点:贪心,双指针。

显然每次取最小的两个负数,或者取最大的两个正数乘在一起,这样最大。

考虑从小到大排序,用双指针指向最小的两个数和最大的两个数,每次取这两组较大值加入答案。

时间复杂度 \(O(n)\)

空间复杂度 \(O(n)\)

代码

#include <bits/stdc++.h>
using namespace std;
using ll = long long; ll a[200007];
int main() {
std::ios::sync_with_stdio(0), cin.tie(0), cout.tie(0);
int n, k;
cin >> n >> k;
for (int i = 1;i <= n;i++) cin >> a[i];
sort(a + 1, a + n + 1);
int l = 1, r = n;
ll ans = 0;
while (k && l < r) {
ll x = a[l] * a[l + 1];
ll y = a[r - 1] * a[r];
if (x >= y) ans += x, l += 2;
else ans += y, r -= 2;
k--;
}
cout << ans << '\n';
return 0;
}

H

题解

知识点:数学。

取 \([1,x-1]\) 与 \([l,r]\) 的交叉部分算概率即可。

时间复杂度 \(O(1)\)

空间复杂度 \(O(1)\)

代码

#include <bits/stdc++.h>
using namespace std;
using ll = long long; int main() {
std::ios::sync_with_stdio(0), cin.tie(0), cout.tie(0);
int x, l, r;
cin >> x >> l >> r;
double ans = (min(r, max(l - 1, x - 1)) - l + 1.0) / (r - l + 1);
cout << fixed << setprecision(10) << ans << '\n';
return 0;
}

I

题解

知识点:BFS,贪心。

一个结论就是,你走过的路可以立刻销毁,并让下一条你走的路变 \(1\) 。因此,对于一条路径,只需要考虑起点出发的第一条路径能否变成 \(1\) 即可,后续的路都可以通过销毁前面的路变 \(1\) 。

我们先处理出起点到终点需要走多少条路,因此bfs时把边权都当 \(1\) 遍历一遍即可。如果到终点的需要走的路数量小于总边数,那么第一条路可以销毁其他路变 \(1\) ,否则第一条路不能变。

时间复杂度 \(O(n+m)\)

空间复杂度 \(O(n+m)\)

代码

#include <bits/stdc++.h>
using namespace std;
using ll = long long; vector<pair<int, int>> g[200007]; bool vis[200007];
int dis[200007];
queue<int> q;
void bfs(int s) {
q.push(s);
vis[s] = 1;
while (q.size()) {
int u = q.front();
q.pop();
for (auto [v, w] : g[u]) {
if (vis[v]) continue;
vis[v] = 1;
dis[v] = dis[u] + 1;
q.push(v);
}
}
} int main() {
std::ios::sync_with_stdio(0), cin.tie(0), cout.tie(0);
int n, m;
cin >> n >> m;
for (int i = 1;i <= m;i++) {
int u, v, w;
cin >> u >> v >> w;
g[u].push_back({ v,w });
g[v].push_back({ u,w });
}
bfs(1);
if (m > dis[n]) cout << dis[n] << '\n';
else cout << dis[n] - 1 + g[1].front().second << '\n';
return 0;
}

J

题解

知识点:模拟,贪心,离线。

显然我们需要离线存储所有事件,事件分为三类:

  1. 刷题,pro中某人加 \(w\) 。
  2. 更新,rank更新为当前pro。
  3. 查询,查询rank中的某个人。

更新事件分为两类:间隔为 \(T\) 的全部更新,查询后的单人更新。注意到,全部更新并不需要更新所有人的rank,我们只需要在 \((k+1)T\) 时刻,更新 \((kT,(k+1)T]\) 这段时间刷过题,即pro改变的人,的rank即可,如此全部更新事件就变成单人更新了。

时间复杂度 \(O(n \log n)\)

空间复杂度 \(O(n)\)

代码

#include <bits/stdc++.h>
using namespace std;
using ll = long long; struct node {
int when, what, name, w;
friend bool operator<(const node &a, const node &b) {
return a.when == b.when ? a.what < b.what : a.when < b.when;
}
};
int main() {
std::ios::sync_with_stdio(0), cin.tie(0), cout.tie(0);
int n, T, R;
cin >> n >> T >> R;
int idx = 0;
map<string, int> mp;
vector<node> v;
for (int i = 1;i <= n;i++) {
int op, t;
string name;
cin >> op >> t >> name;
if (!mp.count(name)) mp[name] = ++idx;
if (op == 1) {
v.push_back({ t,3,mp[name],i });
v.push_back({ t + R,2,mp[name],0 });
}
else {
int w;
cin >> w;
v.push_back({ t,1,mp[name],w });
v.push_back({ (t + T - 1) / T * T,2,mp[name],0 });
}
}
sort(v.begin(), v.end());
vector<ll> pro(idx + 1), rank(idx + 1);
vector<pair<int, ll>> ans;
for (auto val : v) {
if (val.what == 1) pro[val.name] += val.w;
else if (val.what == 2) rank[val.name] = pro[val.name];
else ans.push_back({ val.w,rank[val.name] });
}
sort(ans.begin(), ans.end());
for (auto val : ans) cout << val.second << '\n';
return 0;
}

K

题解

知识点:博弈论,dfs。

考虑dfs搜索操作,边界条件为:

  1. 若超过了全局最大值 \(2x\) ,则一定平局。
  2. 若上一轮的人走到了 \(0\) 或之前走到过的点,那么这一轮看作胜。

转移方式为:

  1. 若能让下一轮必败,那这一轮必胜。
  2. 否则,若能让下一轮平局,则这一轮平局。
  3. 否则,这一轮必败。

先搜索除以 \(2\) 的分支,因为收敛更快,需要剪枝不然会超时。

用记忆化搜索也可以写,状态为 \((l,x,r)\) 表示 \([l,x]\) 已经访问过且大于 \(x\) 第一个被访问过的点为 \(r\) 。但实际上没必要,因为可以推断同一个状态最多被访问两次,不记忆还好写点。

状态空间为 \(O(x^3)\) ,但实际上根本跑不满。

时间复杂度 \(O(\text{玄学})\)

空间复杂度 \(O(x)\)

代码

#include <bits/stdc++.h>
using namespace std;
using ll = long long; int mx;
bool vis[1007];
int dfs(int x) {
if (x >= mx) return 0;
if (x == 0 || vis[x]) return 1;
vis[x] = 1;
int l = dfs(x / 2);
if (l == -1) return vis[x] = 0, 1;
int r = dfs(x + 1);
if (r == -1) return vis[x] = 0, 1;
vis[x] = 0;
return r ? -1 : 0;
} int main() {
std::ios::sync_with_stdio(0), cin.tie(0), cout.tie(0);
int x;
cin >> x;
mx = x * 2;
int f = dfs(x);
if (f == 1) cout << "ning" << '\n';
else if (f == -1) cout << "red" << '\n';
else cout << "draw" << '\n';
return 0;
}

L

题解

方法一

知识点:容斥原理,排列组合。

容斥原理是最直接的思路。

先有两个结论:

  1. \(A(x_1,y_1)\) 到 \(B(x_2,y_2)\) ,即 \(A \to B\) 的路径总数是 \(\dbinom{x_2-x_1 + y_2 - y_1}{x_2-x_1}\) 。
  2. \(A(x,y)\) 到斜边各点,即 \(A \to fin\) 的路径总数是 \(2^{n-x+1-y}\) ,可以通过二项式系数证明。

我们需要 \((1,1) \to fin\) 且不经过禁用点的路径总数,因此我们可以考虑 \((1,1) \to fin\) 路径总数减去经过禁用点的路径总数。

路径总数很好求 \(2^{n-1}\) 。

对于只经过一个禁用点 \((1,1) \to (x,y) \to fin\) 的路径数,我们可以通过 \((1,1) \to (x,y)\) 路径数乘以 \((x,y) \to fin\) 的路径数,利用结论1和2即可求得,结果是 \(\dbinom{x-1 + y - 1}{x-1} \cdot 2^{n-x+1-y}\) 。

但是,禁用点可能会有路径包含关系。若 \(A(x_1,y_1)\) 和 \(B(x_2,y_2)\) 满足 \(x_1 < x_2\) 且 \(y_1 <y_2\) ,则称 \(A\) 包含 \(B\) 。对于 \(A\) 包含 \(B\) 的情况, \((1,1) \to A \to fin\) 的路径可能也通过了 \(B\) ,因此计算 \((1,1) \to A \text{ 或 } B \to fin\) 的路径总数时,需要先求出 \((1,1) \to A \to fin\) 和 \((1,1) \to B \to fin\) 的路径数,再减去 \((1,1) \to A \to B \to fin\) 的路径数。

以此类推,我们枚举所有 \(2^m\) 个选禁用点通过的合法方案的路径数,根据容斥原理,奇数个点的路径加,偶数个点的路径减,最后就可以得到经过禁用点的路径总数。其中合法方案指,需要存在路径能通过所有选中的禁用点,不能出现一条路径没经过某个点的情况,这要保证任意两点都有包含关系。例如对于 \(A,B,C\),若 \(A\) 包含 \(B,C\) 但 \(B\) 和 \(C\) 没用包含关系,那么我们无法找到一条能同时通过 \(A,B,C\) 的路径,所以这是不合法的。

为了方便,我们对点按照 \(x\) 为第一关键字, \(y\) 为第二关键字从小到大排序。

时间复杂度 \(O(2^mm)\)

空间复杂度 \(O(m)\)

方法二

知识点:拓扑序dp,排列组合。

我们不难发现,我们容斥中 \(2^m\) 次的加加减减,最后得到的实际上是到达每个禁用点实际路径数,再乘以各自到达斜边各点的路径数,最后求和便是路径总数。

同时,我们还能发现,实际上 \(2^m\) 次方案有很多重复计算。例如我们要求出到达 \(v\) 的实际路径数,我们只需要知道所有 \(v\) 的前驱 \(u_i\) 的实际路径数,然后用 \((1,1)\) 到 \(v\) 的全部路径数减去 \((1,1)\) 通过 \(u_i\) 到 \(v\) 的路径数和,即 \(((1,1) \to v) - \sum u_i (u_i \to v)\) ,可以得到 \(v\) 的实际路径数。

注意到,这是一个可以递推的过程,免去了容斥枚举的复杂度。因此,我们可以 \(O(m^2)\) 建一个DAG,设 \(f_u\) 为到 \(u\) 的实际路径数,利用拓扑序dp,计算方法同上即可。最后,对于 \(f_u\) ,那么 \(f_u \cdot 2^{n-u_x + 1-u_y}\) 即经过 \(u\) 的实际路径数。

时间复杂度 \(O(m^2)\)

空间复杂度 \(O(m^2)\)

方法三

知识点:线性dp,排列组合。

更进一步,我们发现建图的过程中其实可以直接dp了,为了方便我们排好序直接线性dp即可。

时间复杂度 \(O(m^2)\)

空间复杂度 \(O(m)\)

代码

方法一

#include <bits/stdc++.h>
using namespace std;
using ll = long long; const int P = 1e9 + 7;
const int N = 2e5 + 7;
namespace CNM {
int qpow(int a, ll k) {
int ans = 1;
while (k) {
if (k & 1) ans = 1LL * ans * a % P;
k >>= 1;
a = 1LL * a * a % P;
}
return ans;
}
int fact[N], invfact[N];
void init(int n) {
fact[0] = 1;
for (int i = 1;i <= n;i++) fact[i] = 1LL * i * fact[i - 1] % P;
invfact[n] = qpow(fact[n], P - 2);
for (int i = n;i >= 1;i--) invfact[i - 1] = 1LL * invfact[i] * i % P;
}
int C(int n, int m) {
if (n == m && m == -1) return 1; //* 隔板法特判
if (n < m || m < 0) return 0;
return 1LL * fact[n] * invfact[n - m] % P * invfact[m] % P;
}
}
using namespace CNM;
using pii = pair<int, int>; int AtoB(pii A, pii B) {
auto [x1, y1] = A;
auto [x2, y2] = B;
return C(x2 + y2 - x1 - y1, x2 - x1);
} int n, m;
pii pos[20];
int f[20]; int main() {
std::ios::sync_with_stdio(0), cin.tie(0), cout.tie(0);
cin >> n >> m;
for (int i = 1;i <= m;i++) cin >> pos[i].first >> pos[i].second;
sort(pos + 1, pos + m + 1);
init(n);
int ans = 0;
for (int i = 0;i < (1 << m);i++) {
pii pre = { 1,1 };
auto &[px, py] = pre;
bool ok = 1, flag = 0;
int mul = 1;
for (int j = 0;j < m;j++) {
if (!(i & (1 << j))) continue;
auto [x, y] = pos[j + 1];
if (px > x || py > y) {
ok = 0;
break;
}
mul = 1LL * mul * AtoB(pre, pos[j + 1]) % P;
pre = pos[j + 1];
flag ^= 1;
}
if (!ok) continue;
if (flag) (ans -= 1LL * mul * qpow(2, n - px + 1 - py) % P - P) %= P;
else (ans += 1LL * mul * qpow(2, n - px + 1 - py) % P) %= P;
}
cout << ans << '\n';
return 0;
}

方法二

#include <bits/stdc++.h>
using namespace std;
using ll = long long; const int P = 1e9 + 7;
const int N = 2e5 + 7;
namespace CNM {
int qpow(int a, ll k) {
int ans = 1;
while (k) {
if (k & 1) ans = 1LL * ans * a % P;
k >>= 1;
a = 1LL * a * a % P;
}
return ans;
}
int fact[N], invfact[N];
void init(int n) {
fact[0] = 1;
for (int i = 1;i <= n;i++) fact[i] = 1LL * i * fact[i - 1] % P;
invfact[n] = qpow(fact[n], P - 2);
for (int i = n;i >= 1;i--) invfact[i - 1] = 1LL * invfact[i] * i % P;
}
int C(int n, int m) {
if (n == m && m == -1) return 1; //* 隔板法特判
if (n < m || m < 0) return 0;
return 1LL * fact[n] * invfact[n - m] % P * invfact[m] % P;
}
}
using namespace CNM;
using pii = pair<int, int>; int n, m;
pii pos[20]; int AtoB(pii A, pii B) {
auto [x1, y1] = A;
auto [x2, y2] = B;
return C(x2 + y2 - x1 - y1, x2 - x1);
} vector<int> g[20];
int f[20]; int deg[20];
queue<int> q;
void toposort() {
for (int i = 1;i <= m;i++) if (!deg[i]) q.push(i);
while (!q.empty()) {
int u = q.front();
q.pop();
(f[u] += AtoB({ 1,1 }, pos[u])) %= P;
for (auto v : g[u]) {
(f[v] -= 1LL * f[u] * AtoB(pos[u], pos[v]) % P - P) %= P;
deg[v]--;
if (!deg[v]) q.push(v);
}
}
} int main() {
std::ios::sync_with_stdio(0), cin.tie(0), cout.tie(0);
cin >> n >> m;
for (int i = 1;i <= m;i++) cin >> pos[i].first >> pos[i].second;
for (int i = 1;i <= m;i++) {
auto [x1, y1] = pos[i];
for (int j = 1;j <= m;j++) {
if (i == j) continue;
auto [x2, y2] = pos[j];
if (x2 <= x1 && y2 <= y1) {
g[j].push_back(i);
deg[i]++;
}
}
}
init(n);
toposort();
ll ans = qpow(2, n - 1);
for (int i = 1;i <= m;i++) {
auto [x, y] = pos[i];
(ans -= 1LL * f[i] * qpow(2, n - x + 1 - y) % P - P) %= P;
}
cout << ans << '\n';
return 0;
}

方法三

#include <bits/stdc++.h>
using namespace std;
using ll = long long; const int P = 1e9 + 7;
const int N = 2e5 + 7;
namespace CNM {
int qpow(int a, ll k) {
int ans = 1;
while (k) {
if (k & 1) ans = 1LL * ans * a % P;
k >>= 1;
a = 1LL * a * a % P;
}
return ans;
}
int fact[N], invfact[N];
void init(int n) {
fact[0] = 1;
for (int i = 1;i <= n;i++) fact[i] = 1LL * i * fact[i - 1] % P;
invfact[n] = qpow(fact[n], P - 2);
for (int i = n;i >= 1;i--) invfact[i - 1] = 1LL * invfact[i] * i % P;
}
int C(int n, int m) {
if (n == m && m == -1) return 1; //* 隔板法特判
if (n < m || m < 0) return 0;
return 1LL * fact[n] * invfact[n - m] % P * invfact[m] % P;
}
}
using namespace CNM;
using pii = pair<int, int>; int AtoB(pii A, pii B) {
auto [x1, y1] = A;
auto [x2, y2] = B;
return C(x2 + y2 - x1 - y1, x2 - x1);
} int n, m;
pii pos[20];
int f[20]; int main() {
std::ios::sync_with_stdio(0), cin.tie(0), cout.tie(0);
cin >> n >> m;
for (int i = 1;i <= m;i++) cin >> pos[i].first >> pos[i].second;
sort(pos + 1, pos + m + 1);
init(n);
int ans = qpow(2, n - 1);
for (int i = 1;i <= m;i++) {
auto [x1, y1] = pos[i];
f[i] = AtoB({ 1,1 }, pos[i]);
for (int j = 1;j < i;j++) {
auto [x2, y2] = pos[j];
if (x2 <= x1 && y2 <= y1) (f[i] -= 1LL * f[j] * AtoB(pos[j], pos[i]) % P - P) %= P;
}
(ans -= 1LL * f[i] * qpow(2, n - x1 + 1 - y1) % P - P) %= P;
}
cout << ans << '\n';
return 0;
}

2023牛客寒假算法基础集训营6 A-L的更多相关文章

  1. 2020牛客寒假算法基础集训营2 J题可以回顾回顾

    2020牛客寒假算法基础集训营2 A.做游戏 这是个签到题. #include <cstdio> #include <cstdlib> #include <cstring ...

  2. 2020牛客寒假算法基础集训营1 J题可以回顾回顾

    2020牛客寒假算法基础集训营1 这套题整体来说还是很简单的. A.honoka和格点三角形 这个题目不是很难,不过要考虑周全,面积是1,那么底边的长度可以是1也可以是2, 注意底边1和2会有重复的, ...

  3. Applese 的毒气炸弹 G 牛客寒假算法基础集训营4(图论+最小生成树)

    链接:https://ac.nowcoder.com/acm/contest/330/G来源:牛客网 Applese 的毒气炸弹 时间限制:C/C++ 2秒,其他语言4秒 空间限制:C/C++ 262 ...

  4. 牛客寒假算法基础集训营3B 处女座的比赛资格(用拓扑排序解决DAG中的最短路)

    链接:https://ac.nowcoder.com/acm/contest/329/B 来源:牛客网 时间限制:C/C++ 1秒,其他语言2秒 空间限制:C/C++ 262144K,其他语言5242 ...

  5. 牛客寒假算法基础集训营4 I题 Applese 的回文串

    链接:https://ac.nowcoder.com/acm/contest/330/I 来源:牛客网 自从 Applese 学会了字符串之后,精通各种字符串算法,比如--判断一个字符串是不是回文串. ...

  6. 牛客寒假算法基础集训营4 I Applese 的回文串

    链接:https://ac.nowcoder.com/acm/contest/330/I来源:牛客网 自从 Applese 学会了字符串之后,精通各种字符串算法,比如……判断一个字符串是不是回文串. ...

  7. 牛客寒假算法基础集训营2 【处女座与复读机】DP最小编辑距离【模板题】

    链接:https://ac.nowcoder.com/acm/contest/327/G来源:牛客网 一天,处女座在牛客算法群里发了一句“我好强啊”,引起无数的复读,可是处女座发现复读之后变成了“处女 ...

  8. 欧拉函数-gcd-快速幂(牛客寒假算法基础集训营1-D-小a与黄金街道)

    题目描述: 链接:https://ac.nowcoder.com/acm/contest/317/D来源:牛客网小a和小b来到了一条布满了黄金的街道上.它们想要带几块黄金回去,然而这里的城管担心他们拿 ...

  9. 牛客寒假算法基础集训营3处女座和小姐姐(三) (数位dp)

    链接:https://ac.nowcoder.com/acm/contest/329/G来源:牛客网 时间限制:C/C++ 1秒,其他语言2秒 空间限制:C/C++ 262144K,其他语言52428 ...

  10. 牛客寒假算法基础集训营4 F Applese 的大奖

    链接:https://ac.nowcoder.com/acm/contest/330/H来源:牛客网 Applese 和它的小伙伴参加了一个促销的抽奖活动,活动的规则如下:有一个随机数生成器,能等概率 ...

随机推荐

  1. C#与Halcon联合编程之用PictureBox控件替代HWindowControl控件

    在使用HALCON和C#联合编程,有时候要使用halcon的HWindowControl控件,但是我发现,HWindowControl的图片显示控件,不能使用GDI+绘制ROI,不知道为什么,反正我测 ...

  2. Go语言核心36讲08

    在上一篇文章,我们一直都在围绕着可重名变量,也就是不同代码块中的重名变量,进行了讨论. 还记得吗?最后我强调,如果可重名变量的类型不同,那么就需要引起我们的特别关注了,它们之间可能会存在"屏 ...

  3. 云原生之旅 - 12)使用 Kaniko 在 Kubernetes上构建 Docker 容器镜像

    前言 前一篇文章[云原生之旅 - 11)基于 Kubernetes 动态伸缩 Jenkins Build Agents]有讲到在 Kubernetes Pod (Jenkins build agent ...

  4. Pycharm2022.1.3安装教程(包含基础使用配置)

    pycharm的下载安装及使用 以我的Pycharm2022.1.3为例 首先去官网下载professtional(专业版)版本 2022.1.3版本Pycharm软件 https://www.jet ...

  5. C温故补缺(十二):预编译器与头文件

    预编译器 预编译器就是之前学的预编译指令的执行者 gcc -E test.c -o test.i 生成预编译文件就是翻译#指令 比如#include<stdio.h>就是把整个stdio. ...

  6. day25 前端

    https://www.dcloud.io/hbuilderx.html 下载HbuilderX,直接解压缩双击打开 html5 <!DOCTYPE html><!-- 文档类型,声 ...

  7. 【PPT】NET Conf China 2022,主题:C#在iNeuOS工业互联网操作系统的开发及应用

    从技术生态发展过程及理念.产品级解决方案理念.产品系统框架及主要功能介绍.产品系统二次开发和应用案例等5个方面进行了主题发言. 从2003到现在,使用.NET技术生态19年左右.  10多年的煤炭.电 ...

  8. 【Hadoop学习】补充:优化、新特性

    一.数据压缩 1.概述 原则:IO密集而不是计算密集的job 压缩算法选择 2.压缩位置选择 通过参数进行配置 3.压缩实例: 数据流的压缩和解压缩 Map输出端采用压缩 Reduce输出端采用压缩 ...

  9. Semaphore信号量源码解析(基于jdk11)

    目录 1.Semaphore信号量源码解析(基于jdk11) 1.1 Semaphore概述 1.2 Semaphore的原理 1.2.1 基本结构(jdk11) 1.2.2 可中断获取信号量 1.2 ...

  10. CMS可视化---ECharts图表

    一.ECharts介绍 ECharts,全称Enterprise Charts,商业级数据图表,一个纯Javascript的图表库,能够流畅的运行在PC以及移动设备上,兼容当前绝大部分浏览器.为我们许 ...