2021.8.24 比赛题整理
UPDATE 21/08/26
更改题面、转移链接等。
2021.8.24 新初一暑假测试七
4 / 18,300 / 400。
总结
谁会想到分数第一和第二分别都三个同分的。。我的第二。。。
状态不错,好好做了推了的都 AC 了 (虽然这次比赛题都是板子)。
题目概述
T1:贪心 + 模拟;
T2:快速幂 + 逆元 + 进制模拟(除 T4 外最难的题);
T3:KMP 的 nxt 数组应用;
T4:树形 DP(全军覆没)。
赛时情况
(按提交顺序阐明。)
T1 & T3:一遍 AC;
T2:开始用了模拟栈,RE,后面改成了变量直接加上,AC;
T4:打了暴力,WA + TLE。
T1
题意
给一个长度为 n n n 的序列 a,
一开始你有一个数 A ← 0 A \gets 0 A←0,每次可以从序列中选一个数 b,
令 A ← A + b A \gets A + b A←A+b 或者 A ← A × b A \gets A \times b A←A×b,
每个数都要使用一次,加的次数要和乘的次数相同,要求最大化 A,
输出 A 对 998244353 取模的值。
思路
很简单的贪心 (甚至都不在贪心题型之内),直接排序,
较小的就先加上,较大的再乘上即可。
不要问我证明过程。
AC 代码
感觉码风有点毒瘤啊。。
#include<bits/stdc++.h>
using namespace std;
#define int long long
const int maxn = 500005;
const int mod = 998244353;
int n;
int a[maxn], ans;
int read ()
{
int x = 1, s = 0;
char ch = getchar ();
while (ch < '0' or ch > '9') {if (ch == '-') x = -1; ch = getchar ();}
while (ch >= '0' and ch <= '9') s = s * 10 + ch - '0', ch = getchar ();
return x * s;
}
signed main ()
{
n = read ();
for (register int i (1); i <= n; ++i) a[i] = read ();
sort (a + 1, a + n + 1);
for (register int i (1); i <= (n >> 1); ++i) ans = (ans + a[i]) % mod;
for (register int i ((n >> 1) + 1); i <= n; ++i) ans = (ans % mod * a[i] % mod);
printf ("%lld\n", ans % mod);
return 0;
}
T2
题意
有五个数字,分别是 5、2、1、3、9。可以取任意数字,每个数字可以取无限次。
如:取两个 5,则组合为:55;取 2 与 1,则组合为:21。
现在要问你所有组合中第 C n m m o d ( 1 e 9 + 7 ) C_n^m \bmod (1e9+7) Cnmmod(1e9+7) 个数有多大?
思路
这道题第一难点在于算是第几个数,第二难点在于那个数是什么。
Part 1 求 C n m m o d ( 1 e 9 + 7 ) C_n^m \bmod (1e9+7) Cnmmod(1e9+7)
一旦是需要在除法的基础上进行取模,那么就必须要使用逆元才能得到正确答案。
写了四十分钟的博客:逆元 - 算法博客
《关于我写博客写一半就滚去补算法博客并补了四十分钟的事》
根据博客所说, m ! m o d M O D m! \mod MOD m!modMOD 的逆元就是 k s m ( m ! , M O D − 2 ) ksm(m!,MOD-2) ksm(m!,MOD−2)。
接着的操作就如博客所说了。
代码如下:
n = read (), m = read ();
for (register int i (n); i >= (n - m + 1); --i) cnt = (cnt % mod * i % mod);
for (register int i (m); i >= 2; --i) cnt2 = (cnt2 % mod * i % mod);
ksm (cnt2, mod - 2);
cnt = (cnt % mod * res % mod);
cnt 即最终我们要求的 C n m m o d ( 1 e 9 + 7 ) C_n^m \bmod (1e9+7) Cnmmod(1e9+7)。
Part 2 求第 cnt 个数是什么
根据题意,能组成满足要求的数的只有 5 个数字。
所以!!可以推得这差不多就是一个五进制再加一点改变。
第一次见,所以我也不知道我考试的时候怎么推出来的。。
注:此时我们已经用 1 到 5 分别从小到大代替了题目给的 5 个数字。
那么此时这个操作就约等于用十进制转化为五进制。
因为和五进制有点出入,我们可以出现 5 ,
所以在 while 取余数时,如果可以整除 5,我们将 5 纳入统计,
然后 c n t ← c n t / 5 − 1 cnt \gets cnt / 5 - 1 cnt←cnt/5−1 即可。
啊这就是我考试推出来的,所以结论是由实践推得的。。无证明,谢谢。
代码如下:
int tmp = 1;
while (cnt)
{
if (cnt % 5 == 0)
{
ans += (cho[5] * tmp);
cnt = (cnt / 5 - 1);
tmp *= 10;
}
else
{
ans += (cho[cnt % 5] * tmp);
cnt = ((cnt - (cnt % 5)) / 5);
tmp *= 10;
}
}
AC 代码
#include<bits/stdc++.h>
using namespace std;
#define int long long
const int mod = 1e9 + 7;
int T, n, m;
int cnt, cnt2;
int res;
int ans;
int cho[10];
int read ()
{
int x = 1, s = 0;
char ch = getchar ();
while (ch < '0' or ch > '9') {if (ch == '-') x = -1; ch = getchar ();}
while (ch >= '0' and ch <= '9') s = s * 10 + ch - '0', ch = getchar ();
return x * s;
}
void ksm (int x, int p)
{
while (p >= 1)
{
if (p & 1)
p -= 1, res = res * x % mod;
p /= 2;
x = (x * x % mod);
}
}
signed main ()
{
cho[1] = 1, cho[2] = 2, cho[3] = 3, cho[4] = 5, cho[5] = 9;
T = read ();
while (T--)
{
cnt = cnt2 = res = 1;
ans = 0;
n = read (), m = read ();
for (register int i (n); i >= (n - m + 1); --i) cnt = (cnt % mod * i % mod);
for (register int i (m); i >= 2; --i) cnt2 = (cnt2 % mod * i % mod);
ksm (cnt2, mod - 2);
cnt = (cnt % mod * res % mod);
int tmp = 1;
while (cnt)
{
if (cnt % 5 == 0)
{
ans += (cho[5] * tmp);
cnt = (cnt / 5 - 1);
tmp *= 10;
}
else
{
ans += (cho[cnt % 5] * tmp);
cnt = ((cnt - (cnt % 5)) / 5);
tmp *= 10;
}
}
printf ("%lld\n", ans);
}
return 0;
}
T3
题意
给定若干字符串(这些字符串总长 ≤ 4 × 105 ≤ 4 × 105 ≤4×105),
在每个字符串中求出所有既是前缀又是后缀的子串长度。
例如:ababcababababcabab,
既是前缀又是后缀的:ab,abab,ababcabab,ababcababababcabab。
思路
很明显要用到 KMP 数组中的 nxt 数组。
易得, n x t n nxt_n nxtn、 n x t n x t n nxt_{nxt_n} nxtnxtn、 n x t n x t n x t n nxt_{nxt_{nxt_n}} nxtnxtnxtn 等等都是满足题意的子串。
证明:
长度为 n x t n nxt_n nxtn 前缀的 最长相同前后缀的前缀 同时也是在长度为 n x t n nxt_n nxtn 的后缀的后缀。
AC 代码
#include<bits/stdc++.h>
using namespace std;
const int maxn = 400005;
char a[maxn];
int n, nxt[maxn];
int ans[maxn];
int main ()
{
while (scanf ("%s", a + 1) != EOF)
{
n = strlen (a + 1);
int j = 0;
for (register int i (2); i <= n; ++i)
{
while (j and a[j + 1] != a[i]) j = nxt[j];
if (a[j + 1] == a[i]) j++;
nxt[i] = j;
}
j = nxt[n];
int tot = 0;
while (j)
{
tot++;
ans[tot] = j;
j = nxt[j];
}
for (register int i (tot); i >= 1; --i) printf ("%d ", ans[i]);
printf ("%d\n", n);
}
return 0;
}
T4
题意
给定一棵树,每个节点有一权值,0 或 1。
定义一种操作,即可以更改一个节点的权值,并同时更改其能直接到达的点的权值。
更改时,0 变为 1,1 变为 0。
开始的时候,所有的权值为 0。请编程计算最少要进行多少次操作,才能让所有节点的权值为 1。
思路
树上 dp,据说还是比较裸的树上 dp。
定义四个状态:
0:不按,但是亮的;
1:按了,是亮的;
2:不按,也不亮;
3:按了,但不亮。
我们从根节点开始,遍历每一个它所能到达的点。但注意每一次它都会重新赋值,而不是和之前比较取较小值。
因为我们要满足所有的灯都是亮的,即最初被赋值的最少次数就算小,却不能满足条件。
言归正传,我们定义 f i , j f_{i,j} fi,j 表示当 i 的子树(不含 i)都是亮的时候 i 的状态为 j。
j 即刚刚定义的四个状态。
然后就可以退出这个方程了 (我还是晕的啊,大雾)。
t0 = min (f[x][0] + f[y][0], f[x][2] + f[y][1]);
t1 = min (f[x][1] + f[y][2], f[x][3] + f[y][3]);
t2 = min (f[x][2] + f[y][0], f[x][0] + f[y][1]);
t3 = min (f[x][3] + f[y][2], f[x][1] + f[y][3]);
论树形 dp 的入门还是得看这道题:P1352 没有上司的舞会
AC 代码
(逃
#include<bits/stdc++.h>
using namespace std;
#define int long long
const int inf = 1e9;
const int maxn = 105;
int n;
int cnt, hd[maxn];
struct node{
int to, nxt;
}e[maxn * 2];
int f[maxn][5];
int read ()
{
int x = 1, s = 0;
char ch = getchar ();
while (ch < '0' or ch > '9') {if (ch == '-') x = -1; ch = getchar ();}
while (ch >= '0' and ch <= '9') s = s * 10 + ch - '0', ch = getchar ();
return x * s;
}
void add (int u, int v)
{
e[++cnt].to = v;
e[cnt].nxt = hd[u];
hd[u] = cnt;
}
void treedp (int x, int fa)//0:不按,亮;1:按,亮;2:不按,不亮;3:按,不亮
{
int t0, t1, t2, t3;
f[x][2] = 0, f[x][1] = 1, f[x][0] = f[x][3] = inf;
for (int i = hd[x]; i; i = e[i].nxt)
{
int y = e[i].to;
if (y == fa) continue;
treedp (y, x);
t0 = min (f[x][0] + f[y][0], f[x][2] + f[y][1]);
t1 = min (f[x][1] + f[y][2], f[x][3] + f[y][3]);
t2 = min (f[x][2] + f[y][0], f[x][0] + f[y][1]);
t3 = min (f[x][3] + f[y][2], f[x][1] + f[y][3]);
f[x][0] = min (t0, inf);
f[x][1] = min (t1, inf);
f[x][2] = min (t2, inf);
f[x][3] = min (t3, inf);
}
}
signed main ()
{
n = read ();
while (n)
{
cnt = 0;
memset (hd, 0, sizeof hd);
memset (e, 0, sizeof e);
for (register int i (1); i < n; ++i)
{
int u, v;
u = read (), v = read ();
add (u, v), add (v, u);
}
treedp (1, 0);
printf ("%lld\n", min (f[1][0], f[1][1]));
n = read ();
}
return 0;
}
—— E n d End End——
阳 和 启 蛰 , 枯 木 逢 春 。 阳和启蛰,枯木逢春。 阳和启蛰,枯木逢春。

浙公网安备 33010602011771号