这次练习赛难度有点裂开。。
A、牛妹的游戏
解题思路:其实题意比较简单,我们假设给出的点,没有被蓝色方占领的边全部被绿色方战领。这样我们就可以得出一个结论,只有三条边成一个环,才可能被控制,否则四个条边的话,如果中间存在自己家的边,就会形成三边环,否则就不构成控制区域。
现在题目就转变成求图或者补图中是否存在三边的环。
我们先得到一个结论,当整个地图上 的时候一定会形成控制区域。我通过题解给出简单的理由,假设 存在 六个点,那么对于每一个点,都一定至少存在三条边被蓝色方或者绿色方控制,蓝色方控制2条,剩余3条被绿色方控制,蓝色方控制3条,就显然存在三边给一方控制。我们假设为 给蓝色方控制,那么只要 其中一条给蓝色方控制就可以形成控制区域,否则绿色方控制了这三条边,也会形成控制区域,听说这叫拉姆塞结论)不过我太菜。。比赛的时候没想到。
那么就有 的情况,这个时候直接三重循环爆搜就行了,枚举第一个点,第二个点,第三个点,如果这些点属于蓝色方阵营,或者都不属于蓝色方阵营,都会形成控制区域。
https://ac.nowcoder.com/acm/contest/view-submission?submissionId=43563661
#include <bits/stdc++.h> using namespace std; #define js ios::sync_with_stdio(false);cin.tie(0); cout.tie(0) typedef long long ll; inline int read() { int s = 0, w = 1; char ch = getchar(); while (ch < 48 || ch > 57) { if (ch == '-') w = -1; ch = getchar(); } while (ch >= 48 && ch <= 57) s = (s << 1) + (s << 3) + (ch ^ 48), ch = getchar(); return s * w; } const int N = 10; int vis[N][N]; int main() { int T = read(); while (T--) { bool flag = false; memset(vis, 0, sizeof(vis)); int n = read(), m = read(); if (n > 5) { //大于5个节点时根据拉姆塞结论必有三点构成环 while (m--) n = read(), n = read(); puts("yes"); continue; } for (int i = 1; i <= m; ++i) { int u = read(), v = read(); vis[u][v] = vis[v][u] = 1; } for (int i = 1; i <= n; ++i) for (int j = 1; j <= n; ++j) { if (i == j) continue; for (int k = 1; k <= n; ++k) { if (i == k || j == k) continue; if (vis[i][j] && vis[i][k] && vis[j][k]) flag = true; // 蓝方三点成环 else if (!vis[i][j] && !vis[i][k] && !vis[j][k]) flag = true; //绿方三点成环 } } if (!flag) puts("no"); else puts("yes"); } return 0; }
B、病毒扩散
宝哥天下第一!。好的开始补题。
乍一眼看这个题目还挺熟悉,这不是和上次周练那个只能往下走和只能玩右走从最左上角走到最右下角有点像么!!震惊,当我快速的敲完这个 之后,一测样例1,哇!!过了,直接上交)别问为什么没测样例二,因为没看见。。然后直接不出所料的WA了一发,这个时候,我突然看到个样例二,一看不对!0 14 15 怎么输出14。原来是不止一次增加感染人数,可能会有左边和下面的点多次增加感染人数。。这时候就有点头痛了,不过我想还是先找找规律,既然如果是第一次增加,直接就是对应 值,那么肯定和 有关系。那就是这个时间 在作怪。这个时候,百思不得其解的时候开始算吧。。脑子不好使,手来凑,对于后面算出来的答案,我们吧 值提到前面去,可以惊奇的发现答案竟然等于 ,可以先交了,果不其然的A掉了,那有时间了再想想问什么和组合数有关系,还记得我前面说到这个时间在作怪么,那我们想想能不能吧这个时间给他转换到另外一个角度去,脑子灵光一闪看了题解之后,我们再把对应组合数写出来。
发现第一行从开始到结束是
第二行为 可以很明显的发现上面是递增加一的,那为什么第二行开头0没有了,并且比第一行少一个元素,因为一定需要花费时间1去向上一次行动,再一路向右走可以走的次数减一。那么这个组合数是不是就可以转换成从(0,0)每个时刻可以不动或者向上或者向右走,在时刻走到(i,j)的方案数。那么题目再根据我们推出来的公式,得到(i,j)感染人数等于走到此处的方案数乘上对应路径数。
如果你觉得上面方法不好理解这个组合数如何得来的,那么我刚刚又想到了一个新的解释为什么可以吧问题转换到时刻可动可不动的转换思路。我们通过上面知道一个点可能被多次更新,我们写出新的状态转移方程
通过这个状态转移方程我们不可能通过三重循环去求解,那么我们考虑每个对应状态,等于原来(i,j)不动,原来(i-1,j)原来(i,j-1)移动一格的方案数。所以就把时间划分为找 个时间动,这一条路中前往(x,y)的走法数。
其实到了这里还有另外一种不通过的方法就是在时间中找到 时间向上走,剩余 时间中取 次向右走的方法数。
那么整个题目要求就是求到(x,y)的方案数,就是路径数乘以每条路的走法数量。
#include <bits/stdc++.h> using namespace std; #define js ios::sync_with_stdio(false);cin.tie(0); cout.tie(0) typedef long long ll; inline int read() { int s = 0, w = 1; char ch = getchar(); while (ch < 48 || ch > 57) { if (ch == '-') w = -1; ch = getchar(); } while (ch >= 48 && ch <= 57) s = (s << 1) + (s << 3) + (ch ^ 48), ch = getchar(); return s * w; } const int N = 1e3 + 7; const int MOD = 998244353; ll jc[N * 5], inv[N * 5], dp[N][N]; ll qpow(ll a, ll b) { ll ans = 1; while (b) { if (b & 1) (ans *= a) %= MOD; b >>= 1; (a *= a) %= MOD; } return ans; } void init() { jc[0] = 1; //组合数预处理 for (int i = 1; i <= 5000; ++i) jc[i] = jc[i - 1] * i % MOD; inv[5000] = qpow(jc[5000], MOD - 2); for (int i = 4999; i >= 0; --i) inv[i] = inv[i + 1] * (i + 1) % MOD; for (int i = 0; i < N; ++i) //求解路径方法数 dp[i][0] = dp[0][i] = 1; for (int i = 1; i < N; ++i) for (int j = 1; j < N; ++j) dp[i][j] = (dp[i - 1][j] + dp[i][j - 1]) % MOD; } ll C(int n, int m) { return jc[n] * inv[m] % MOD * inv[n - m] % MOD; } int main() { init(); int T = read(); while (T--) { int n = read(), m = read(), k = read(); if (n + m > k) { puts("0"); continue; } int ans = dp[n][m] * C(k, n + m) % MOD; //dp[i][j]*C(t,x+y) printf("%d\n", ans); } return 0; } /* 第1秒 1 1 1 第2秒 1 1*2 2*1 1 2 1 第3秒 1 1*3 2*1 1*3 2*3 3*1 1*1 1*3 1*3 1*3 C(3,0) C(3,1) C(3,2) C(3,3) 第4秒 1 4 4 6 12 6 4 12 12 4 1 4 6 4 1 ---> 1 1*4 4*1 1*6 3*4 6*1 1*4 2*6 3*4 4*1 dp[i][j]*C(t,x+y) 1 4 6 4 1 */