CSP-S模拟19
T1.木棍
结论题。先列举所有可能的组合情况,容易发现可以先让抵消,然后分情况讨论:1.多,那么消;2.多,那么消,如果最后还剩一个,那就,最后把剩下的消完。
代码
#define sandom signed
#include <bits/stdc++.h>
#define int long long
using namespace std;
inline char getc() { static char buf[1 << 18], *p1, *p2; if (p1 == p2) { p1 = buf, p2 = buf + fread(buf, 1, 1 << 18, stdin); if (p1 == p2) return EOF; } return *p1++; }
inline int read() { int x = 0, f = 0; char c = getc(); while (!isdigit(c)) f = c == '-', c = getc(); while (isdigit(c)) x = (x << 1) + (x << 3) + (c ^ 48), c = getc(); return f ? -x : x; }
inline void write(int x) { static int wrt[20]; int TP = 0; if (x < 0) putchar('-'), x = -x; while (x >= 10) wrt[++TP] = x % 10, x /= 10; wrt[++TP] = x; while (TP) putchar(wrt[TP--] | 48); putchar('\n'); }
inline int max(int a, int b) { return a > b ? a : b; } inline int min(int a, int b) { return a < b ? a : b; }
int a, b, c, k, ans;
sandom main()
{
int T = read();
while (T--)
{
a = read(), b = read(), c = read(); ans = 0;
k = min(b >> 1, c); ans += k;
b -= 2 * k, c -= k;//2个3、1个4
if (!c)//还有3
{
k = min(a >> 1, b >> 1); ans += k;
a -= 2 * k, b -= 2 * k;//2个2、2个3
}
else //还有4
{
k = min(a, c >> 1); ans += k;
a -= k, c -= 2 * k;//1个2、2个4
}
if (c == 1 && a >= 3) ans++, c--, a -= 3;//3个2、1个4
ans += a / 5;//5个2
write(ans);
}
return 0;
}
T2.环
定义为考虑到左边前个位置,右边前个位置,右边的点构成条有向路径(链)(把单点也看成一条有向路径)的方案数。为了更方便的转移,不妨先将排序。考虑新加入第个点,这时右边新加入这些点,从这些点中选择若干个作为单点插入点集,得到转移
这时左侧第个点是独立的,没有与任何点连边,那也就方便了构造环。从我们已经构造出的链中选取一条,并将这条链的起点和终点分别和连边,构成一个环(因为这时起点和终点都在前面),还要减去单点构成的路径,它们不构成回路。所以有
这时我们选取两条链,让它们通过第个点合并成一条链,因为有向,显然每一组都有两种合并方式,有转移
正序枚举是为了保证合并之前没有与连边。不难发现,单向路径的起点和终点都属于右侧点。
代码
#define sandom signed
#include <bits/stdc++.h>
#define re register int
#define int long long
#define rep(i, a, b) for (re (i) = (a); (i) <= (b); ++(i))
#define dwn(i, a, b) for (re (i) = (a); (i) >= (b); --(i))
using namespace std;
const int Z = 5010; const int mod = 998244353;
inline char getc() { static char buf[1 << 18], *p1, *p2; if (p1 == p2) { p1 = buf, p2 = buf + fread(buf, 1, 1 << 18, stdin); if (p1 == p2) return EOF; } return *p1++; }
inline int read() { int x = 0, f = 0; char c = getc(); while (!isdigit(c)) f = c == '-', c = getc(); while (isdigit(c)) x = (x << 1) + (x << 3) + (c ^ 48), c = getc(); return f ? -x : x; }
inline int max(int a, int b) { return a > b ? a : b; } inline int min(int a, int b) { return a < b ? a : b; }
inline int qpow(int a, int b, int c) { int res = 1; while (b) { if (b & 1) res = 1ll * res * a % c; a = 1ll * a * a % c; b >>= 1; } return res; }
int n, m, ans;
int a[Z], dp[Z][Z];
int fac[Z], inv[Z];
void init(int n)
{
fac[0] = 1;
rep(i, 1, n) fac[i] = fac[i - 1] * i % mod;
inv[n] = qpow(fac[n], mod - 2, mod);
dwn(i, n - 1, 0) inv[i] = inv[i + 1] * (i + 1) % mod;
}
inline int C(int n, int m) { return fac[n] * inv[m] % mod * inv[n - m] % mod; }
sandom main()
{
n = read();
init(n);
rep(i, 1, n) a[i] = read();
sort(a + 1, a + 1 + n);
dp[0][0] = 1;
rep(i, 1, n)
{
int tmp = a[i] - a[i - 1];
rep(j, 0, a[i]) dwn(k, min(tmp, j), 0) (dp[i][j] += C(tmp, k) * dp[i - 1][j - k]) %= mod;//选择新加入的点
(ans += dp[i][1] - a[i]) %= mod;//强制通过第i个点构成环,并减去不构成回路的单点
rep(j, 0, a[i]) (dp[i][j] += dp[i][j + 1] * C(j + 1, 2) * 2) %= mod;//通过i合并两条链
}
cout << ans * qpow(2, mod - 2, mod) % mod;
return 0;
}
T3.传令
二分版的将军令。可以二分所用时间,然后贪心得到最少需要几个城市,与进行比较。当二分时间为时,一个显然的贪心:把节点从深到浅排序依次考虑,一个节点被覆盖到至少需要让它的级祖先被选,但是如果路径上有其他可以满足的点,就直接跳出。可以;也可以加一个标记优化,然后直接跳父亲;最好把能覆盖的点提前删掉,就不需要跳父亲了。
代码
#define sandom signed
#include <bits/stdc++.h>
#define re register int
#define rep(i, a, b) for (re (i) = (a); (i) <= (b); ++(i))
#define ede(i, rt) for (re i = head[rt]; i; i = e[i].ne)
using namespace std;
const int Z = 2e5 + 10;
inline char getc() { static char buf[1 << 18], *p1, *p2; if (p1 == p2) { p1 = buf, p2 = buf + fread(buf, 1, 1 << 18, stdin); if (p1 == p2) return EOF; } return *p1++; }
inline int read() { int x = 0, f = 0; char c = getc(); while (!isdigit(c)) f = c == '-', c = getc(); while (isdigit(c)) x = (x << 1) + (x << 3) + (c ^ 48), c = getc(); return f ? -x : x; }
inline int max(int a, int b) { return a > b ? a : b; } inline int min(int a, int b) { return a < b ? a : b; }
int n, m, k, ans;
struct edge { int v, ne; } e[Z << 1];
int head[Z], cnt, p[Z];
inline void add(int x, int y) { e[++cnt] = edge{y, head[x]}; head[x] = cnt; }
int dep[Z], dad[Z], kid[Z];
bool del[Z];
inline bool cmp(int x, int y) { return dep[x] > dep[y]; }
void dfs(int rt, int fa)
{
dep[rt] = dep[fa] + 1, dad[rt] = fa;
ede(i, rt) if (e[i].v != fa) dfs(e[i].v, rt);
}
void update(int rt, int x, int d)
{
rep(i, 0, d)
{
kid[rt] = min(kid[rt], dep[x]);
if (dad[rt] && i != d) rt = dad[rt];
}
}
int query(int rt, int x, int d)
{
rep(i, 0, d)
{
if (dep[x] + kid[rt] - 2 * dep[rt] <= d) return -rt;
if (dad[rt] && i != d) rt = dad[rt];
}
return rt;
}
void clear(int rt)
{
if (del[rt]) return;
del[rt] = 1;
ede(i, rt) if (e[i].v != dad[rt]) clear(e[i].v);
}
bool check(int d)
{
int tot = 0;
rep(i, 1, n) kid[i] = 1e9, del[i] = 0;
rep(i, 1, n)
{
if (del[p[i]]) continue;//已经被删除了
int j = query(p[i], p[i], d);
if (j > 0) tot++, update(j, j, d);//需要一个新的接收点
clear(abs(j));//清空子树
if (tot > k) return false;//超过数量
}
return true;
}
sandom main()
{
n = read(), k = read();
rep(i, 2, n)
{
int x = read(), y = read();
add(x, y), add(y, x);
}
dfs(1, 0);
rep(i, 1, n) p[i] = i;
sort(p + 1, p + 1 + n, cmp);
int l = 1, r = n / k;
while (l <= r)//最短的时间
{
int mid = l + r >> 1;
if (check(mid)) ans = mid, r = mid - 1;
else l = mid + 1;
}
cout << ans;
return 0;
}
T4.序列
正难则反,考虑容斥。先计算出总方案数,不考虑的序列是否是彩色序列,显然有
表示枚举出现的位置,剩下的位置随便放。
先求出一些方案数,定义为长度为,末尾一段互不相同的数字最长为的非彩色序列的排列数量。转移有两种情况:1.在末尾添加一个在中没有出现过的,那么,否则枚举新加入的数与前面哪一个数冲突了,变为后面新的一段,手磨一下,发现:
可以用后缀和优化,定义,这个东西后面也有用。
在非彩色序列中出现的次数,分三种情况考虑:
1.本身就是彩色序列,那么也一定是彩色序列,不合法的次数为,总方案数就是答案。
2.中存在相等的数字,那么序列一定不会包含,这时分别考虑左侧和右侧出现序列,在原序列中找到左边最长一段互不相同的数长度为,右边为,那么有
枚举的位置,前面有个空余的位置将它与左侧的连接,且不让它构成彩色序列,那么方案就是,只需要保证后缀最长的互不相同的长度小于即可,除以排列数是因为这一段已经确定;注意到我们的定义不仅可以是从前往后放,也可以从后往前放,所以也满足前缀和性质,因此右侧同理。因为相互独立,所以运用乘法原理。
3.中不存在相同的数,那么可能被序列包含,这时枚举向左右分别还扩展多长。
枚举的末尾位置,结尾最长扩展,那么这后面一整段的最长扩展范围应是左侧基础的和右侧的。前面的排列数是因为整个已经确定,后面是因为对于的每一种情况,个位置已经确定。
代码
#define sandom signed
#include <bits/stdc++.h>
#define re register int
#define int long long
#define rep(i, a, b) for (re (i) = (a); (i) <= (b); ++(i))
#define _rep(i, a, b) for (re (i) = (a); (i) < (b); ++(i))
#define dwn(i, a, b) for (re (i) = (a); (i) >= (b); --(i))
using namespace std;
const int Z = 25010; const int W = 410; const int mod = 1e9 + 7;
inline char getc() { static char buf[1 << 18], *p1, *p2; if (p1 == p2) { p1 = buf, p2 = buf + fread(buf, 1, 1 << 18, stdin); if (p1 == p2) return EOF; } return *p1++; }
inline int read() { int x = 0, f = 0; char c = getc(); while (!isdigit(c)) f = c == '-', c = getc(); while (isdigit(c)) x = (x << 1) + (x << 3) + (c ^ 48), c = getc(); return f ? -x : x; }
inline int max(int a, int b) { return a > b ? a : b; } inline int min(int a, int b) { return a < b ? a : b; }
inline int qpow(int a, int b, int c) { int res = 1; while (b) { if (b & 1) res = 1ll * res * a % c; a = 1ll * a * a % c; b >>= 1; } return res; }
int n, m, k, ans;
int a[Z], cnt[Z];
int dp[Z][W], sum[Z][W];
int fac[Z], inv[Z];
void init(int n)
{
fac[0] = 1;
rep(i, 1, n) fac[i] = fac[i - 1] * i % mod;
inv[n] = qpow(fac[n], mod - 2, mod);
dwn(i, n - 1, 0) inv[i] = inv[i + 1] * (i + 1) % mod;
}
inline int _A(int n, int m) { return fac[n - m] * inv[n] % mod; }
bool judge()
{
_rep(i, 1, k) cnt[a[i]]++;
rep(i, k, m)
{
cnt[a[i - k]]--, cnt[a[i]]++;
bool op = 1;
rep(j, 1, k) if (cnt[j] > 1) { op = 0; break; }
if (op) return true;//a是彩色序列
}
return false;
}
sandom main()
{
n = read(), k = read(), m = read();
init(n);
rep(i, 1, m) a[i] = read();
ans = (n - m + 1) * qpow(k, n - m, mod) % mod;//总方案数
if (judge()) { cout << ans; return 0; }
dp[0][0] = sum[0][0] = 1;
rep(i, 1, n)
{
dwn(j, k - 1, 1) dp[i][j] = ((k - j + 1) * dp[i - 1][j - 1] % mod + sum[i - 1][j]) % mod;
dwn(j, k - 1, 1) sum[i][j] = (sum[i][j + 1] + dp[i][j]) % mod;//后缀和
}
int l = 0, r = m + 1;//两端最长的互不相同
memset(cnt, 0, sizeof(cnt));
while (l < m && !cnt[a[l + 1]]) cnt[a[++l]] = 1;
memset(cnt, 0, sizeof(cnt));
while (r > 1 && !cnt[a[r - 1]]) cnt[a[--r]] = 1;
r = m - r + 1;
if (l < m)//存在相同的
{
rep(i, 0, n - m)
ans -= sum[i + l][l] * _A(k, l) % mod * sum[n - m - i + r][r] % mod * _A(k, r) % mod, ans %= mod;
}
else//互不相同的
{
rep(i, m, n) rep(j, m, min(k - 1, i))
ans -= dp[i][j] * _A(k, m) % mod * sum[n - i + j][j] % mod * _A(k, j) % mod, ans %= mod;
}
cout << (ans % mod + mod) % mod;
return 0;
}
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 分享一个免费、快速、无限量使用的满血 DeepSeek R1 模型,支持深度思考和联网搜索!
· 基于 Docker 搭建 FRP 内网穿透开源项目(很简单哒)
· ollama系列01:轻松3步本地部署deepseek,普通电脑可用
· 按钮权限的设计及实现
· 25岁的心里话