前言
太惨了太惨了。
比赛入口
A 串
这题我看到题目以为要用容斥原理做,想了好久都没想出来,想到底该如何去减少重复的次数,然后就没了😟
对一个序列进行处理,并且有几种状态,目标求数量那么要尝试去使用DP去做。
DP表示为二维数组,
f
[
i
]
[
j
]
f[i][j]
f[i][j]代表前i个字符串中,状态为j,属性为数量的集合。
- j = 0 j = 0 j=0 代表无u,表示前i - 1个字符串中没有包含u且第i为不取u的状态。
- j = 1 j = 1 j=1 代表有u无s,表示前i - 1个字符串中包含u第i位取除s以外字母的状态 + 前i - 1个字符串中不包含u第i位取u的状态。
- j = 2 j = 2 j=2 代表有us,表示前i - 1个字符串中包含u第i位取s的状态 + 前i - 1个字符串中包含us第i位任意取字母的状态。
虽然不是自己想出来的,但是写出来好有成就感(hhh~),感觉DP还是蛮有意思的。
AC代码
#include<bits/stdc++.h>
using namespace std;
#define _for(i, a, b) for (int i = (a); i < (b); ++i)
#define _rep(i, a, b) for (int i = (a); i <= (b); ++i)
#define For(i, a, b) for (int i = (a); i >= (b); --i)
typedef long long ll;
typedef pair<int, int> pii;
typedef vector<int> vi;
const int N = 1000000 + 10, mod = 1e9 + 7;
ll f[N][3];
int main()
{
#ifdef LOCAL
freopen("data.in", "r", stdin);
#endif
ios::sync_with_stdio(false);
cout.tie(0);
cin.tie(0);
int n;
cin >> n;
f[1][0] = 25;
f[1][1] = 1;
f[1][2] = 0;
ll ans = 0;
_rep(i, 2, n)
{
f[i][0] = f[i - 1][0] * 25 % mod;
f[i][1] = (f[i - 1][1] * 25 + f[i - 1][0])% mod;
f[i][2] = (f[i - 1][1] + f[i - 1][2] * 26) % mod;
ans = (ans + f[i][2]) % mod;
}
cout << ans << endl;
return 0;
}
B 括号
这个题目我是用模拟做出来的。
可以发现
(
(
(
)
)
)
((()))
((())) 获得的括号数是3 + 3 + 3, 把第三个左括号往右移动
(
(
)
(
)
)
(()())
(()())就会减少一个得到3 + 3 + 2。
根据这个规律,我们首先先找到他需要几个括号把k取根号向上取整即可得到有几对括号,然后让中间的括号一直往右移动,直到端点,然后换下一个移动,移动的同时将括号数减少,直到减少到k值,那么就停止移动。
AC代码
#include<bits/stdc++.h>
using namespace std;
#define _for(i, a, b) for (int i = (a); i < (b); ++i)
#define _rep(i, a, b) for (int i = (a); i <= (b); ++i)
#define For(i, a, b) for (int i = (a); i >= (b); --i)
typedef long long ll;
typedef pair<int, int> pii;
typedef vector<int> vi;
const int N = 100000 + 10, INF = 0x3f3f3f3f;
int k, g[N], n;
ll x;
void solve()
{
For(i, n - 1, 0) _for(j, i + 1, 2 * n)
{
x--;
swap(g[j], g[j - 1]);
if (x == k) return;
}
}
int main()
{
#ifdef LOCAL
freopen("data.in", "r", stdin);
#endif
ios::sync_with_stdio(false);
cout.tie(0);
cin.tie(0);
cin >> k;
if (k == 0) {
cout << ")(" << endl;
return 0;
}
n = ceil(sqrt(k));
_for(i, 0, n) g[i] = 1; // 1 代表 左括号, 0代表右括号
x = n * 1LL * n;
if (x != k) solve();
_for(i, 0, 2 * n) {
if (g[i]) cout << '(';
else cout << ')';
}
cout << endl;
return 0;
}
C 红和蓝
我们要抓住一个关键点,就是每个颜色周围只能有一种颜色是相同颜色, 所以说,每对颜色是相间排列的。
设u为树的一个节点,如果它的下面有偶数个节点,那么他下面的节点就可以让每对颜色相间排列。如果后面有奇数个节点,那么让他下面的第一个节点与他颜色相同,其他节点每对颜色成对排列。
这个可能还是有点没说明白,下面将加上详细注释的代码再讲述一遍。
AC代码
#include<bits/stdc++.h>
using namespace std;
#define _for(i, a, b) for (int i = (a); i < (b); ++i)
#define _rep(i, a, b) for (int i = (a); i <= (b); ++i)
#define For(i, a, b) for (int i = (a); i >= (b); --i)
typedef long long ll;
typedef pair<int, int> pii;
typedef vector<int> vi;
const int N = 100000 + 10, mod = 1e9 + 7;
int h[N], e[N], ne[N], idx, n;
int f[N]; //代表以i为根的子树下面有多少个结点。
int color[N]; // 1 代表红色, 0 代表蓝色
bool fail; // 匹配结果
void add(int a, int b) // 拉链法 数组模拟链表
{
e[idx] = b, ne[idx] = h[a], h[a] = idx++;
}
int cal(int u, int fa) // 计算每个结点后面有多少个结点。
{
int sum = 1;
for (int i = h[u]; ~i; i = ne[i])
{
int j = e[i];
if (j != fa) sum += cal(j, u);
}
return f[u] = sum;
}
void dfs(int u, int fa, int c)
{
if (fail) return;
color[u] = c;
int cnt = 0; // u 有几个分支中的节点数是奇数。
for (int i = h[u]; ~i; i = ne[i])
{
int j = e[i];
if (j != fa) cnt += f[j] & 1;
}
if (color[u] == color[fa]) // 它与它的父节点是相同的颜色, 他的节点只能是偶数个,否则无解
{
if (cnt)
{
fail = true;
return;
}
// 数量是偶数个,递归下一层,颜色取和他不一样的。
for (int i = h[u]; ~i; i = ne[i])
{
int j = e[i];
if (j != fa) dfs(j, u, !c);
}
return;
}
// 它与它的父结点颜色不一样,说明他的具有奇数个数的儿子节点和他取一样的颜色。但是只能是一个儿子结点的结点个数为奇数个。
if (cnt != 1) fail = true;
else for (int i = h[u]; ~i; i = ne[i])
{
int j = e[i];
if (j != fa) dfs(j, u, f[j] & 1 ? c : !c);
}
}
int main()
{
#ifdef LOCAL
freopen("data.in", "r", stdin);
#endif
ios::sync_with_stdio(false);
cout.tie(0);
cin.tie(0);
memset(h, -1, sizeof h); // 初始化链表
cin >> n;
_for(i, 0, n - 1)
{ // 建图
int u, v;
cin >> u >> v;
add(u, v);
add(v, u);
}
cal(1, -1);
dfs(1, 0, 1);
if (fail) cout << -1;
else _rep(i, 1, n) if (color[i]) cout << 'R'; else cout << 'B';
cout << endl;
return 0;
}
D 点一成零
题目中的操作是对连通块进行操作,在脑中匹配算法,尝试去往并查集、bfs、dfs方向想。
通过样例我们发现:
- 每一种先将哪一个连通块变化是有序的。
- 操作完之后连通块会保存,延续至下一个操作。
通过第一点我们可以推出式子:
n
!
∗
∏
i
=
0
n
a
i
n! * \prod_{i = 0}^{n}a_i
n!∗∏i=0nai (
a
i
a_i
ai为每一个联通块中的数量,n为连通块个数因为是有序的,所以要乘以阶乘)。
那么就很明显了,每次操作之后使用并查集去维护连通块中的数量。
AC代码
#include<bits/stdc++.h>
using namespace std;
#define _for(i, a, b) for (int i = (a); i < (b); ++i)
#define _rep(i, a, b) for (int i = (a); i <= (b); ++i)
#define For(i, a, b) for (int i = (a); i >= (b); --i)
typedef long long ll;
typedef pair<int, int> pii;
typedef vector<int> vi;
const int N = 500 + 10, mod = 1e9 + 7;
char g[N][N];
int p[N * N], sz[N * N], cnt, n;
bool vis[N][N];
int find(int x)
{
return p[x] == x ? x : p[x] = find(p[x]);
}
void merge(int a, int b)
{
int pa = find(a), pb = find(b);
if (pa != pb)
{
p[pb] = pa;
sz[pa] += sz[pb];
}
}
bool inside(int a, int b)
{
return 0 < a && a <= n && 0 < b && b <= n;
}
int dx[] = { 0, 0, 1, -1 }, dy[] = { 1, -1, 0, 0 };
void bfs(int u, int v)
{
queue<pii> q;
q.push({u, v});
vis[u][v] = true;
while (!q.empty())
{
pii t = q.front();
q.pop();
int x = t.first, y = t.second;
_for (i, 0, 4) _for(j, 0, 4)
{
int a = x + dx[i], b = y + dy[i];
if (inside(a, b) && g[a][b] == '1' && !vis[a][b])
{
merge(u * n + v, a * n + b);
vis[a][b] = true;
q.push({ a, b });
}
}
}
}
int qmi(int a, int b)
{
ll res = 1, t = a;
while (b)
{
if (b & 1) res = res * t % mod;
t = t * t % mod;
b >>= 1;
}
return res;
}
int inverse(int a)
{
return qmi(a, mod - 2);
}
int main()
{
#ifdef LOCAL
freopen("data.in", "r", stdin);
#endif
ios::sync_with_stdio(false);
cout.tie(0);
cin.tie(0);
cin >> n;
_rep(i, 1, n) cin >> g[i] + 1;
_rep(i, 1, (n + 1) * (n + 1)) p[i] = i, sz[i] = 1;
ll ans = 1;
_rep(i, 1, n) _rep(j, 1, n) if (g[i][j] == '1' && !vis[i][j])
bfs(i, j), cnt++, ans = ans * cnt % mod * sz[i * n + j] % mod;
int T;
cin >> T;
while(T--)
{
int x, y;
cin >> x >> y;
++x, ++y;
if (g[x][y] == '1') cout << ans << endl;
else
{
g[x][y] = '1';
ans = ans * (++cnt) % mod;
int pa = find(x * n + y);
_for (i, 0, 4)
{
int a = x + dx[i], b = y + dy[i];
int pb = find(a * n + b);
if (inside(a, b) && g[a][b] == '1' && pa != pb)
{
ans = ans * inverse(cnt--) % mod;
ans = ans * inverse(sz[pb]) % mod;
ans = ans * inverse(sz[pa]) % mod;
merge(pa, pb);
ans = ans * sz[pa] % mod;
}
}
cout << ans << endl;
}
}
return 0;
}
F 对答案一时爽
分母题。
最少肯定是0,因为就两个人,两个人总有没选到的选项。最多的就是一方全对,然后另一方有答案和他相同的数量就是他的分数。
AC代码
#include<bits/stdc++.h>
using namespace std;
#define _for(i, a, b) for (int i = (a); i < (b); ++i)
#define _rep(i, a, b) for (int i = (a); i <= (b); ++i)
#define For(i, a, b) for (int i = (a); i >= (b); --i)
typedef long long ll;
typedef pair<int, int> pii;
typedef vector<int> vi;
const int N = 100 + 10, INF = 0x3f3f3f3f;
char a[N], b[N];
int main()
{
#ifdef LOCAL
freopen("data.in", "r", stdin);
#endif
ios::sync_with_stdio(false);
cout.tie(0);
cin.tie(0);
int n;
cin >> n;
_for(i, 0, n) cin >> a[i];
_for(i, 0, n) cin >> b[i];
int cnt = 0;
_for(i, 0, n) if (a[i] == b[i]) cnt++;
cout << n + cnt << " " << 0 << endl;
return 0;
}
H 幂塔个位数的计算
这个题目是欧拉降幂的模板题,如果知道欧拉降幂这个题目很容易做。
a
b
≡
{
a
b
%
ϕ
(
p
)
g
c
d
(
a
,
b
)
=
1
a
b
g
c
d
(
a
,
b
)
≠
1
,
b
<
ϕ
(
p
)
a
b
%
ϕ
(
p
)
+
ϕ
(
p
)
g
c
d
(
a
,
b
)
≠
1
,
b
≥
ϕ
(
p
)
a^b \equiv \begin{cases} a^{b \% \phi(p)} & gcd(a, b) = 1\\ a ^ b & gcd(a, b) \neq 1, b < \phi(p) \\ a^{b \% \phi(p) + \phi(p)} & gcd(a, b) \neq 1, b \geq \phi(p) \end{cases}
ab≡⎩⎪⎨⎪⎧ab%ϕ(p)abab%ϕ(p)+ϕ(p)gcd(a,b)=1gcd(a,b)=1,b<ϕ(p)gcd(a,b)=1,b≥ϕ(p)
第一个要求a和b互质, 第二三个是广义欧拉降幂,不需要a,b互质,但要根据b的值选择取模。
AC代码
#include<bits/stdc++.h>
using namespace std;
#define _for(i, a, b) for (int i = (a); i < (b); ++i)
#define _rep(i, a, b) for (int i = (a); i <= (b); ++i)
#define For(i, a, b) for (int i = (a); i >= (b); --i)
#define debug(a) cout << #a << " = " << a << endl;
typedef long long ll;
typedef pair<int, int> pii;
typedef vector<int> vi;
const int N = 100000 + 10;
int primes[N], euler[N], cnt;
bool st[N];
void get_euler()
{
euler[1] = 1;
int t;
for (int i = 2; i < N; ++i)
{
if (!st[i])
{
euler[i] = i - 1;
primes[cnt++] = i;
}
for (int j = 0; (t = i * primes[j]) < N; ++j)
{
st[t] = true;
if (i % primes[j] == 0)
{
euler[t] = euler[i] * primes[j];
break;
}
euler[t] = euler[i] * (primes[j] - 1);
}
}
}
ll qmi(ll a, ll b, ll p)
{
ll res = 1;
while (b)
{
if (b & 1)
{
res = res * a;
if (res > p) res = res % p + p;
}
a = a * a;
if (a > p) a = a % p + p;
b >>= 1;
}
return res;
}
ll solve(ll a, ll b, ll p)
{
if (p == 1 || !b) return 1;
return qmi(a, solve(a, b - 1, euler[p]), p);
}
int main()
{
#ifdef LOCAL
freopen("data.in", "r", stdin);
#endif
ios::sync_with_stdio(false);
cout.tie(0);
cin.tie(0);
get_euler();
string x, y;
cin >> x >> y;
ll sz_x = x.size(), sz_y = y.size(), a = 0, b = 0;
for (ll i = max(1ll * 0, sz_x - 2); i < sz_x; ++i) a = a * 10 + (x[i] - '0');
if (sz_y >= 3) b = 100;
else _for(i, 0, sz_y) b = b * 10 + (y[i] - '0');
cout << solve(a, b, 10) % 10 << endl;
return 0;
}
I 限制不互素对的排列
需要注意的是:
- 0 ≤ k ≤ n / 2 0 \leq k \leq n / 2 0≤k≤n/2, 而范围中恰好有 n / 2 个偶数,暗示对偶数位置进行处理。
- 有序的列相邻两个元素互素。
分类讨论:
- 0 ≤ k < n / 2 0 \leq k < n / 2 0≤k<n/2 时,直接构造,先将偶数输出,后面再按顺序输出。
- k = n / 2 k = n / 2 k=n/2时,我们我们发现当存在6和3的时候这个一定有解,不存在即无解。 有解的情况,先把除了6的所有偶数输出来,然后再将6 和 3 输出,剩下的按顺序输出即可。
当范围越来越大的时候,我们可以看出 类似 10 和 5 这样的数也可以做偶数后面的数字输出,但是情况和 6 、3是一样的,所以就统一用 6 、3输出。
AC代码
#include<bits/stdc++.h>
using namespace std;
#define _for(i, a, b) for (int i = (a); i < (b); ++i)
#define _rep(i, a, b) for (int i = (a); i <= (b); ++i)
#define For(i, a, b) for (int i = (a); i >= (b); --i)
typedef long long ll;
typedef pair<int, int> pii;
typedef vector<int> vi;
const int N = 100000 + 10;
bool st[N];
int main()
{
#ifdef LOCAL
freopen("data.in", "r", stdin);
#endif
ios::sync_with_stdio(false);
cout.tie(0);
cin.tie(0);
int n, k;
cin >> n >> k;
if (k == n / 2)
{
if (n < 6) cout << -1 << endl;
else {
st[6] = st[3] = true;
int x = 2;
while (x <= n) {
if (!st[x]) cout << x << " ", st[x] = true;
x += 2;
}
cout << 6 << " " << 3 << " ";
_rep(i, 1, n) if (!st[i]) cout << i << " ";
cout << endl;
}
}
else
{
cout << 2 << " ";
st[2] = true;
int x = 4;
while (k--) cout << x << " ", st[x] = true, x += 2;
_rep(i, 1, n) if (!st[i]) cout << i << " ";
cout << endl;
}
return 0;
}
J 一群小青蛙呱蹦呱蹦呱
题目意思就是将 1 − n 1-n 1−n中的质数幂排除掉之后的所有数(设为数组a)的最大公倍数。我是用将lcm转化为gcd去做,但是超时了,看完大佬的题解之后发现自己多此一举了,直接从定义入手,不要转化什么什么的。
先介绍一个数学知识:
算术基本定理:任何一个大于1的自然数 N,如果N不为质数,那么N可以唯一分解成有限个质数的乘积
N
=
P
1
a
1
∗
P
2
a
2
∗
P
3
a
3
…
…
P
n
a
n
N=P_1^{a_1}*P_2^{a_2}*P_3^{a_3}……P_n^{a_n}
N=P1a1∗P2a2∗P3a3……Pnan,这里
P
1
P_1
P1小于
P
2
P_2
P2小于
P
3
P_3
P3……小于
P
n
P_n
Pn均为质数,其中指数
a
i
a_i
ai是正整数。
根据基本算术定理,可以推出以下式子:
数组a中的每个值会等于:
a
i
=
p
1
k
1
∗
p
2
k
2
∗
p
3
k
3
∗
…
…
∗
p
n
k
n
a_i = p_1^{k_1} * p_2^{k_2} * p_3^{k_3} * ……* p_n^{k_n}
ai=p1k1∗p2k2∗p3k3∗……∗pnkn (
n
>
1
n > 1
n>1)
数组a的最大公约数会等于:
a
n
s
=
p
1
m
1
∗
p
2
m
2
∗
p
3
m
3
∗
…
…
∗
p
i
m
i
ans = p_1^{m_1} * p_2^{m_2} * p_3^{m_3} * ……* p_i^{m_i}
ans=p1m1∗p2m2∗p3m3∗……∗pimi (
m
i
m_i
mi是为该质数的最大幂, 最大公约数定义。)
那么问题就转化为求每个质数的最大次幂的问题,当
p
=
2
p = 2
p=2时,他的最大幂情况为:
a
i
=
2
k
∗
3
≤
n
a_i = 2^k *3 \leq n
ai=2k∗3≤n (因为至少要有两个数,只要把另一个质数控制在最小,另一个质数的幂就会最大。), 所以当
p
>
2
p > 2
p>2时,同理可得:
a
i
=
p
k
∗
2
≤
n
a_i = p^k *2 \leq n
ai=pk∗2≤n。
将k求出来也就是
p
i
m
i
p_i^{m_i}
pimi 求出来了,代入式子结果就出来了。
n最大为
1.6
e
8
1.6e8
1.6e8 所以质数最大不会超过的值是
8
e
7
8e7
8e7,只需求出这个范围内的质数即可。
求质数用的是线性筛法求质数,这个就自己去百度吧,基础模板就不多讲了。
AC代码
#include<bits/stdc++.h>
using namespace std;
#define _for(i, a, b) for (int i = (a); i < (b); ++i)
#define _rep(i, a, b) for (int i = (a); i <= (b); ++i)
#define For(i, a, b) for (int i = (a); i >= (b); --i)
typedef long long ll;
typedef pair<int, int> pii;
typedef vector<int> vi;
const int N = 80000000 + 10, mod = 1e9 + 7;
int primes[N], cnt;
bool st[N];
void get_primes()
{
for (int i = 2; i < N; ++i)
{
if (!st[i]) primes[cnt++] = i;
for (int j = 0; i * primes[j] < N; ++j)
{
st[i * primes[j]] = true;
if (i % primes[j] == 0) break;
}
}
}
int main()
{
#ifdef LOCAL
freopen("data.in", "r", stdin);
#endif
ios::sync_with_stdio(false);
cout.tie(0);
cin.tie(0);
int n;
cin >> n;
if (n < 6)
{
puts("empty");
return 0;
}
get_primes();
ll sum = 1;
while (sum * 6 <= n) sum *= 2;
ll ans = sum;
for (int i = 1; primes[i] * 2 <= n; ++i)
{
int p = primes[i];
sum = 1;
while (sum * p * 2 <= n) sum *= p;
ans = ans * sum % mod;
}
cout << ans << endl;
return 0;
}