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 A0,每次可以从序列中选一个数 b,

A ← A + b A \gets A + b AA+b 或者 A ← A × b A \gets A \times b AA×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!,MOD2)

接着的操作就如博客所说了。

代码如下:

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 cntcnt/51 即可。

啊这就是我考试推出来的,所以结论是由实践推得的。。无证明,谢谢。

代码如下:

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

既是前缀又是后缀的:abababababcababababcababababcabab

思路

很明显要用到 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——

阳 和 启 蛰 , 枯 木 逢 春 。 阳和启蛰,枯木逢春。

posted @ 2022-03-25 07:25  pldzy  阅读(29)  评论(0)    收藏  举报