[赛记] 暑假集训CSP提高模拟7,8,10
学长pig 出题规律:T1签到题,T2套路题(但没见过),T3神奇题(赛时想的做法几乎都是错的),T4peppapig题
学长pig:今天T3防AK
peppapig:今天比赛防爆零
A. Permutations & Primes 20pts
签到题,可惜没有签到;
显然,我们要让经过1的区间最多,所以将1放在序列中间;
除了1,就是2和3,所以我们把2和3放在两边,这样就可以保证中间及不同时覆盖两边且覆盖到1的区间都是合法的;
然后随便填,就完了;
赛时将1和2放在了一块,导致20pts;
其实也可以将合数放中间,1放最中间,质数放两边,这样也是最大的,就是需要筛一下;
点击查看代码
#include <iostream>
#include <cstdio>
using namespace std;
int t;
int n;
int a[500005];
bool pri[500005];
bool vis[500005];
void w(int x) {
for (int i = 2; i <= x; i++) {
if (!vis[i]) {
vis[i] = true;
pri[i] = true;
for (int j = 2; i * j <= x; j++) {
vis[i * j] = true;
}
}
}
}
int main() {
cin >> t;
w(300005);
while(t--) {
cin >> n;
if (n == 1) {
cout << 1 << endl;
continue;
}
if (n == 2) {
cout << 2 << ' ' << 1 << endl;
continue;
}
a[n / 2 + 1] = 1;
a[1] = 2;
a[n] = 3;
int x = 4;
for (int i = 2; i <= n / 2; i++) {
a[i] = x;
x++;
}
for (int i = n / 2 + 2; i <= n - 1; i++) {
a[i] = x;
x++;
}
for (int i = 1; i <= n; i++) {
cout << a[i] << ' ';
}
cout << endl;
}
return 0;
}
B. 树上游戏 47pts
原题:
貌似又是套路题。。。
我们可以贪心的来考虑,假设现在我们所允许的不满意度(危险度)为
发现:如果知道答案,那么验证这个答案的合法性就变得比较容易;
做法:二分答案;
具体的,我们进行
最后判断当前选的点数与
赛时居然想到用找重心的方法去做,很显然是错的(新知识没掌握导致的,总是会乱想是不是刚学的)。又乱搞推出了一个看起来不太对的“通式”,整了47pts;
看来现在赛时想出正解确实不容易啊。。。
点击查看代码
#include <iostream>
#include <cstdio>
using namespace std;
int n, k;
struct sss{
int t, ne;
}e[1000005];
int h[1000005], cnt;
void add(int u, int v) {
e[++cnt].ne = h[u];
h[u] = cnt;
e[cnt].t = v;
}
int f[1000005], g[1000005];
int dfs(int x, int fa, int dis) {
int res = 0;
bool vis = false;
f[x] = 0x3f3f3f3f;
g[x] = -0x3f3f3f3f;
for (int i = h[x]; i; i = e[i].ne) {
int u = e[i].t;
if (u == fa) continue;
vis = true;
res += dfs(u, x, dis);
f[x] = min(f[x], f[u] + 1);
g[x] = max(g[x], g[u] + 1);
}
if (f[x] + g[x] <= dis) { //最大的危险程度可以接受,直接删除这棵子树;
g[x] = -0x3f3f3f3f;
}
if (!vis) { //是叶子节点;
g[x] = 0;
}
if (f[x] > dis) g[x] = max(g[x], 0); //危险程度太高,需要等祖先设立;
if (g[x] == dis) { //此时必须设立,要不后代就不满足要求了;
res++;
f[x] = 0;
g[x] = -0x3f3f3f3f; //删除这棵子树;
}
return res;
}
bool ck(int x) {
return (dfs(1, 0, x) + (g[1] >= 0)) <= k; //根节点需不需要设立;
}
int main() {
cin >> n >> k;
int x, y;
for (int i = 1; i <= n - 1; i++) {
cin >> x >> y;
add(x, y);
add(y, x);
}
int l = 1;
int r = n;
int ans;
while(l <= r) {
int mid = (l + r) >> 1;
if (ck(mid)) {
r = mid - 1;
ans = mid;
} else {
l = mid + 1;
}
}
cout << ans;
return 0;
}
可能把
C. Ball Collector 0pts
用到了一种数据结构,叫可撤销并查集;
其实这种题又有一种常见套路:将
其实赛时想出来了,不过是将 ;
连完边后,我们会得到若干个连通块,每个连通块有如下两个形态:
-
一棵树;
-
不是一棵树;
我们发现,只要一个点能够对应上独立的一条边,那么这个点就能选;
所以第二种情况能够选所有的边,要对答案产生贡献,只需研究第一种情况;
显然,我们让每条边对应一个儿子节点,能够匹配的点数最多;
注意,可撤销并查集只能用按秩合并,不能压缩路径,因为这样会破坏树的结构;
合并时,类似启发式合并,我们将
所以我们每次维护可撤销并查集,只需判断要连的两个点是否在同一个连通块内,若在,则判断这个连通块内深度最小的那个点是否被选,若被选(第二种情况),则不用考虑,继续搜;若没有被选(第一种情况),则标记其被选,并统计答案;若不在,则将
至于撤销的操作,其实就类似我们打暴搜时回溯取消标记一样,回溯时直接将所有改动的数组及变量复原即可;
点击查看代码
#include <iostream>
#include <cstdio>
using namespace std;
int n;
int a[1000005], b[1000005];
struct sas{
int t, ne;
}e[1000005];
int h[1000005], cnt;
void add(int u, int v) {
e[++cnt].t = v;
e[cnt].ne = h[u];
h[u] = cnt;
}
int fa[1000005], siz[1000005];
int num;
bool vis[1000005];
int ans[1000005];
int find(int x) {
return (x == fa[x]) ? x : find(fa[x]);
}
void dfs(int x, int faa) {
ans[x] = num;
for (int i = h[x]; i; i = e[i].ne) {
int u = e[i].t;
if (u == faa) continue;
int x1 = a[u];
int y = b[u];
x1 = find(x1);
y = find(y);
if (siz[x1] < siz[y]) swap(x1, y);
if (x1 == y) { // 在一个连通块里;
if (!vis[x1]) {
vis[x1] = true;
num++;
dfs(u, x);
vis[x1] = false; //回溯时取消更改;
num--;
} else {
dfs(u, x);
}
} else { // 不在一个连通块里;
int s = siz[x1];
bool xx = vis[x1];
bool yy = vis[y];
fa[y] = x1;
vis[y] = true;
vis[x1] = xx | yy;
siz[x1] += siz[y];
if (!xx || !yy) num++;
dfs(u, x);
fa[y] = y; // 回溯时取消更改;
siz[x1] = s;
vis[x1] = xx;
vis[y] = yy;
if (!xx || !yy) num--;
}
}
}
int main() {
cin >> n;
for (int i = 1; i <= n; i++) {
cin >> a[i] >> b[i];
}
int x, y;
for (int i = 1; i <= n - 1; i++) {
cin >> x >> y;
add(x, y);
add(y, x);
}
for (int i = 1; i <= n; i++) {
fa[i] = i;
siz[i] = 1;
}
num = 1;
if (a[1] == b[1]) {
vis[a[1]] = true;
} else {
fa[b[1]] = a[1];
siz[a[1]] += siz[b[1]];
}
dfs(1, 0);
for (int i = 2; i <= n; i++) cout << ans[i] << ' ';
return 0;
}
T4没改完,不写了;
B. 简单的拉格朗日反演练习题(lagrange)0pts
记一下套路;
将每条边的
主要是将每条边的
B. 速度型高松灯
原题:Luogu P3216 [HNOI2011] 数学作业
记一下套路;
这题的递推很好写,主要是加速过程;
用矩阵快速幂可以加速线性转移;
把转移矩阵写出来后,给它快速幂即可;
赛时忘了矩阵快速幂还可以干这事,其实如果想起来也忘了要咋打;
点击查看代码
#include <iostream>
#include <cstdio>
#include <cstring>
#include <cmath>
using namespace std;
unsigned long long nn, mm;
struct Mat{
unsigned long long n, m;
unsigned long long a[105][105];
void zero() {
memset(a, 0, sizeof(a));
}
void rsize(unsigned long long a, unsigned long long b) {
n = a;
m = b;
}
void one() {
zero();
for (unsigned long long i = 1; i <= n; i++) a[i][i] = 1;
}
Mat operator *(const Mat &A) const {
Mat ans;
ans.zero();
ans.rsize(n, A.m);
for (unsigned long long i = 1; i <= n; i++) {
for (unsigned long long j = 1; j <= A.m; j++) {
for (unsigned long long k = 1; k <= m; k++) {
ans.a[i][j] = (ans.a[i][j] % mm + (unsigned long long)a[i][k] * A.a[k][j] % mm) % mm;
}
}
}
return ans;
}
};
Mat ksm(Mat A, unsigned long long b) {
Mat ans;
ans.rsize(3, 3);
ans.one();
while(b) {
if (b & 1) ans = ans * A;
A = A * A;
b >>= 1;
}
return ans;
}
unsigned long long p[105];
unsigned long long cnt;
int main() {
cin >> nn >> mm;
Mat B;
B.rsize(3, 3);
B.zero();
Mat A;
A.rsize(3, 3);
A.zero();
A.one();
p[0] = 1;
for (unsigned long long i = 1; i <= 18; i++) {
p[i] = p[i - 1] * 10ll;
}
unsigned long long s = nn;
while(s) {
s /= 10;
cnt++;
}
for (unsigned long long k = 1; k <= cnt; k++) {
B.zero();
B.a[1][1] = p[k] % mm;
B.a[1][2] = B.a[2][2] = B.a[2][3] = B.a[3][3] = 1;
if (k == 1) {
A = A * ksm(B, min(nn - p[k - 1], p[k] - p[k - 1] - 1));
} else {
A = ksm(B, min(nn - p[k - 1] + 1, p[k] - p[k - 1])) * A;
}
}
cout << (A.a[1][1] + 2ll * A.a[1][2] + A.a[1][3]) % mm;
return 0;
}
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· Blazor Hybrid适配到HarmonyOS系统
· Obsidian + DeepSeek:免费 AI 助力你的知识管理,让你的笔记飞起来!
· 解决跨域问题的这6种方案,真香!
· 一套基于 Material Design 规范实现的 Blazor 和 Razor 通用组件库
· 数据并发安全校验处理工具类