历届 CSP 刷题记录
J 组
注意到一点:每天卖出纪念品换回的金币可以立即用于购买纪念品,当日购买的纪念品也可以当日卖出换回金币。当然,一直持有纪念品也是可以的。
这告诉我们:在一天内,纪念品就是钱,钱就是纪念品,钱和纪念品没有本质区别,这满足动态规划对于最优化原理和无后效性的要求,可以大胆地购买。
所以可以做如下处理:
把今天手里的钱当做背包的容量
把商品今天的价格当成它的消耗
把商品明天的价格当做它的价值
每一天结束后把总钱数加上今天赚的钱,做
题目太过冗杂,转化一下就是:
给定一张无向图,边权都是
若递归来做直接 T 飞,其实使用无向图的一个小 tip 就可以快速想出正解。
重要 tip:无向图的每一条边都是一个长度为
所以先求出
注意到环的长度为
求出
S 组
江西
J 组
一道妥妥的中等模拟。
首先看到这种抽象的逻辑运算式子还要搞一些乱七八糟的询问,那多半都要建立一棵表达式树。
(其实建完树之后就已经做完了)
建完树之后结构清晰显然,树形 dp 即可。
用
写起来也不算太难,毕竟连我都两遍写过。
一眼 dp,先确定阶段,因为竖着可以上下走,横着只能往右走,所以将列数作为阶段,放在循环最外层。
但竖着有两个方向,不满足后效性怎么办?
想到了这道题,它也是有两个方向走。
借用这道题的思想,设置一个
设
接下来就可以写出状态转移方程:
最后的结果即为
发现计算
发现今天刚好是做这道题的一周年,但是挫折重重……
这道题需要处理的细节比较多:
-
第一列和最后一列都只能往下走,所以状态转移方程有些不同,要单独拎出来求;
-
只能从左径直走来或从下走过来,而其他列的第一个可以从左下走来或、左边径直走来或从下走来,所以应该这么写:
if(i > 2) dp[i][1][1] = max(dp[i - 1][1][0], dp[i - 1][1][1]) + a[1][i];
else dp[i][1][1] = dp[i - 1][1][1] + a[1][i];
而除第一列和最后一列之外每一列的最后一行都可以从左边径直走来、左上走来或从上走来,所以可以这么写:
dp[i][n][0] = max(dp[i - 1][n][0], dp[i - 1][n][1]) + a[n][i];
- 加滚动数组时要注意以下 hack 数据:
1 1
1
把最后一句改成:
printf("%lld", max((ll)a[m][n], max(dp[m & 1][n][0], dp[m & 1][n][1])));
即可。
(一年前的我不会这道题看题解过的)
S 组
好,把模拟放在
题面太过冗杂,先把题意梳理一下:
-
将公元前
年 月 日正午 点定为初始时间; -
在公元
年 月 日即以前都采用儒略历,即:若年份是公元后 年,则只要 ,则 就为闰年,否则为公元前 年,若 ,则 就为闰年; -
公元
年 月 日至 月 日不存在,要扣去,即 年 月 日的后一天为 年 月 日; -
从公元
年 月 日开始,改用格里高利历,即:当年份是 的倍数,或日期年份是 的倍数但不是 的倍数时,该年为闰年。 -
没有公元
年。
现在给定一个数
做这种问题考虑分段,先考虑采用儒略历的区间,在此基础上抠去不存在的那几天再考虑格里高利历。
先考虑用儒略历的时间区间。
不难发现在儒略日下,
注意:由于每月没有
计算儒略历部分的代码:
void solve1(ll x) {
int year = -4713;
year += x / per_4 * 4; //per_4 是 1461,表示四年的周期
x %= per_4;
//将剩下不满四年的年先过完
int flag = -1;
if(x > 365) x -= 365, year++, flag++;
if(x > 365 && !flag) x -= 365, year++, flag++;
if(x > 365 && flag == 1) x -= 365, year++, flag++;
if(flag == -1) x++; //不要忘记将闰年多的一天加回来
int month;
for(int i = 1; i <= 12; i++) {
int days = months[i];
if(i == 2 && flag == -1) days++;
if(x > days) x -= days;
else {
month = i;
break;
}
}
if(year < 0) printf("%d %d %d BC\n", x, month, -year);
else printf("%d %d %d\n", x, month, year + 1);
} //由于没有公元 0 年,所以年份需要 + 1
接着考虑后续时间区间。
用计算器算出公元前
计算格里高利历的代码:
void solve2(ll x) {
x -= to_gc;
if(x > 17) x -= 17;
else {
//没有过完 10 月
printf("%d %d %d\n", 14 + x, 10, 1582);
return ;
}
// 1582.11.1
int month;
bool flag = false;
for(int i = 11; i <= 12; i++) {
if(x > months[i]) x -= months[i];
else {
month = i;
flag = true;
break;
}
}
if(flag) {
//没有过完 1582 年
printf("%d %d %d\n", x, month, 1582);
return ;
}
// 1583.1.1
int year;
for(int i = 1583; i < 1600; i++) {
int days = check(i) ? 366 : 365;
if(x > days) x -= days;
else {
year = i;
flag = true;
break;
}
}
if(flag) {
//没有过完 1599 年
for(int i = 1; i <= 12; i++) {
int days = months[i];
if(i == 2 && check(year)) days++;
if(x > days) x -= days;
else {
month = i;
break;
}
}
printf("%d %d %d\n", x, month, year);
return ;
}
//1600.1.1
year = 1600;
year += x / per_400 * 400;
x %= per_400;
for(int i = year; i < year + 400; i++) {
int days = check(i) ? 366 : 365;
if(x > days) x -= days;
else {
year = i;
flag = true;
break;
}
}
for(int i = 1; i <= 12; i++) {
int days = months[i];
if(i == 2 && check(year)) days++;
if(x > days) x -= days;
else {
month = i;
break;
}
}
//若刚好整除 400 年的天数,那么应该是某年 12 月 31 日,需要倒推一天
if(!x) {
x = months[(month + 11) % 13];
month = (month + 11) % 13;
if(month == 12) year--;
}
printf("%d %d %d\n", x, month, year);
return ;
}
总的说来,我们将整个时间轴分成了以下四段:
; 年末; 年; 年以后。
注意:最后一个点只说了答案年份不超过
不得不说,
稍微想一想就能想出正解(第一遍做的时候虽然做对了,但是思路没理顺,用了两个 unordered_map 来做映射,效率较低)。
首先用
思考一下:一个动物要满足什么条件才能养?
- 有饲料供应了;
- 未被要求。
所以求出哪些二进制位是可以随意设置的即可,设这样的二进制位有
注意:
J 组
S 组
J 组
S 组
J 组
同余最短路入坑题。
分析题意,发现到达每个点的时间必定是
其实这种有关同余的题目见多之后,条件反射根据它来划分集合。具体地,因为
设
这道题最毒瘤的一点就是每条边都有时间限制,其实解决方法也很简单:如果在计算过程中发现当前时间小于这条路的开放时间,那么我们就晚一点出发,使得走到这里时刚好能够通过,形式化地:当走到点
这里有一个细节:虽然边权都是
最终答案就是
最后不要忘了无解输出
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 阿里最新开源QwQ-32B,效果媲美deepseek-r1满血版,部署成本又又又降低了!
· 单线程的Redis速度为什么快?
· SQL Server 2025 AI相关能力初探
· AI编程工具终极对决:字节Trae VS Cursor,谁才是开发者新宠?
· 展开说说关于C#中ORM框架的用法!