[考试记录] 2024.8.10 csp-s 模拟赛18
80 + 20 + 0 + 70 = 170 第三题应该有 10 分暴力的,但我没打。
T1 星际旅行
题面翻译
总共有n个节点,m条路径,要求其中m-2条路径走两遍,剩下2条路径仅走一遍,问不同的路径总数有多少,如果仅走一遍的两条边不同则将这两条路径视为不同。
样例 #1
样例输入 #1
5 4
1 2
1 3
1 4
1 5
样例输出 #1
6
样例 #2
样例输入 #2
5 3
1 2
2 3
4 5
样例输出 #2
0
解析
结论题
考场上没放输出 \(0\) 的那个样例,于是显然想不到非联通的那种方案。是我太\(菜\)了。
多手玩几个样例(1h)可以发现:
- 对于没有自环的情况,假设将 \(rt\) 作为该条航线的出发点,那么手玩一下即可发现,能产生的合法航线数即为 \(rt\) 的儿子的儿子数量。emmm……有点绕。看图。
标红的线即为可能出现的只走一次的边,标绿色的线就是可能的走法。也就是每次从 \(rt\) 出发最终回到某个儿子的儿子上,这个儿子的儿子与 \(rt\) 连的边即为只走一次的边。那么 \(rt\) 对答案的贡献就是:
\]
其中,\(size\) 表示这个点与多少个点直接相连,因为不能算父亲,所以减一。总贡献即为:
\]
- 对于存在自环的情况,考虑两种情况:自环配自环、自环配普通边。
- 自环配自环:方案数即为从所有自环里随机取出两条的组合:\({\large\binom{cir}{2}}\)。\(cir\) 为自环数。
- 自环配普通边:方案数即为从所有自环里随机选一条和从普通边里随机选取一条。运用乘法原理:\(cir\times(m-cir)\)。
算完了?你猜为什么样例有个 \(0\)?如果存在某条边与其他所有的边都不联通,那么就无法走完 \(m\) 条边。所以要判边是否联通。运用并查集即可。
code
#include<bits/stdc++.h>
using namespace std;
#define int long long
constexpr int N = 1e5 + 5;
int n, m, cir, ans1, ans2, f[N], x[N];
vector<int> G[N];
inline int find(int k){
if(!f[k]) return k;
return f[k] = find(f[k]);
}
signed main(){
// freopen("t1.in", "r", stdin);
ios::sync_with_stdio(0), cin.tie(0), cout.tie(0);
cin>>n>>m;
for(int i=1, y; i<=m; ++i){
cin>>x[i]>>y;
int fa = find(x[i]), fb = find(y);
if(fa != fb) f[fb] = fa;
if(x[i] == y){ ++cir; continue; }
G[x[i]].push_back(y), G[y].push_back(x[i]);
} int bg = find(x[1]);
for(int i=2; i<=m; ++i) if(find(x[i]) != bg) return cout<<0, 0;
if(cir) ans2 = cir * (cir-1) / 2 + cir * (m - cir);
for(int i=1; i<=n; ++i) for(int v : G[i]) ans1 += G[v].size() - 1;
ans1 >>= 1; return cout<<ans1 + ans2, 0;
}
T2 砍树
题面
林先森买了 \(n\) 棵树苗,种在一条直线上,用来装点他的花园。初始时所有树苗的高度是 \(0\),每过 \(1\) 天每棵树苗都会长高 \(1\) 米。对每棵树苗,林先森希望它 的最终高度为 \(a_i\),因此他会定时检查树苗的情况,并及时砍掉过高的树苗。具 体来说,从种下所有树苗开始,每d天(即:第 \(d\) 天、第 \(2d\) 天,. . . ,以此类推) 林先森会检查一遍所有的树苗,如果有树苗的高度不低于他希望的高度,林先 森会把高出的部分(可以为 \(0\))砍掉,之后这棵树苗便不再长高。由于砍树是一 件辛苦的工作,林先森希望砍掉的树苗的总长度不超过k米。在这个前提下, 为了偷懒,林先森想要知道最大可能的 \(d\) 值。
sample
3 4
1 3 5
3
解析
熊出没题
考场上脑子宕机拉了一泡二分答案,样例过了就没再管,喜提 \(20\) 分。下考后拿脚指头想都觉得二分没单调性。
考虑这么个事情。我们要找的 \(d\) 都满足这么个狮子:
\]
移项得:
\]
这就清楚了。可以发现,我们可以枚举 \(\left \lceil \frac{a_i}{d} \right \rceil\) 和 \(d\) 的所有可能取值,暴力枚举判断即可。假设现在枚举到了一个可能的 \(d'\) 值。那么现在的 \(d\) 的取值范围就可以表示为:
\]
如果 \(d'\) 在这个范围内,那么这个范围就是合法的。用这个范围的最大值更新答案即可。
code
#include<bits/stdc++.h>
using namespace std;
#define int long long
int n, k, a[101], ans;
vector<int> vec;
signed main(){
ios::sync_with_stdio(0), cin.tie(0), cout.tie(0);
cin>>n>>k;
for(int i=1; i<=n; ++i){
cin>>a[i]; k += a[i];
for(int j=1; j*j<=a[i]; ++j){
vec.push_back(j);
vec.push_back((a[i] + j - 1) / j);
}
}
sort(vec.begin(), vec.end());
vec.erase(unique(vec.begin(), vec.end()), vec.end());
for(int it : vec){
int sum = 0, d;
for(int i=1; i<=n; ++i){
sum += (a[i] + it - 1) / it;
} d = k / sum;
if(d >= it) ans = max(ans, d);
} return cout<<ans, 0;
}
对于以后类似这种题的情况。提供两种解决思路:
- 枚举样例 + 手玩。找到规律。
- 列出答案的表达式。观察 + 暴力拆狮子。
T3 超级树
T4 成绩单
题目描述
期末考试结束了,班主任 L 老师要将成绩单分发到每位同学手中。L 老师共有 \(n\) 份成绩单,按照编号从 \(1\) 到 \(n\) 的顺序叠放在桌子上,其中编号为 \(i\) 的的成绩单分数为 \(W_i\)。
成绩单是按照批次发放的。发放成绩单时,L 老师会从当前的一叠成绩单中抽取连续的一段,让这些同学来领取自己的成绩单。当这批同学领取完毕后,L 老师再从剩余的成绩单中抽取连续的一段,供下一批同学领取。经过若干批次的领取后,成绩单将被全部发放到同学手中。
然而,分发成绩单是一件令人头痛的事情,一方面要照顾同学们的心理情绪,不能让分数相差太远的同学在同一批领取成绩单;另一方面要考虑时间成本,尽量减少领取成绩单的批次数。对于一个分发成绩单的方案,我们定义其代价为:
\]
其中 \(k\) 是分发的批次数,对于第 \(i\) 批分发的成绩单,\(max_i\) 是最高分数,\(min_i\) 是最低分数,\(a\) 和 \(b\) 是给定的评估参数。现在,请你帮助 L 老师找到代价最小的分发成绩单的方案,并将这个最小的代价告诉 L 老师。当然,分发成绩单的批次数 \(k\) 是你决定的。
输入格式
第一行包含一个正整数 \(n\),表示成绩单的数量。第二行包含两个非负整数 \(a,b\),表示给定的评估参数。第三行包含 \(n\) 个正整数,\(w_i\) 表示第 \(i\) 张成绩单上的分数。
输出格式
仅一个正整数,表示最小的代价是多少。
样例 #1
样例输入 #1
10
3 1
7 10 9 10 6 7 10 7 1 2
样例输出 #1
15
提示
\(n \leq 50\),\(a \leq 1500\),\(b \leq 10\),\(w_i \leq 1000\)。
解析
for(int l=1, r=len; r<=len; ++l, ++r)
下回遇到这种情况直接重构得了
一眼区间 DP,但状态没设对,还是太。考场上瞬间想了个 DP,设 \(dp[l][r][k]\) 表示通过 \(k\) 次消去区间 \(l\sim r\) 所需要的最小花费。想都没想拉了一坨 dP,没想到小样例竟然过了,并且取得了高贵的 \(70pts\)。但事后想了想,很明显是假的。
假的code(场码)
#include<bits/stdc++.h>
using namespace std;
#define int long long
int n, a, b, res[51], dp[51][51][51], ans = LONG_MAX;
struct SquareTable{
int mx[51][10], mn[51][10];
inline void init(){
for(int i=1; i<=n; ++i) mx[i][0] = mn[i][0] = res[i];
for(int j=1; j<=__lg(n); ++j) for(int i=1; i+(1<<j)-1<=n; ++i){
mx[i][j] = max(mx[i][j-1], mx[i+(1<<(j-1))][j-1]);
mn[i][j] = min(mn[i][j-1], mn[i+(1<<(j-1))][j-1]);
}
}
inline int QueryMx(int l, int r){
if(l > r) return -1;
int k = __lg(++r - l);
return max(mx[l][k], mx[r-(1<<k)][k]);
}
inline int QueryMn(int l, int r){
if(l > r) return INT_MAX;
int k = __lg(++r - l);
return min(mn[l][k], mn[r-(1<<k)][k]);
}
} st;
signed main(){
ios::sync_with_stdio(0), cin.tie(0) ,cout.tie(0);
cin>>n>>a>>b; for(int i=1; i<=n; ++i) cin>>res[i]; st.init();
if(b == 0) return cout<<a, 0;
memset(dp, 0x7f, sizeof(dp));
for(int len=1; len<=n; ++len) for(int l=1, r=len; r<=n; ++l, ++r)
dp[l][r][1] = a + b * (st.QueryMx(l, r) - st.QueryMn(l, r)) * (st.QueryMx(l, r) - st.QueryMn(l, r));
for(int len=2; len<=n; ++len){
for(int l=1, r=len; r<=n; ++r, ++l){
for(int k=2; k<=len; ++k){
for(int lenn=1; lenn<len; ++lenn){
for(int ln=l, rn=l+lenn-1; rn<=r; ++ln, ++rn){
// printf("l = %lld r = %lld ln = %lld rn = %lld ", l, r, ln, rn);
int lmx = st.QueryMx(l, ln-1), lmn = st.QueryMn(l, ln-1);
int rmx = st.QueryMx(rn+1, r), rmn = st.QueryMn(rn+1, r);
int mx, mn;
if(l == ln) mx = rmx, mn = rmn;
else if(r == rn) mx = lmx, mn = lmn;
else mx = max(lmx, rmx), mn = min(lmn, rmn);
// printf("mx = %lld mn = %lld\n", mx, mn);
dp[l][r][k] = min(dp[l][r][k], dp[ln][rn][k-1] + a + b * (mx - mn) * (mx - mn));
}
}
}
}
}
for(int i=1; i<=n; ++i) ans = min(ans, dp[1][n][i]);
return cout<<ans, 0;
}
就比如这段区间,我的 dP 只能一段一段扩展,不能让次数为 \(k\) 和 \(p\) 的两个区间合并。也就是说,我的 dP 不能在一整段里扣,而是不断从两边上扣。所以是假的。数据很水。
正解
灵魂 DP
给你 \(n\le 50\) 是有原因的。我们发现 DP 状态只有 \(l\) 和 \(r\) 两个维度很难转移。所以考虑添加状态。因为每一段区间的代价只与 \(max\) 和 \(min\) 有关。所以设 \(g[l][r][mx][mn]\) 表示消去了 \(l\sim r\) 这段区间中某些部分剩下的散块中最值为 \(mx\) 和 \(mn\),然后全部消掉(把 \(l\sim r\) 全消掉)的最小花费。设 \(f[l][r]\) 表示把 \(l\sim r\) 全消掉的最小花费。对于多状态 DP 方程,现在就需要建立两个状态之间的关系。
可以发现,对于 \(g[l][r][mx][mn]\) 的散块其实只需要一步操作就能全部消掉,所以有:
\]
现在考虑 \(g\) 的扩展。假设在右边加入新点 \(r+1\)。那么会有两种情况:
把新点加到已经消掉的那部分里去,那么就不会对现有状态产生影响:
\[g[l][r+1][mx][mn]=min\{g[l][r][mx][mn]+f[r][r] \}
\]但是发现,对于 \(r\) 后面任何一个点都能满足这个转移方程,显然是需要刷表的。考虑要把区间扩展到 \(l\sim k\),那么有:
\[g[l][k][mx][mn]=min\{g[l][r][mx][mn]+f[r+1][k] \}
\]不好转移?把 \(r\) 和 \(k\) 调换一下:
\[g[l][r][mx][mn]=min\{g[l][k][mx][mn]+f[k+1][r] \}
\]把新点加到没有消除的散块里去,需要更新当前状态:
\[g[l][r+1][max(mx, a_{r+1})][min(mn,a_{r+1})]=min\{g[l][r][mx][mn] \}
\]依旧考虑换一下:
\[g[l][r][max(mx, a_{r})][min(mn,a_{r})]=min\{g[l][r-1][mx][mn] \}
\]
至此所有转移都已完成。但,\(a\) 的范围有点大,需要离散化。复杂度 \(\mathcal{O}(n^5)\)。
code
#include<bits/stdc++.h>
using namespace std;
#define int long long
int n, a, b, res[51], pos[51], g[51][51][51][51], f[51][51];
signed main(){
ios::sync_with_stdio(0), cin.tie(0), cout.tie(0);
cin>>n>>a>>b; for(int i=1; i<=n; ++i) cin>>res[i], pos[i] = res[i];
sort(pos+1, pos+1+n); int cnt = unique(pos+1, pos+1+n) - pos - 1;
for(int i=1; i<=n; ++i) res[i] = lower_bound(pos+1, pos+1+cnt, res[i]) - pos;
memset(f, 0x3f, sizeof(f)), memset(g, 0x3f, sizeof(g));
for(int i=1; i<=n; ++i) f[i][i] = a, g[i][i][res[i]][res[i]] = 0;
for(int len=2; len<=n; ++len) for(int l=1, r=len; r<=n; ++l, ++r){
for(int mx=1; mx<=cnt; ++mx) for(int mn=1; mn<=mx; ++mn)
g[l][r][max(mx, res[r])][min(mn, res[r])] = min(g[l][r][max(mx, res[r])][min(mn, res[r])], g[l][r-1][mx][mn]);
for(int mx=1; mx<=cnt; ++mx) for(int mn=1; mn<=mx; ++mn){
for(int k=l; k<r; ++k) g[l][r][mx][mn] = min(g[l][r][mx][mn], g[l][k][mx][mn] + f[k+1][r]);
f[l][r] = min(f[l][r], g[l][r][mx][mn] + a + b * (pos[mx]-pos[mn]) * (pos[mx]-pos[mn]));
}
} return cout<<f[1][n], 0;
}
[考试记录] 2024.8.10 csp-s 模拟赛18的更多相关文章
- 10.17 NOIP模拟赛
目录 2018.10.17 NOIP模拟赛 A 咒语curse B 神光light(二分 DP) C 迷宫maze(次短路) 考试代码 B 2018.10.17 NOIP模拟赛 时间:1h15min( ...
- 10.16 NOIP模拟赛
目录 2018.10.16 NOIP模拟赛 A 购物shop B 期望exp(DP 期望 按位计算) C 魔法迷宫maze(状压 暴力) 考试代码 C 2018.10.16 NOIP模拟赛 时间:2h ...
- 10.30 NFLS-NOIP模拟赛 解题报告
总结:今天去了NOIP模拟赛,其实是几道USACO的经典的题目,第一题和最后一题都有思路,第二题是我一开始写了个spfa,写了一半中途发现应该是矩阵乘法,然后没做完,然后就没有然后了!第二题的暴力都没 ...
- 2018.10.16 NOIP模拟赛解题报告
心路历程 预计得分:\(100 + 100 + 20 = 220\) 实际得分:\(100 + 100 + 30 = 230\) 辣鸡模拟赛.. T1T2都是一眼题,T3考验卡常数还只有一档暴力分. ...
- 2016.10.29 NOIP模拟赛 PM 考试整理
300分的题,只得了第三题的100分. 题目+数据:链接:http://pan.baidu.com/s/1o7P4YXs 密码:4how T1:这道题目存在着诸多的问题: 1.开始的序列是无法消除的( ...
- 2016.10.30 NOIP模拟赛 day2 AM 整理
题目+数据:链接:http://pan.baidu.com/s/1gfBg4h1 密码:ho7o 总共得了:130分, 1:100分 2:30分(只会这30分的暴力) 3:0(毫无思路) 虽然不高, ...
- 2017 10.25 NOIP模拟赛
期望得分:100+40+100=240 实际得分:50+40+20=110 T1 start取了min没有用,w(゚Д゚)w O(≧口≦)O T3 代码3个bug :数组开小了,一个细节没注意, ...
- 2018.10.26 NOIP2018模拟赛 解题报告
得分: \(0+10+10=20\)(\(T1\)死于假题面,\(T3\)死于细节... ...) \(P.S.\)由于原题是图片,所以我没有上传题目描述,只有数据. \(T1\):颜料大乱斗(点此看 ...
- 【2019.10.7 CCF-CSP-2019模拟赛 T1】树上查询(tree)(思维)
思维 这道题应该算是一道思维题吧. 首先你要想到,既然这是一棵无根树,就要明智地选择根--以第一个黑点为根(不要像我一样习惯性以\(1\)号点为根,结果直到心态爆炸都没做出来). 想到这一点,这题就很 ...
- 2016.10.30 NOIP模拟赛 day2 PM 整理
满分:300分 直接全部爆零,真的是很坑啊! 10.30的题目+数据:链接:http://pan.baidu.com/s/1jHXLace 密码:i784 T1: 题目中的难点就是每次折叠的点可能应经 ...
随机推荐
- ThreadLocal 核心源码分析
ThreadLocal 简介 多线程访问同一个共享变量的时候容易出现并发问题,特别是多个线程对一个变量进行写入的时候,为了保证线程安全,一般使用者在访问共享变量的时候需要进行额外的同步措施才能保证线程 ...
- gitlab角色与权限
用户在项目中的角色 Guest:访客.可以创建issue.发表评论,不能读写版本库.(就是看不了代码-) Reporter:Git项目测试人员.可以克隆代码,不能提交.QA.PM可以赋予这个权限. D ...
- 07-Linux文件权限管理
文件的类型 Linux的哲学思想:一切皆文件. Linux的文件分为多种类型. 可以通过ll命令查看文件的类型: ll #输出: -rw-------. 1 root root 1266 2月 29 ...
- CLR via C# 笔记 -- 线程基础(26)
1. Microsoft 设计这个OS内核时,决定在一个进程中运行应用程序的每个实例.进程实际是应用程序的实例要使用的资源的集合.每个进程都被赋予了一个虚拟地址空间,确保在一个进程中使用的代码和数据无 ...
- Python数据分析方法与技巧
背景介绍 数据分析是数据科学领域的核心技能之一,它涉及到数据的收集.清洗.处理.分析和可视化. 数据分析是指通过收集.清洗.处理.分析和可视化数据来发现隐藏的模式.趋势和关系的过程. 数据分析是数据科 ...
- VUE商城项目 -权限功能 - 手稿
- input标签 手机端数字键盘
要一点击提起数字键盘,安卓只要设置input的类型是number或tel, ios 需要 pattern="number"可以直接打开搜狗输入法的数字键盘,可以输入.和数字如果只能 ...
- Python 生成条形码、二维码 (Code 128、EAN-13、QR code等)
条形码和二维码是现代信息交换和数据存储的重要工具,它们将信息以图形的形式编码,便于机器识别和数据处理,被广泛应用于物流.零售.医疗.教育等各领域.本文将介绍如何使用Python快速生成各种常见的条形码 ...
- [oeasy]python0050_动态类型_静态类型_编译_运行
动态类型_静态类型 回忆上次内容 上次了解了 帮助文档的 生成 开头的三引号注释 可以生成 帮助文档 文档 可以写成网页 python3 本身 也有 在线的帮助手册 目前的程序 提高了 可读性 ...
- PowerShell 使用 Azure
PowerShell 使用 Azure Azure 提供了三种管理工具: Azure 门户:Azure 门户是一个网站,可在其中创建.配置和更改 Azure 订阅中的资源,该门户是一个图形用户界面 ( ...