A – Range Flip Find Route
思博题。考虑最后的路径肯定是多个黑白子路径的拼接,那么路径的代价就是进入黑色子路径的次数,那么我们只需要建图的时候给白色入黑色的边赋 \(1\) 的权即可。
const int MAX_N = 110, dx[2] = {0, 1}, dy[2] = {1, 0};
typedef pair<int, int> pii;
int n, m, idx[MAX_N][MAX_N], head[MAX_N * MAX_N], current, dist[MAX_N * MAX_N], start_pos;
} edges[MAX_N * MAX_N << 3];
void addpath(int src, int dst, int weight)
edges[current].to = dst, edges[current].nxt = head[src];
edges[current].weight = weight, head[src] = current++;
memset(dist, 0x3f, sizeof(dist));
pq.push(make_pair(0, start_pos)), dist[start_pos] = 0;
for (int i = head[u]; i != -1; i = edges[i].nxt)
if (dist[edges[i].to] > dist[u] + edges[i].weight)
dist[edges[i].to] = dist[u] + edges[i].weight, pq.push(make_pair(-dist[edges[i].to], edges[i].to));
memset(head, -1, sizeof(head));
for (int i = 1; i <= n; i++)
for (int i = 1; i <= n; i++)
for (int j = 1; j <= m; j++)
idx[i][j] = m * (i - 1) + j;
for (int i = 1; i <= n; i++)
for (int j = 1; j <= m; j++)
for (int d = 0; d < 2; d++)
int dstx = i + dx[d], dsty = j + dy[d];
if (str[dstx][dsty] != 0)
if (str[dstx][dsty] != str[i][j])
addpath(idx[i][j], idx[dstx][dsty], 1);
addpath(idx[i][j], idx[dstx][dsty], 0);
addpath(idx[i][j], idx[dstx][dsty], 0);
addpath(start_pos, idx[1][1], str[1][1] == '#');
shortestPath(), printf("%d\n", dist[idx[n][m]]);
// A.cpp
#include <bits/stdc++.h>
using namespace std;
const int MAX_N = 110, dx[2] = {0, 1}, dy[2] = {1, 0};
typedef pair<int, int> pii;
int n, m, idx[MAX_N][MAX_N], head[MAX_N * MAX_N], current, dist[MAX_N * MAX_N], start_pos;
char str[MAX_N][MAX_N];
bool vis[MAX_N * MAX_N];
struct edge
{
int to, nxt, weight;
} edges[MAX_N * MAX_N << 3];
void addpath(int src, int dst, int weight)
{
edges[current].to = dst, edges[current].nxt = head[src];
edges[current].weight = weight, head[src] = current++;
}
void shortestPath()
{
memset(dist, 0x3f, sizeof(dist));
priority_queue<pii> pq;
pq.push(make_pair(0, start_pos)), dist[start_pos] = 0;
while (!pq.empty())
{
int u = pq.top().second;
pq.pop();
if (vis[u])
continue;
vis[u] = true;
for (int i = head[u]; i != -1; i = edges[i].nxt)
if (dist[edges[i].to] > dist[u] + edges[i].weight)
dist[edges[i].to] = dist[u] + edges[i].weight, pq.push(make_pair(-dist[edges[i].to], edges[i].to));
}
}
int main()
{
memset(head, -1, sizeof(head));
scanf("%d%d", &n, &m);
for (int i = 1; i <= n; i++)
scanf("%s", str[i] + 1);
for (int i = 1; i <= n; i++)
for (int j = 1; j <= m; j++)
idx[i][j] = m * (i - 1) + j;
start_pos = n * m + 1;
for (int i = 1; i <= n; i++)
for (int j = 1; j <= m; j++)
for (int d = 0; d < 2; d++)
{
int dstx = i + dx[d], dsty = j + dy[d];
if (str[dstx][dsty] != 0)
if (str[i][j] == '.')
if (str[dstx][dsty] != str[i][j])
addpath(idx[i][j], idx[dstx][dsty], 1);
else
addpath(idx[i][j], idx[dstx][dsty], 0);
else
addpath(idx[i][j], idx[dstx][dsty], 0);
}
addpath(start_pos, idx[1][1], str[1][1] == '#');
shortestPath(), printf("%d\n", dist[idx[n][m]]);
return 0;
}
// A.cpp
#include <bits/stdc++.h>
using namespace std;
const int MAX_N = 110, dx[2] = {0, 1}, dy[2] = {1, 0};
typedef pair<int, int> pii;
int n, m, idx[MAX_N][MAX_N], head[MAX_N * MAX_N], current, dist[MAX_N * MAX_N], start_pos;
char str[MAX_N][MAX_N];
bool vis[MAX_N * MAX_N];
struct edge
{
int to, nxt, weight;
} edges[MAX_N * MAX_N << 3];
void addpath(int src, int dst, int weight)
{
edges[current].to = dst, edges[current].nxt = head[src];
edges[current].weight = weight, head[src] = current++;
}
void shortestPath()
{
memset(dist, 0x3f, sizeof(dist));
priority_queue<pii> pq;
pq.push(make_pair(0, start_pos)), dist[start_pos] = 0;
while (!pq.empty())
{
int u = pq.top().second;
pq.pop();
if (vis[u])
continue;
vis[u] = true;
for (int i = head[u]; i != -1; i = edges[i].nxt)
if (dist[edges[i].to] > dist[u] + edges[i].weight)
dist[edges[i].to] = dist[u] + edges[i].weight, pq.push(make_pair(-dist[edges[i].to], edges[i].to));
}
}
int main()
{
memset(head, -1, sizeof(head));
scanf("%d%d", &n, &m);
for (int i = 1; i <= n; i++)
scanf("%s", str[i] + 1);
for (int i = 1; i <= n; i++)
for (int j = 1; j <= m; j++)
idx[i][j] = m * (i - 1) + j;
start_pos = n * m + 1;
for (int i = 1; i <= n; i++)
for (int j = 1; j <= m; j++)
for (int d = 0; d < 2; d++)
{
int dstx = i + dx[d], dsty = j + dy[d];
if (str[dstx][dsty] != 0)
if (str[i][j] == '.')
if (str[dstx][dsty] != str[i][j])
addpath(idx[i][j], idx[dstx][dsty], 1);
else
addpath(idx[i][j], idx[dstx][dsty], 0);
else
addpath(idx[i][j], idx[dstx][dsty], 0);
}
addpath(start_pos, idx[1][1], str[1][1] == '#');
shortestPath(), printf("%d\n", dist[idx[n][m]]);
return 0;
}
B – 123 Triangle
算贡献 + 找规律的题。
首先我们可以把输入的 \(1, 2, 3\) 转换成 \(0, 1, 2\)(做差结果不会变)。然后我们先来考虑判断答案的奇偶性,这个子问题的本质其实是若干个 \(0, 1\) 进行异或。这个三角形其实就是 Pascal 三角形,所以贡献就是 \({n – 1 \choose i}\)。所以,可以考虑用 Lucas 算这个组合数的奇偶性,然后放答案里。
如果最后判断出来是奇数,那么显然就是 \(1\);如果不是奇数,有性质:如果 \(1\) 出现在原序列中,则答案是 \(0\),否则为 \(2\)。如果原序列中只有 \(0, 2\),那么这个题完全可以看成 \(0, 1\) 的子问题,然后给答案乘回来一个 \(2\)。
const int MAX_N = 1e6 + 200;
int n, ai[MAX_N], fac[MAX_N];
int binomial(int n_, int k_) { return fac[n_] - fac[k_] - fac[n_ - k_]; }
scanf("%d%s", &n, str + 1);
for (int i = 1; i <= n; i++)
ai[i] = str[i] - '1', flag |= (ai[i] == 1);
for (int i = 1; i <= n; i++)
for (int i = 1; i <= n; i++)
for (int i = 1; i <= n; i++)
if ((ai[i] & 1) && (!binomial(n - 1, i - 1)))
// B.cpp
#include <bits/stdc++.h>
using namespace std;
const int MAX_N = 1e6 + 200;
int n, ai[MAX_N], fac[MAX_N];
char str[MAX_N];
int binomial(int n_, int k_) { return fac[n_] - fac[k_] - fac[n_ - k_]; }
int main()
{
scanf("%d%s", &n, str + 1);
bool flag = false;
for (int i = 1; i <= n; i++)
ai[i] = str[i] - '1', flag |= (ai[i] == 1);
for (int i = 1; i <= n; i++)
{
int acc = i;
while (!(acc & 1))
fac[i]++, acc >>= 1;
fac[i] += fac[i - 1];
}
if (!flag)
for (int i = 1; i <= n; i++)
ai[i] >>= 1;
int ans = 0;
for (int i = 1; i <= n; i++)
if ((ai[i] & 1) && (!binomial(n - 1, i - 1)))
ans ^= 1;
if (!flag)
ans <<= 1;
printf("%d\n", ans);
return 0;
}
// B.cpp
#include <bits/stdc++.h>
using namespace std;
const int MAX_N = 1e6 + 200;
int n, ai[MAX_N], fac[MAX_N];
char str[MAX_N];
int binomial(int n_, int k_) { return fac[n_] - fac[k_] - fac[n_ - k_]; }
int main()
{
scanf("%d%s", &n, str + 1);
bool flag = false;
for (int i = 1; i <= n; i++)
ai[i] = str[i] - '1', flag |= (ai[i] == 1);
for (int i = 1; i <= n; i++)
{
int acc = i;
while (!(acc & 1))
fac[i]++, acc >>= 1;
fac[i] += fac[i - 1];
}
if (!flag)
for (int i = 1; i <= n; i++)
ai[i] >>= 1;
int ans = 0;
for (int i = 1; i <= n; i++)
if ((ai[i] & 1) && (!binomial(n - 1, i - 1)))
ans ^= 1;
if (!flag)
ans <<= 1;
printf("%d\n", ans);
return 0;
}
C – Giant Graph
这个题是真你妈的屌。
首先肯定知道一点,因为这个指数设置的贼大,所以我们要用位运算贪心的思想,让 \(i + j + k\) 从大到小进行确定,这个图就可以连成一个 DAG 了。我们现在需要找一个独立集出来。
然后这个题开始变骚了,这是个博弈图,最优解的状态点本身就是最大独立集。我们可以考虑算出来这些 SG 函数值,然后算图里的 \(SG(x) \oplus SG(y) \oplus SG(z) = 0\) 的价值。发现 \(SG(x) \leq \sqrt{m}\),可以考虑直接暴力去算。
const int MAX_N = 1e5 + 200, mod = 998244353;
int n, m[3], base, vis[MAX_N], sg[MAX_N];
vector<int> G[MAX_N], sum[3];
int fpow(int bas, int tim)
ret = 1LL * ret * bas % mod;
bas = 1LL * bas * bas % mod;
scanf("%d", &n), base = fpow(ll(1e18) % mod, mod - 2);
for (int rd = 0; rd < 3; rd++)
for (int i = 1; i <= n; i++)
for (int i = 1, u, v; i <= m[rd]; i++)
memset(sg, 0, sizeof(sg)), memset(vis, -1, sizeof(vis));
int cbase = fpow(10, 18 * n);
for (int i = n; i >= 1; i--)
while (sum[rd].size() <= sg[i])
sum[rd][sg[i]] = (0LL + sum[rd][sg[i]] + cbase) % mod, cbase = 1LL * cbase * base % mod;
for (int i = 0, siz1 = sum[0].size(); i < siz1; i++)
for (int j = 0, siz2 = sum[1].size(); j < siz2; j++)
ans = (0LL + ans + 1LL * sum[0][i] * sum[1][j] % mod * sum[2][k] % mod) % mod;
// C.cpp
#include <bits/stdc++.h>
using namespace std;
const int MAX_N = 1e5 + 200, mod = 998244353;
typedef long long ll;
int n, m[3], base, vis[MAX_N], sg[MAX_N];
vector<int> G[MAX_N], sum[3];
int fpow(int bas, int tim)
{
int ret = 1;
while (tim)
{
if (tim & 1)
ret = 1LL * ret * bas % mod;
bas = 1LL * bas * bas % mod;
tim >>= 1;
}
return ret;
}
int main()
{
scanf("%d", &n), base = fpow(ll(1e18) % mod, mod - 2);
for (int rd = 0; rd < 3; rd++)
{
scanf("%d", &m[rd]);
for (int i = 1; i <= n; i++)
G[i].clear();
for (int i = 1, u, v; i <= m[rd]; i++)
{
scanf("%d%d", &u, &v);
if (u > v)
swap(u, v);
G[u].push_back(v);
}
memset(sg, 0, sizeof(sg)), memset(vis, -1, sizeof(vis));
int cbase = fpow(10, 18 * n);
for (int i = n; i >= 1; i--)
{
for (int v : G[i])
vis[sg[v]] = i;
while (vis[sg[i]] == i)
sg[i]++;
while (sum[rd].size() <= sg[i])
sum[rd].push_back(0);
sum[rd][sg[i]] = (0LL + sum[rd][sg[i]] + cbase) % mod, cbase = 1LL * cbase * base % mod;
}
}
int ans = 0;
for (int i = 0, siz1 = sum[0].size(); i < siz1; i++)
for (int j = 0, siz2 = sum[1].size(); j < siz2; j++)
{
int k = i ^ j;
if (k < sum[2].size())
ans = (0LL + ans + 1LL * sum[0][i] * sum[1][j] % mod * sum[2][k] % mod) % mod;
}
printf("%d\n", ans);
return 0;
}
// C.cpp
#include <bits/stdc++.h>
using namespace std;
const int MAX_N = 1e5 + 200, mod = 998244353;
typedef long long ll;
int n, m[3], base, vis[MAX_N], sg[MAX_N];
vector<int> G[MAX_N], sum[3];
int fpow(int bas, int tim)
{
int ret = 1;
while (tim)
{
if (tim & 1)
ret = 1LL * ret * bas % mod;
bas = 1LL * bas * bas % mod;
tim >>= 1;
}
return ret;
}
int main()
{
scanf("%d", &n), base = fpow(ll(1e18) % mod, mod - 2);
for (int rd = 0; rd < 3; rd++)
{
scanf("%d", &m[rd]);
for (int i = 1; i <= n; i++)
G[i].clear();
for (int i = 1, u, v; i <= m[rd]; i++)
{
scanf("%d%d", &u, &v);
if (u > v)
swap(u, v);
G[u].push_back(v);
}
memset(sg, 0, sizeof(sg)), memset(vis, -1, sizeof(vis));
int cbase = fpow(10, 18 * n);
for (int i = n; i >= 1; i--)
{
for (int v : G[i])
vis[sg[v]] = i;
while (vis[sg[i]] == i)
sg[i]++;
while (sum[rd].size() <= sg[i])
sum[rd].push_back(0);
sum[rd][sg[i]] = (0LL + sum[rd][sg[i]] + cbase) % mod, cbase = 1LL * cbase * base % mod;
}
}
int ans = 0;
for (int i = 0, siz1 = sum[0].size(); i < siz1; i++)
for (int j = 0, siz2 = sum[1].size(); j < siz2; j++)
{
int k = i ^ j;
if (k < sum[2].size())
ans = (0LL + ans + 1LL * sum[0][i] * sum[1][j] % mod * sum[2][k] % mod) % mod;
}
printf("%d\n", ans);
return 0;
}
D – Merge Triplets
考虑生成后的序列的性质。原序列 \(A_i = (X_1, X_2, X_3)\),如果满足 \(X_i > X_{i + 1}\),那么这两个元素在 \(P\) 序列中铁定连续;当然,如果满足 \(X_1 > X_2, X_1 > X_3 \),那么也是铁定连续的。那么一个这样的 \(X_i\) 可以作为块起点,把三元组进行细分。那么这些小块其实就是目标序列的一个个子串。
所以如果一个目标排列 \(P\) 要能被计数,当且仅当存在一种分块方式,使得若干个块拼成若干个单元,且每个单元的元素个数为 \(3\)。
如果分块方式确定了,是不是元素就被固定了?显然是这样的。首先块内的大小已经被确定,且每次放置一个块,则这个块的第一个数肯定大于之前的所有块的第一个数。又因为第一个数是块的最大数,那么至少块间的大小关系已经被确认;且,块内的数字也是递减的,所以块内的关系也确定了。这个肯定是个唯一的排列。
所以只需要算分块方案即可。怎样的分块方案合法?要么分 \(3, 2, 1\) 块,但是注意长度为 \(2\) 块数量一定要少于长度为 \(1\) 的块数,这样有完美匹配。设置 \(dp[i][j]\),\(i\) 为最后位置,\(j\) 为长度为 \(1\) 的块的块数减去长度为 \(2\) 的块的块数,设置方式有点像 CSP-S 2019 D2T1。最后取 \(\geq 0\) 的即可。
const int MAX_N = 2020, base = MAX_N;
int n, mod, dp[MAX_N * 3][MAX_N * 4];
scanf("%d%d", &n, &mod), dp[0][base] = 1;
for (int i = 0; i < 3 * n; i++)
for (int j = -n; j <= 3 * n; j++)
dp[i + 1][j + 1 + base] = (0LL + dp[i + 1][j + 1 + base] + dp[i][j + base]) % mod;
dp[i + 2][j - 1 + base] = (0LL + dp[i + 2][j - 1 + base] + 1LL * dp[i][j + base] * (i + 1) % mod) % mod;
dp[i + 3][j + base] = (0LL + dp[i + 3][j + base] + 1LL * dp[i][j + base] * (i + 2) % mod * (i + 1) % mod) % mod;
for (int i = 0; i <= 3 * n; i++)
ans = (0LL + ans + dp[3 * n][i + base]) % mod;
// D.cpp
#include <bits/stdc++.h>
using namespace std;
const int MAX_N = 2020, base = MAX_N;
int n, mod, dp[MAX_N * 3][MAX_N * 4];
int main()
{
scanf("%d%d", &n, &mod), dp[0][base] = 1;
for (int i = 0; i < 3 * n; i++)
for (int j = -n; j <= 3 * n; j++)
{
dp[i + 1][j + 1 + base] = (0LL + dp[i + 1][j + 1 + base] + dp[i][j + base]) % mod;
dp[i + 2][j - 1 + base] = (0LL + dp[i + 2][j - 1 + base] + 1LL * dp[i][j + base] * (i + 1) % mod) % mod;
dp[i + 3][j + base] = (0LL + dp[i + 3][j + base] + 1LL * dp[i][j + base] * (i + 2) % mod * (i + 1) % mod) % mod;
}
int ans = 0;
for (int i = 0; i <= 3 * n; i++)
ans = (0LL + ans + dp[3 * n][i + base]) % mod;
printf("%d\n", ans);
return 0;
}
// D.cpp
#include <bits/stdc++.h>
using namespace std;
const int MAX_N = 2020, base = MAX_N;
int n, mod, dp[MAX_N * 3][MAX_N * 4];
int main()
{
scanf("%d%d", &n, &mod), dp[0][base] = 1;
for (int i = 0; i < 3 * n; i++)
for (int j = -n; j <= 3 * n; j++)
{
dp[i + 1][j + 1 + base] = (0LL + dp[i + 1][j + 1 + base] + dp[i][j + base]) % mod;
dp[i + 2][j - 1 + base] = (0LL + dp[i + 2][j - 1 + base] + 1LL * dp[i][j + base] * (i + 1) % mod) % mod;
dp[i + 3][j + base] = (0LL + dp[i + 3][j + base] + 1LL * dp[i][j + base] * (i + 2) % mod * (i + 1) % mod) % mod;
}
int ans = 0;
for (int i = 0; i <= 3 * n; i++)
ans = (0LL + ans + dp[3 * n][i + base]) % mod;
printf("%d\n", ans);
return 0;
}