第十二次CCF-CSP
第三题 Crontab
原题链接: 3254. Crontab - AcWing题库
思路分析
这道模拟题让我找不到思路…太菜了呜呜呜…
看了y总的讲解之后 --> 还能这样?原来如此!妙啊!妙极了!
下面我就从头开始分析一下y总的思路
首先肯定是输入数据,一个整型 n、两个字符串
接着输入n行要处理的数据:
每行分别是分钟minute、小时hour、月份中的天数day_of_month、月份month、星期几day_of_week、以及命令的名字name
输入完之后,需要对数据进行处理。也就是把字符串中的有效信息抽取出来!
详细看代码:
void work(string s, bool st[], int x) //处理输入的数据,更新它对应的bool[] 出现过就变为 1
{
if (s == "*") //如果是 *,也就是所有的范围都是允许出现的,st[] = 1
{
for (int i = 0; i < x; i++)
st[i] = 1;
}
else
{
//对于其他的情况(数字和字母、以及','和'-'),我们要把其中所表示的范围提取出来
for (int i = 0; i < s.size(); i++) //利用双指针算法
{
int j = i + 1;
while (j < s.size() && s[j] != ',') //找到第一个逗号(如果存在的话)
j++;
string str = s.substr(i, j - i);
i = j;
int k = str.find('-'); //判断逗号之间是否有'-'。
if (k != -1)
{
int l = get_num(str.substr(0, k));
int r = get_num(str.substr(k + 1));
for (int u = l; u <= r; u++)
st[u] = 1;
}
else
st[get_num(str)] = 1; //如果不存在'-',那么也就是str只有一个元素,直接把st[]=1
}
}
}
这里为了更方便的判断某一时刻(分钟)是否需要执行命令,这里y总利用bool数组,把上面的五个时间的约束条件都用bool[]表示出来,如果其中某一天(分钟/小时/…)满足条件,那么就把它赋值为1。这样一来,判断某一时刻是否需要执行命令,只需要看它们是否都为1! 方便起见,我们构建了一个结构体实现这个功能:
struct Task //处理任务
{
string name;
bool minutes[60], hours[24], day_of_months[32], months[13], day_of_week[7];
bool check(Timer &t)
{
return minutes[t.minute] && hours[t.hour] &&
day_of_months[t.day] && months[t.month] &&
day_of_week[t.week];
}
};
目前为止,我们已经实现了对输入的时间的提取、以及是否执行命令的判断的函数。
那么我们要怎么找到所有满足要求的时刻呢?很简单 遍历啊!
但是这里的时间的表示方法很抽象,所以我们需要构建一个结构体来具体的表示一下这里的时间。具体的操作就看代码吧~
struct Timer //处理时间
{
int year, month, week, minute, day,hour;
Timer(string str)
{
//sscanf 把字符串 str,格式化处理,分别存放到year,month,day,hour,minute
sscanf(str.c_str(), "%04d%02d%02d%02d%02d", &year, &month, &day, &hour, &minute);
}
bool operator < (const Timer& t) const
{
if (year != t.year)
return year < t.year; //意思是,按照年份从小到大排序
if (month != t.month)
return month < t.month;
if (day != t.day)
return day < t.day;
if (hour != t.hour)
return hour < t.hour;
return minute < t.minute;
}
string to_string()
{
char str[20];
sprintf(str, "%04d%02d%02d%02d%02d", year, month, day, hour, minute);
return str;
}
int is_leap()
{
if (year % 4 == 0 && year % 100 != 0 || year % 400 == 0)
{
return 1;
}
return 0;
}
int get_day()
{
if (month == 2)
return months[month] + is_leap();
return months[month];
}
void next() //要对日期进行一定的变换 以得到后一天的日期
{
if (++minute == 60)
{
minute = 0;
if (++hour == 24)
{
hour = 0;
week = (week + 1) % 7;
if (++day > get_day())
{
day = 1;
if (++month > 12)
{
month = 1;
year++;
}
}
}
}
}
};
好了,我们距离成功只差一点了! 模拟题会有很多细节,比如这道题规定月份和周几可以使用数字和英文缩写,且不区分大小写。所以我们想,不妨用哈希表的思想,建立一个字符串到数字的映射关系。
//映射的初始化
void init()
{
string keys[] = {
"jan","feb","mar","apr","may","jun", //1--12月
"jul","aug","sep","oct","nov","dec",
"sun","mon","tue","wed","thu","fri","sat" //周一到周日
};
int values[] = {
1,2,3,4,5,6,
7,8,9,10,11,12,
0,1,2,3,4,5,6
};
for (int i = 0; i < 19; i++)
nums[keys[i]] = values[i];
}
//处理数字和大小写
int get_num(string s)
{
if (s[0] >= '0' && s[0] <= '9') //如果s是数字
return stoi(s); //直接把字符串类型转换为int型
else //如果s是字符串
{
string str;
for (auto c : s)
{
str += tolower(c); //统一转换为小写字母
}
return nums[str]; //返回哈希表的映射(映射为数字)
}
}
最后就是遍历啦~
Timer time("197001010000"), Start(start), End(end); //定义了三个时间节点:初始化 开始时间 截止时间
time.week = 4; //1970年1月1日是周四
//下面按照时间 '遍历' 如果在规定时间之内的所有时刻(分钟),出现了满足输入数据所要求的情况,那么就把该时刻打印输出。
while (time < End) //需要重载 <
{
if (!(time < Start))
{
for (int i = 1; i <= n; i++)
{
if (task[i].check(time))
{
//需要手写 to_string()
cout << time.to_string() << " " << task[i].name << endl;
}
}
}
time.next(); //手写 next() 遍历时间
}
代码展示
#include<bits/stdc++.h>
#include<unordered_map>
using namespace std;
int n;
int months[13] = { //12 个月的天数
0,31,28,31,30,31,30,31,31,30,31,30,31
};
unordered_map<string, int> nums; //做一个从字符串到整形的映射
struct Timer //处理时间
{
int year, month, week, minute, day,hour;
Timer(string str)
{
//sscanf 把字符串 str,格式化处理,分别存放到year,month,day,hour,minute
sscanf(str.c_str(), "%04d%02d%02d%02d%02d", &year, &month, &day, &hour, &minute);
}
bool operator < (const Timer& t) const
{
if (year != t.year)
return year < t.year; //意思是,按照年份从小到大排序
if (month != t.month)
return month < t.month;
if (day != t.day)
return day < t.day;
if (hour != t.hour)
return hour < t.hour;
return minute < t.minute;
}
string to_string()
{
char str[20];
sprintf(str, "%04d%02d%02d%02d%02d", year, month, day, hour, minute);
return str;
}
int is_leap()
{
if (year % 4 == 0 && year % 100 != 0 || year % 400 == 0)
{
return 1;
}
return 0;
}
int get_day()
{
if (month == 2)
return months[month] + is_leap();
return months[month];
}
void next() //要对日期进行一定的变换 以得到后一天的日期
{
if (++minute == 60)
{
minute = 0;
if (++hour == 24)
{
hour = 0;
week = (week + 1) % 7;
if (++day > get_day())
{
day = 1;
if (++month > 12)
{
month = 1;
year++;
}
}
}
}
}
};
struct Task //处理任务
{
string name;
bool minutes[60], hours[24], day_of_months[32], months[13], day_of_week[7];
bool check(Timer &t)
{
return minutes[t.minute] && hours[t.hour] &&
day_of_months[t.day] && months[t.month] &&
day_of_week[t.week];
}
};
void init()
{
string keys[] = {
"jan","feb","mar","apr","may","jun", //1--12月
"jul","aug","sep","oct","nov","dec",
"sun","mon","tue","wed","thu","fri","sat" //周一到周日
};
int values[] = {
1,2,3,4,5,6,
7,8,9,10,11,12,
0,1,2,3,4,5,6
};
for (int i = 0; i < 19; i++)
nums[keys[i]] = values[i];
}
int get_num(string s)
{
if (s[0] >= '0' && s[0] <= '9') //如果s是数字
return stoi(s); //直接把字符串类型转换为int型
else //如果s是字符串
{
string str;
for (auto c : s)
{
str += tolower(c); //统一转换为小写字母
}
return nums[str]; //返回哈希表的映射(映射为数字)
}
}
void work(string s, bool st[], int x) //处理输入的数据,更新它对应的bool[] 出现过就变为 1
{
if (s == "*") //如果是 *,也就是所有的范围都是允许出现的,st[] = 1
{
for (int i = 0; i < x; i++)
st[i] = 1;
}
else
{
//对于其他的情况(数字和字母、以及','和'-'),我们要把其中所表示的范围提取出来
for (int i = 0; i < s.size(); i++) //利用双指针算法
{
int j = i + 1;
while (j < s.size() && s[j] != ',') //找到第一个逗号(如果存在的话)
j++;
string str = s.substr(i, j - i);
i = j;
int k = str.find('-'); //判断逗号之间是否有'-'。
if (k != -1)
{
int l = get_num(str.substr(0, k));
int r = get_num(str.substr(k + 1));
for (int u = l; u <= r; u++)
st[u] = 1;
}
else
st[get_num(str)] = 1; //如果不存在'-',那么也就是str只有一个元素,直接把st[]=1
}
}
}
Task task[100];
int main()
{
init(); //初始化,用来生成哈希表
string start, end;
cin >> n;
cin >> start >> end;
for (int i = 1; i <= n; i++)
{
string minute, hour, day_of_month, month, day_of_week,name;
cin >> minute >> hour >> day_of_month >> month >> day_of_week>>name;
work(minute, task[i].minutes, 60);
work(hour, task[i].hours, 24);
work(day_of_month, task[i].day_of_months, 32);
work(month, task[i].months, 13);
work(day_of_week, task[i].day_of_week, 7);
task[i].name = name;
}
Timer time("197001010000"), Start(start), End(end); //定义了三个时间节点:初始化 开始时间 截止时间
time.week = 4; //1970年1月1日是周四
//下面按照时间 '遍历' 如果在规定时间之内的所有时刻(分钟),出现了满足输入数据所要求的情况,那么就把该时刻打印输出。
while (time < End) //需要重载 <
{
if (!(time < Start))
{
for (int i = 1; i <= n; i++)
{
if (task[i].check(time))
{
//需要手写 to_string()
cout << time.to_string() << " " << task[i].name << endl;
}
}
}
time.next(); //手写 next() 遍历时间
}
return 0;
}
总结反思
通过这道大模拟题,我学到了很多巧妙的方法。
-
stoi(s)可以把字符串 s 直接转换为整数! -
tolower(c)可以把字符 c 转为小写 -
toupper(c)把字符 c 转为大写 -
结构体构建的巧妙!!!
-
bool数组的巧妙利用!!!
-
哈希表的映射!!!
-
…
y总nb!!!
第四题 行车路线
思路分析
这道题的意思是说,有 n 个节点, m 条边,每条边的权重(长度)为 x,有小路和大路两种边。其中大路边权给多少就是多少,但是小路的边权是连续小路的长度之和的平方! 好像这样说也并没有很清楚… 总之意思就是这个意思,在题目中是这样描述的:大道比较好走,每走 1 公里小明会增加 1 的疲劳度。小道不好走,如果连续走小道,小明的疲劳值会快速增加,连续走 s 公里小明会增加 s^2 的疲劳度。
所以不难发现,难点就在于小路的处理。
不过鉴于本人水平有限,且发现题目中说有部分的评测用例,不存在小道;故写出正常的dijkstra()算法,得了60分hhh
言归正传,正确的思路是什么呢?
观察数据范围不难发现,节点的个数很少,且保证答案不超过1e6! 这说明什么?说明连续的小路的长度不会超过1000公里! 所以我们将使用拆点的思想:
也就是说,我们要把原来的dist[i],从节点 1 到节点 i 的距离,变为dist[i][j] ,第一维还是表示从节点 1 到节点 i 的距离。 第二维 j 表示,最后一段路(通往i的那段)的小路的长度——如果是大路,则长度 j=0,如果是小路则长度j = 1,2,…,1000(因为小路的长度最大就是1000)
那么在更新dist[][] 时,就要这样:
//从点i更新临点k,当这段路是大路时:
dist[k][0] = min(dist[k][0],dist[i][0] + w[i]);
//当是小路时:y表示这段小路的长度(连续长度)
y += w[i]; //更新长度
dist[k][y] = min(dist[k][y],dist[i][j]-j^j + y^2); //原来的权重 - 上一个节点的连续的小路的权重,再加上更新的节点的连续小路的权重。
完整代码
#include<bits/stdc++.h>
using namespace std;
const int N = 1010, M = 2e5 + 5,INF=1e6;
int h[N], e[M], ne[M], t[M], w[M], idx;
int n, m;
int dist[N][1010];
bool st[N][1010];
struct node //重载
{
int x, y, v; //点的编号,最后一段小路的长度,1到x点的最短距离
bool operator < (const node& t) const //优先队列默认是大根堆,所以这样重载就是小根堆!
{
return v > t.v;
}
};
void add(int a, int b, int c, int d)
{
w[idx] = c;
t[idx] = d;
e[idx] = b;
ne[idx] = h[a];
h[a] = idx++;
}
void dijkstra()
{
memset(dist, 0x3f, sizeof dist);
priority_queue<node> q;
dist[1][0] = 0;
q.push({ 1,0,0 });
while (!q.empty())
{
auto tmp = q.top();
q.pop();
if (st[tmp.x][tmp.y])
continue;
st[tmp.x][tmp.y] = true;
for (int i = h[tmp.x]; ~i; i = ne[i])
{
int j = e[i];
if (t[i] == 1) //小路
{
int y = tmp.y, v = tmp.v;
y += w[i];
if(y<=1000) //注意这个条件!!! 可以剪枝!!!
if (dist[j][y] > v - tmp.y * tmp.y + y * y)
{
dist[j][y] = v - tmp.y * tmp.y + y * y;
// if(dist[j][y]<=INF)
q.push({ j,y,dist[j][y] });
}
}
else //大路
{
if (dist[j][0] > tmp.v + w[i])
{
dist[j][0] = tmp.v + w[i];
// if(dist[j][0]<=INF)
q.push({ j,0,dist[j][0] });
}
}
}
}
}
int main()
{
memset(h, -1, sizeof h);
cin >> n >> m;
for (int i = 1; i <= m; i++)
{
int a, b, c, d;
cin >> d >> a >> b >> c;
add(a, b, c, d);
add(b, a, c, d); //无向边!!!
}
dijkstra();
int ans = INF;
for (int i = 0; i <= 1010; i++)
{
ans = min(dist[n][i], ans);
}
cout << ans;
return 0;
}
反思总结:
对于优先队列,不能只会用它默认的形式,还有会把它和结构体结合起来。
下面代码的意思是,构造一个优先队列 q ,其中按照 z 值的大小,从小到大排序!!!
因为优先队列默认是大根堆!所以重载 < 时,return > 才会是从小到大排序!(这点和sort相反)
struct node
{
int x,y,z;
bool operator< (const node &t)const
{
return z > t.z;
}
}
priority_queue<node> q;
60分代码:
#include<bits/stdc++.h>
using namespace std;
const int N = 1010, M = 2e5 + 5;
int n, m;
int h[N], e[M], ne[M], w[M], t[M],idx;
queue<int> q;
int dist[N];
void add(int a, int b, int c, int d)
{
w[idx] = c;
t[idx] = d;
e[idx] = b;
ne[idx] = h[a];
h[a] = idx++;
}
void bfs(int x)
{
memset(dist, 0x3f, sizeof dist);
q.push(x);
dist[x] = 0;
while (!q.empty())
{
int tmp = q.front();
q.pop();
for (int i = h[tmp]; ~i; i = ne[i])
{
int j = e[i];
int v = 0;
if (t[i] == 1)
v = w[i] * w[i];
else
v = w[i];
if (dist[j] > dist[tmp] + v)
{
dist[j] = dist[tmp] + v;
q.push(j);
}
}
}
}
int main()
{
cin >> n >> m;
memset(h, -1, sizeof h);
for (int i = 1; i <= m; i++)
{
int a, b, c, d;
cin >> d >> a >> b >> c;
add(a, b, c, d);
add(b, a, c, d);
}
bfs(1);
// for (int i = 1; i <= n; i++)
// {
// cout << dist[i] << " ";
// }
cout<<dist[n];
return 0;
}

2209

被折叠的 条评论
为什么被折叠?



