A. Exercising Walk
以 x 的移动为例,令 dis=x2−x1,那么当它累积向左移动 dis 步,同时累积向右移动 dis 步时就会回到起始点,所以我们可以先将所有能够回到起始点的步数都减掉,使得所需要向左移动的步数以及向右移动的步数中,至少有一个的范围在 dis 以内。
此时就可以用 if else
判断,先向左移动几步,再向右移动几步,…,是否出界。由于至少有一侧的移动步数是小于 dis 的,所以讨论的情况并不多,只是细节需要考虑清楚。
#include <bits/stdc++.h>
using namespace std;
bool check(int p, int l, int r, int a, int b, int dis)
{
if (a <= p - l) {
p -= a;
return b <= r - p;
}
a -= p - l;
if (b <= dis)
return a <= b;
b -= dis;
return a <= dis && b <= a;
}
int main()
{
int T;
cin >> T;
while (T--) {
int a, b, c, d;
cin >> a >> b >> c >> d;
int x, y, x1, y1, x2, y2;
cin >> x >> y >> x1 >> y1 >> x2 >> y2;
int dis = x2 - x1, s;
if (dis)
s = min(a / dis, b / dis);
else
s = 0;
a -= s * dis, b -= s * dis;
if (check(x, x1, x2, a, b, dis)) {
dis = y2 - y1;
if (dis)
s = min(c / dis, d / dis);
else
s = 0;
c -= s * dis, d -= s * dis;
if (check(y, y1, y2, c, d, dis))
cout << "YES" << endl;
else
cout << "NO" << endl;
} else
cout << "NO" << endl;
}
return 0;
}
B. Composite Coloring
当几个数都是同一个质数的倍数时,它们就可以被涂上相同的颜色。
而题目又保证了颜色数量在 11 以内一定存在解,所以我们可以先把 1000 以内的质数筛出来(数据范围比较小,也不需要用线性筛),然后对于每个质数,再找它的倍数,如果这个倍数在原数组中且未被染色,就涂上一个新的颜色,最后输出颜色数组即可。
#include <bits/stdc++.h>
using namespace std;
const int N = 1010;
int n;
int a[N];
unordered_set<int> exist;
unordered_map<int, int> color;
bool vis[N];
void init()
{
for (int i = 2; i < N; i++)
if (!vis[i])
for (int j = 2; i * j < N; j++)
vis[i * j] = true;
return;
}
int main()
{
init();
int T;
cin >> T;
while (T--) {
exist.clear();
color.clear();
cin >> n;
for (int i = 1; i <= n; i++) {
cin >> a[i];
exist.insert(a[i]);
}
int c = 0;
for (int i = 2; i < N && c <= 10; i++)
if (!vis[i]) {
bool find = false;
for (int j = 1; i * j < N; j++)
if (exist.count(i * j) && !color.count(i * j)) {
if (!find) {
c++;
find = true;
}
color[i * j] = c;
}
}
cout << c << endl;
for (int i = 1; i <= n; i++)
cout << color[a[i]] << ' ';
cout << endl;
}
return 0;
}
C. K-Complete Word
我们来考虑建立这样一张图:
将字符串中的 n 个下标当做点,最终需要变为同一个字母的两个下标之间连一条无向边,由于具有传递性,所以只需要已判定的相同字母的下标集合中的任意一点,与新加入的点连边即可。
建完图之后就可以发现,所有需要出现相同字母的下标之间一定直接或者间接有边相连,否则的话,两个字母可以相同也可以不同。即:这张图上会出现若干个连通块。
对于每一个连通块,记录一下连通块中点的数量以及连通块中每个字母出现的次数,想要改变的次数最少,就是取该连通块中本就存在的字母数量最多的字母,让其它的字母全都变为该字母的操作次数。
最终将每个连通块的操作次数相加即为答案。
#include <bits/stdc++.h>
using namespace std;
const int N = 2e5 + 5, M = 8e5 + 5;
int n, k, res;
int cnt[30];
char str[N];
int h[N], e[M], ne[M], idx;
bool vis[N];
void add(int u, int v)
{
e[idx] = v, ne[idx] = h[u], h[u] = idx++;
}
void init()
{
if (idx) {
for (int i = 0; i < idx; i++)
h[i] = -1;
idx = 0;
} else
memset(h, -1, sizeof h);
for (int i = 1; i <= n; i++)
vis[i] = false;
return;
}
void dfs(int u)
{
vis[u] = true;
cnt[str[u] - 'a']++;
for (int i = h[u]; ~i; i = ne[i]) {
int j = e[i];
if (vis[j])
continue;
dfs(j);
}
return;
}
int main()
{
int T;
scanf("%d", &T);
while (T--) {
cin >> n >> k >> str + 1;
init();
for (int i = 1, j = n; i < j; i++, j--)
add(i, j), add(j, i);
for (int i = 1; i <= k; i++)
for (int j = i + k; j <= n; j += k)
add(i, j), add(j, i);
res = 0;
for (int i = 1; i <= n; i++)
if (!vis[i]) {
memset(cnt, 0, sizeof cnt);
dfs(i);
int sum = 0, val = 0;
for (int i = 0; i < 26; i++) {
sum += cnt[i];
val = max(val, cnt[i]);
}
res += sum - val;
}
printf("%d\n", res);
}
return 0;
}
D. Walk on Matrix
在样例 2 的启发下,我们能否构造出这样一组解,使得 (n,m−1) 从上方转移的值为 k,从左侧转移的值大于 k,但是和 k 进行与运算之后的结果为 0.
矩阵中数的范围为 3⋅105,而 k 的数据范围只有 105,所以一定存在一个数 t,使得 105<2t<3⋅105,我们就来构造如下矩阵(令 val=2t):
val + k k 0
val k 0
val val + k k
此时 (3,2) 这个点从上方转移的话,值为 k;从左侧转移的话,值为 val;而 val>k ,所以 dp[3][3]=val,又因为val & k=0,所以 (3,3) 这个点从左侧转移的话值为 0,而从上方转移值也为 0。因此,根据题目所给代码得到的 dp[3][3] 结果为 0,而实际上,(3,2) 这个点选择从上方转移,再转移到 (3,3) 的话,最终的值为 k。k−0=k,正好满足题意要求。
综上,只需要找到一个 val,使得 val+k≤3≤105,然后不论输入的 k 是多少,直接输出该矩阵即可。
#include <bits/stdc++.h>
using namespace std;
const int val = 1 << 17;
int k, a[5][5];
int main()
{
cin >> k;
a[1][1] = a[3][2] = val + k;
a[2][1] = a[3][1] = val;
a[1][2] = a[2][2] = a[3][3] = k;
cout << 3 << ' ' << 3 << endl;
for (int i = 1; i <= 3; i++) {
for (int j = 1; j <= 3; j++)
cout << a[i][j] << ' ';
cout << endl;
}
return 0;
}
E. Height All the Same
只使用操作 1 的话,我们总能把格子上方块的数量变成只相差一个或者直接相等:
- 若初始的各个格子方块的数量的奇偶性相同,则通过操作 1 总能使格子上方块的数量同时达到某一个值。
- 若初始的各个格子方块的数量的奇偶性不同,则通过操作 1 总能使格子上方块的数量两两直接相同或者只相差 1 个。
只使用操作 2 的话,我们总能改变两个格子上方块数量各自的奇偶性,例如用操作 2 变换这些格子:(m1,m2),(m2,m3),…,(mk−1,mk),除了 m1,mk 上方块数量只增了 1,改变了数量的奇偶性之外,中间的所有格子上方块的数量都增加了 2,不改变奇偶性;而如果在操作之前,m1,mk 上的方块数量相等,中间各个格子的方块数量相等且比 m1,mk 的方块数量多 1,则经过这个操作之后,再在 m1,mk 上各自执行一次操作 1,这条路径上的方块个数就会相等。
受此启发,不难发现,如果初始方块个数是偶数的格子数量也是偶数个,则它们之间可以两两通过上述方式改变,最终使得每个格子上方块的数量相同,而当 n⋅m 是奇数时,我们总能变化成该情况,即此时无论初始状态是什么,最终都可以将各个格子上方块的数量变成相等的个数。
接下去讨论 n⋅m 为偶数的情况,假设 [L,R] 区间内奇数有 odd 个,偶数有 even 个,此时若要从中挑选 i 个格子填偶数,则应有如下数量的填法:
eveni⋅oddn⋅m−i⋅Cin⋅m
因此,我们分别取 i 为 even 以内的偶数,累加起来就是答案数量,即:
n⋅m2∑i=0even2⋅i⋅oddn⋅m−2⋅i⋅C2⋅in⋅m
而
(even+odd)n⋅m=n⋅m2∑i=0even2⋅i⋅oddn⋅m−2⋅i⋅C2⋅in⋅m+n⋅m2∑i=1even2⋅i−1⋅oddn⋅m−(2⋅i−1)⋅C2⋅i−1n⋅m
(even−odd)n⋅m=n⋅m2∑i=0even2⋅i⋅oddn⋅m−2⋅i⋅C2⋅in⋅m−n⋅m2∑i=1even2⋅i−1⋅oddn⋅m−(2⋅i−1)⋅C2⋅i−1n⋅m
这两式相加即为答案的两倍,而中间过程取模之后可能改变奇偶性,而 mod+1 是偶数,且对 mod 取模之后为 1,所以可以将答案乘上 mod+1 再除以 2 来得到最终的结果。
#include <bits/stdc++.h>
using namespace std;
typedef long long LL;
const int mod = 998244353;
LL quickpow(LL a, LL b)
{
LL res = 1;
while (b) {
if (b & 1)
res = res * a % mod;
a = a * a % mod;
b >>= 1;
}
return res;
}
int main()
{
LL n, m, l, r;
cin >> n >> m >> l >> r;
if (n * m % 2)
cout << quickpow(r - l + 1, n * m) << endl;
else {
LL odd, even;
if (l & 1) {
odd = r - l + 1 + 1 >> 1;
even = r - l + 1 - odd;
} else {
even = r - l + 1 + 1 >> 1;
odd = r - l + 1 - even;
}
cout << (quickpow(odd + even, n * m) + quickpow(even - odd, n * m)) * (mod + 1) / 2 % mod << endl;
}
return 0;
}
F. Independent Set
对于每一种合法的方案,都可以对应成:将这种方案中的独立集中的点染上色,然后把其余所有点、边都加进来,但是其余点都不染色的方案。
因此我们来考虑如下动态规划:dp[i][0] 表示只考虑以 i 为根的子树,且 i 这个点未被染色的所有方案数;dp[i][1] 表示只考虑以 i 为根的子树,且 i 这个点被染色的所有方案数,由于每一条边的选与不选也会对答案产生影响,所以再定义一个 f[i] 表示删除 i 这个点与其儿子节点的边的方案数。
现假设我们需要计算 i 这个节点的上述三个值,而当前从 j 这个儿子节点回溯:
- 首先,对于一个节点有多个不同的儿子的时候,每个儿子节点为根的子树的选择方案都不会影响其它儿子,所以不断将答案累乘即可。
- 对于 dp[i][0]: 在不选择 i−>j 这条边的情况下,j 可以染色也可以不染色,只是当 j 染色的情况下,j 连向其儿子节点的边就至少得选一条,否则的话 j 这个点将孤立,而题意中最后挑选的独立集中的点不可能是孤立点,所以需要减去 f[j],因此该情况的方案数为:dp[j][0]+dp[j][1]−f[j];在选择 i−>j 这条边的情况下,j 也可以选择染色与不染色,该情况方案数为:dp[j][0]+dp[j][1].
- 对于 dp[i][1]: 在不选择 i−>j 这条边的情况下,与 dp[i][0] 同理,该情况方案数为:dp[j][0]+dp[j][1]−f[j];在选择 i−>j 这条边的情况下,j 这个点就不能再被染色,因此方案数为:dp[j][0].
- 对于 f[i]: 与 dp[i][0] 不选择 i−>j 这条边的情况同理,方案数为:dp[j][0]+dp[j][1]−f[j].
只需要在 DFS 过程中维护这三个值即可,注意中间过程很容易爆 int.
至于最终的答案,不妨设根节点为 1,根节点可以选择染色也可以不染色,而当根节点染色时,其连向儿子的边至少得有一条,不然根节点就会被孤立(跟上文同理),因此方案数为:dp[1][0]+dp[1][1]−f[1],只是需要注意的是,有一种特殊情况也被包含进了该方案数中,即:一条边都不选,一个点都不染色。该情况是不满足题意的,因为题目要求选出的边不能为空集,所以,只需要将这种情况减掉,就是我们最终答案,即:dp[1][0]+dp[1][1]−f[1]−1.
#include <bits/stdc++.h>
using namespace std;
const int N = 3e5 + 5, M = 6e5 + 5, mod = 998244353;
int n;
int h[N], e[M], ne[M], idx;
int f[N], dp[N][2];
void add(int u, int v)
{
e[idx] = v, ne[idx] = h[u], h[u] = idx++;
}
void dfs(int u, int fa)
{
dp[u][0] = dp[u][1] = f[u] = 1;
for (int i = h[u]; ~i; i = ne[i]) {
int j = e[i];
if (j == fa)
continue;
dfs(j, u);
dp[u][0] = dp[u][0] * (((2ll * dp[j][0] + 2 * dp[j][1] - f[j]) % mod + mod) % mod) % mod;
dp[u][1] = dp[u][1] * (((2ll * dp[j][0] + dp[j][1] - f[j]) % mod + mod) % mod) % mod;
f[u] = 1ll * f[u] * (((dp[j][0] + dp[j][1] - f[j]) % mod + mod) % mod) % mod;
}
return;
}
int main()
{
scanf("%d", &n);
memset(h, -1, sizeof h);
for (int i = 1; i < n; i++) {
int u, v;
scanf("%d %d", &u, &v);
add(u, v);
add(v, u);
}
dfs(1, -1);
printf("%lld\n", ((1ll * dp[1][0] + dp[1][1] - f[1] - 1) % mod + mod) % mod);
return 0;
}
G. No Monotone Triples
一开始写了个线段树来维护,测完样例之后发现看漏了题目的约束条件,然后,然后,然后就,好像,对于蒟蒻而言不太可做了?(等以后变强了再补,如果还记得的话