2021牛客寒假算法基础集训营1 题目解析及知识点整理

官方题解

A . 串

考察点:动态规划
这一题用到的是动态规划。对于 d p [ i ] dp[i] dp[i]表示的是成都为 i i i的字符串方案数是多少。现在考虑前一个状态对于后一个状态的影响。

  • 第一种情况 i i i这个长度的字符串里有"us",那第 i + 1 i+1 i+1个位置就随便填,方案数就是 d p [ i ] ∗ 26 dp[i]*26 dp[i]26
  • 第二种情况前 i i i的字符串里只有"s"没有“us”,那第 i i i位置就只填"s",方案数就是, 2 6 i − d p [ i ] − 2 5 i 26^i-dp[i]-25^i 26idp[i]25i,其中 d p [ i ] dp[i] dp[i]表示有"us"的方案数, 2 5 i 25^i 25i表示不填"u"的情况。
    还有一点就是要注意取模。下面是递推代码。
ll d[N];
int main() {
     
	int n;
	cin >> n;
	d[2] = 1ll;
	for (int i = 3; i <= n; i++)
		d[i] = (d[i - 1] * 26 % mod + qpow(26, i - 1, mod) - d[i - 1] - qpow(25, i - 1, mod) + mod) % mod;
	ll ans = 0ll;
	for (int i = 2; i <= n; i++)
		ans = (ans + d[i]) % mod;
	cout << ans << endl;
	return 0;
}

经典中的经典算法:动态规划
kuangbin动态规划入门专题

B . 括号

考察点:构造
简单的构造题,构造方法有很多。

C . 红和蓝

考察点:dfs,图论
这题还是比较有难度的,对于这个题最主要的是找到性质,一共有两种写法。在这里引用一下别人的一份题解。
2021牛客寒假算法基础集训营1 题目解析及知识点整理_第1张图片
上面的讲解都很详细,我在这题不过多赘述。下面是代码。

第一种解法


const int N = 1e5 + 10;
vector<int>G[N];
int ji[N], cnt, col[N];
bool flag = 0;
void dfs(int x, int fa) {
     
	int son = 0;
	for (auto v : G[x]) {
     
		if (v == fa)continue;
		dfs(v, x);                //先遍历子树这样才能保证每个节点被标记
		son++;
	}
	if (son == 0 || ji[x] == 0) {
         //如果这个点没被标记但是他不是叶子说明他和他的子节点不一样只能和父亲节点一样
		if (ji[fa] != 0) {
                
			flag = 1;
			return;
		}
		ji[fa] = ji[x] = ++cnt;
	}
	
}
void dfs2(int x, int fa) {
     
	for (auto v : G[x]) {
     
		if (v == fa)continue;
		if (ji[x] == ji[v])col[v] = col[x];
		else col[v] = col[x] ^ 1;
		dfs2(v, x);
	}
}
int main() {
     
	int n;
	cin >> n;
	for (int i = 1; i < n; i++) {
     
		int u, v;
		cin >> u >> v;
		G[u].push_back(v), G[v].push_back(u);
	}
	dfs(1, 0);
	if (flag || ji[0]) {
           //如果根节点和0被标记了说明根节点与他的子节点都不相同,这是不可能的
		puts("-1");
		return 0;
	}
	col[1] = 0;                  //0/1都一样
	dfs2(1,0);
	for (int i = 1; i <= n; i++) {
     
		if (col[i] == 1)cout << 'R';
		else cout << 'B';
	}
	cout << endl;
	return 0;
}

出题人解法


const int N = 1e5 + 10;
vector<int>G[N];
int sizen[N];
void dfs(int x, int fa) {
         //找每个子树的大小
	sizen[x] = 1;
	for (auto v : G[x]) {
     
		if (v == fa)
			continue;
		dfs(v, x);
		sizen[x] += sizen[v];
	}
}
int ji[N], cnt;
int flag = 0;
void dfs2(int x, int fa) {
     
	if (ji[x] == 0) {
     
		int odd = 0, id = 0;;
		for (auto v : G[x]) {
     
			if (v == fa)continue;
			if (sizen[v] % 2) {
     
				odd++, id = v;
			}
			if (odd > 1) {
     
				flag = 1;
				return;
			}
		}
		if (!odd) {
     
			flag = 1;
			return;
		}
		ji[x] = ji[id] = ++cnt;
	}
	for (auto v : G[x]) {
     
		if (v == fa)continue;
		dfs2(v, x);
	}
}
int clo[N];
void dfs3(int x, int fa) {
     
	for (auto v : G[x]) {
     
		if (v == fa)continue;
		if (ji[x] == ji[v])clo[v] = clo[x];
		else clo[v] = clo[x] ^ 1;
		dfs3(v, x);
	}
}
int main() {
     
	int n;
	cin >> n;
	for (int i = 1; i < n; i++) {
     
		int u, v;
		cin >> u >> v;
		G[u].push_back(v), G[v].push_back(u);
	}
	dfs(1, 0);
	dfs2(1, 0);
	if (flag) {
     
		cout << -1 << endl;
		return 0;
	}
	clo[1] = 1;
	dfs3(1, 0);
	for (int i = 1; i <= n; i++) {
     
		if (clo[i] == 1)cout << 'R';
		else cout << 'B';
	}
	cout << endl;
	return 0;
}

D . 点一成零

考察点:并查集
这题主要考的是并查集维护连通块大小和数量,不要被题目吓到,这题虽然代码量大,但是很好想。
这里提供一个并查集经典例题的讲解
这题主要思路是,我们对于这个二维图,把他们的坐标转化成一维即 ( x , y ) − > x ∗ n + y (x,y)->x*n+y (x,y)>xn+y,把坐标转化为一维以后我们用并查集,来维护每个连通块的大小,还有连通块的数量。初始化我们把每个点当成一个单独的连通块(大小为1)。然后对于没个为1的点,我们遍历他的相邻点,如果也为1,就合并。同时因为每次合并的操作对连通块数量的改变最多为1,答案很好维护。
下面是代码。

ll qpow(ll a, ll b, ll mod) {
      ll res = 1; for (; b > 0; b >>= 1) {
      if (b & 1) res = res * a % mod; a = a * a % mod; } return res; }
inline ll inv(ll a, ll p) {
      return qpow(a, p - 2, p); }   //这里是求逆元的操作
const int N = 250015;
char s[505][505];
int dx[4] = {
      1,-1,0,0 };
int dy[4] = {
      0,0,1,-1 };
int pre[N];
int sizen[N];
int n;
int get_id(int i, int j) {
     
	return i * n + j;
}
int find(int x) {
     
	return x == pre[x] ? x : pre[x] = find(pre[x]);
}
ll ans = 1ll; //记录答案
int cnt = 0;  //记录连通块的数量
void unioun(int x, int y) {
     
	int xx = find(x), yy = find(y);
	if (xx == yy)return;
	ans = ans * inv(cnt, mod) % mod;
	cnt--;
 	ans = ans * inv(sizen[xx], mod) % mod * inv(sizen[yy], mod) % mod;
	ans = ans * (sizen[xx] + sizen[yy]) % mod;
	sizen[xx] += sizen[yy];
	pre[yy] = xx;
}
int main() {
     
#ifdef MPDFDFL
	freopen("D:/input.txt", "r", stdin);
	//freopen("D:/output.txt", "w", stdout);
#endif
	
	cin >> n;
	for (int i = 0; i < n; i++)
		scanf("%s", s[i]);
	for (int i = 0; i <= n * n + 10; i++)
		pre[i] = i;
	for (int i = 0; i < n; i++)
		for (int j = 0; j < n; j++) {
     
			if (s[i][j] == '1')
				ans = (ans * ++cnt) % mod, sizen[get_id(i, j)] = 1;
		}
	
	for (int i = 0; i < n; i++)
		for (int j = 0; j < n; j++) {
     
			if (s[i][j] == '1') {
     
				for (int k = 0; k < 4; k++) {
     
					int sx = dx[k] + i, sy = j + dy[k];
					if (s[sx][sy] == '1')
						unioun(get_id(i, j), get_id(sx, sy));
				}
			}
		}
	int q;
	cin >> q;
	while (q--) {
     
		int x, y;
		cin >> x >> y;
		if (s[x][y] == '1') {
     
			cout << ans << endl;
			continue;
		}
		s[x][y] = '1';
		sizen[get_id(x, y)] = 1;
		cnt++;
		ans = ans * cnt % mod;
		int flag = 0;
		for (int i = 0; i < 4; i++) {
     
			int sx = dx[i] + x, sy = y + dy[i];
			if (s[sx][sy] == '1') {
     
				flag = 1;
				unioun(get_id(sx, sy), get_id(x, y));
			}
		}
		cout << ans << endl;
	}
	return TIME;
}

E . 三棱锥之刻

考察点:计算几何
高中立体几何知识,自己推公式即可。

F . 对答案一时爽

考察点:贪心
签到题。

G . 好玩的数字游戏

考察点:模拟
按照题意模拟即可

H . 幂塔个位数的计算

考察点:欧拉降幂,找规律
这题官方题解里给的是找规律。这里不在多讲,主要讲一下欧拉降幂的解法。

这是一个欧拉降幂的算法讲解
这一题其实是欧拉降幂的一个经典例题的延申。
如果懂了上面哪一题以后这题就非常好理解。很明显这题虽然不是无限的,但是模数是固定的,就是10,所以根据欧拉函数,他最多迭代四层,之后答案就不会在改变了。这里我们控制一下迭代层数,还有每次次迭代处理的模数,这题就解决了。

ll qpow(ll a, ll b, ll mod) {
      ll res = 1; for (; b > 0; b >>= 1) {
      if (b & 1) res = res * a % mod; a = a * a % mod; } return res; }
const int mod = 1e9 + 7;
int phi[15];
int mo(string x,int MOD) {
     
	int len = SZ(x);
	int res = 0;
	for (int i = 0; i < len; i++)
		res = (res*10 + x[i] - '0') % MOD;
	return res;
}
int f(string x, int MOD,int cnt) {
     
	if (MOD == 1)return 0;
	if (cnt == 1)return mo(x, MOD);
	return (qpow(mo(x, MOD) , f(x, phi[MOD], cnt - 1) + phi[MOD], MOD) + MOD) % MOD;
}
int main() {
     
	phi[10] = 4, phi[4] = 2, phi[2] = 1;
	string a, b;
	cin >> a >> b;
	int cnt = 0;
	if (SZ(b) > 1)cnt = 10;
	else cnt = min(10, b[0] - '0');
	cout << (f(a, 10, cnt) % 10 + 10) % 10 << endl;
	return 0;
}

I . 限制不互素对的排列

考察点:构造
这题同样是一个简单的构造,构造方法很多这里不在多讲。

J . 一群小青蛙呱蹦呱蹦呱

考察点:素数筛
这里引用一下别人的一个题解讲的很好。

#include
#include 
using namespace std;
typedef long long ll;
const int mod = 1e9+7;
const int N = 2e8 + 10;
ll Pow(ll x, ll y)
{
     
	ll res = 1;
	while (y)
	{
     
		if (y & 1)
			res = res*x%mod;
		y >>= 1;
		x = x*x%mod;
	}
	return res;
}

bool vis[N]; int p[12000000];
int tot;
void init()
{
     
	for (int i = 2; i < N; i++)
	{
     
		if (!vis[i])
			p[++tot] = i;
		for (int j = 1; j <= tot&&i*p[j] < N; j++)
		{
     
			vis[i*p[j]] = 1;
			if (i%p[j] == 0) break;
		}
	}
}
int main()
{
     
	init();
	int n;
	scanf("%d", &n);
	if (n <= 5)
	{
     
		puts("empty");
		exit(0);
	}
	ll ans = 1;
	int x = n / 3;
	int cnt = 0;
	while (x > 1) x /= 2, cnt++;
	ans = ans*Pow(2, cnt) % mod;
	int pp = sqrt(n);
	for (int i = 2; i <= tot&&p[i]<=n; i++)
	{
     
		if (p[i] > pp)
		{
     
			if (n / p[i] > 1)
				ans = ans*p[i] % mod;
		}
		else
		{
     
			cnt = 0;
			x = n / 2;
			while (x >= p[i]) x /= p[i], cnt++;
			ans = ans*Pow(p[i], cnt) % mod;
		}
	}
	
	printf("%lld\n", ans);
	return 0;
}

你可能感兴趣的:(2021牛客寒假算法基础集训营1 题目解析及知识点整理)