0%

csp 201712-2 游戏 暴力,附:约瑟夫环的递推

题目链接
题目描述:有一n点(500)m边(1e5)的图,有两种路,每个路有距离,走大路疲劳度+d(1e5),走小路疲劳度+连续走的小路长度的平方。求从1到n最小的疲劳度。(答案不超过1e6)

努力了一番终于满分了=w=
提醒大家在交任何题之前一定要从头到尾检查一遍数据范围会不会爆。
还有输出条件的一些限制细节。
另外对于csp这种不实时返回结果的题来说,spfa这种玄学复杂度的算法一定要能剪枝就剪枝。

核心思想:

1.如果全是大路可以直接dijkstra,看到求连续走的小路长度和很多人是不是懵了。但是解法很简单。假设现在准备走一条从i到j的小路,长度dij,i点原来的距离Di, 连续走的小路长度为Li,Di包含了Li^2,

因此Dj = Di - Li^2 + (Li+dij)^2或者可以把平方式展开,Dj = Di + dij^2 + 2dijLi(我用的这种)

所以使用spfa搜索时,需要记录的是三元组(id, len, lazy)(下个点标号,距离,当前连续小路长度),在转移时

    a)转移到大路,(nextid, len+dij, 0)

    b)转移到小路,(nextid, len+2*dij*li + dij^2, lazy+dij)

2.然后存图用vector数组,存(id, dis, type)(编号,路径长度,路径类型)。因为我使用了优先队列做spfa(会被大佬批评),然后自定义排序dis从小到大(cmp要反着写),这样只要访问到n点一定是答案。

3.优化和剪枝:

3.1采用类似dijkstra算法类似的visit数组,初始化为1e6。但是dijkstra的visit是用来防止多次入队的,SPFA用来松弛,我们这是用来剪枝的。如果一条大路一条小路到达同一个点路径长相等,选择大路走一定是对的。所以用大路更新visit最大值,如果某时刻路径长大于visit直接跳过。如果到达i点是一条大路,判断是否要更新visit,如果到达的是小路则不可以更新visit。把从当前点出发的三元组放进队列前,可以用visit和1e6剪枝,判断这个三元组是否应该直接跳过。此处有坑,因为dij的平方和dijLi可能会爆int,因此需要时刻先卡住1e6再卡住visit。

3.2看了看数据范围,发现不能保证图中没有自环和重边。自环要判断一下(不判断不会超时)。最开始我为了避免往queue里放太多元素导致插入排序超时或者爆内存,读完图以后给vector按dis从小到大排了个序,然后发现排序反而比不排慢,不要排了。虽然用map存每对点大路小路两条长度最小的边也是很好的,但是写着太麻烦了。

代码:(cmp的注释颜色很有趣)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
#include <cstdio>
#include <cstring>
#include <cmath>
#include <algorithm>
#include <queue>
#define pii pair<int, int>
#define piii pair<int, pii>
#define mp make_pair
#define pb push_back
using namespace std;
const int MAXN = 505;
vector<piii>path[MAXN];//point, dis, type;
struct cmp//queue用的cmp
{
bool operator () (piii a, piii b)
{
return a.second.first > b.second.first;
}

};
bool cmp0(piii a, piii b)//sort用的cmp0
{
return a.second.first < b.second.first;
}
priority_queue<piii, vector<piii >, cmp>q;//point, dis, lazy
int visit[MAXN];
piii mpp (int a, int b, int c)//pair<int, pair<int, int> >的make_pair函数
{
pii x = mp(b, c);
piii xx = mp(a, x);
return xx;
}

int main()
{
int n, m;
scanf("%d%d", &n, &m);
int a, b, s, t;
for(int i = 0; i < m; i++)
{
scanf("%d %d %d %d", &t, &a, &b, &s);
a--;//此题可用0存边
b--;
if(a == b)continue;//去掉自环
path[a].pb(mpp(b, s, t));
path[b].pb(mpp(a, s, t));
}
for(int i = 0; i < n; i++)
{
visit[i] = 1000001;
//sort(path[i].begin(), path[i].end(), cmp0);//优化
}
int nextp, nextd, nextt;
visit[0] = 0;
for(int i = 0; i < path[0].size(); i++)//第一次入队
{
nextp = path[0][i].first;
nextd = path[0][i].second.first;
nextt = path[0][i].second.second;
if(nextt == 0 && visit[nextp] > nextd){//visit剪枝+更新visit
q.push(mpp(nextp, nextd, 0));
visit[nextp] = nextd;
}
else if(nextt == 1 && nextd <= 1000 && visit[nextp] > nextd * nextd){ //爆1e6剪枝和visit剪枝
q.push(mpp(nextp, nextd*nextd, nextd));
}
}
while(!q.empty())//开始搜索
{
piii pnow = q.top();//当前访问点
q.pop();
int idx = pnow.first;
int lenx = pnow.second.first;
int lazx = pnow.second.second;
if(lenx > 1000000)continue;
if(lenx > visit[idx])continue;//visit剪枝
if(idx == n-1){//ans
printf("%d\n", pnow.second.first);
return 0;
}
for(int i = 0; i < path[idx].size(); i++)
{
nextp = path[idx][i].first;
nextd = path[idx][i].second.first;
nextt = path[idx][i].second.second;
if(nextt == 0){ //大路visit剪枝+更新+更新visit
if(visit[nextp] < nextd + lenx)continue;
q.push(mpp(nextp, nextd + lenx, 0));
visit[nextp] = nextd + lenx;
}
else if(nextt == 1){//小路判断爆1e6(注意爆int)+visit剪枝+更新
double sum = (double)nextd * (double)nextd + (double)lenx + 2 * (double)lazx * (double)nextd;
if(sum > 1000000)continue;
else if(visit[nextp] < nextd * nextd + lenx + 2 * lazx * nextd)continue;
q.push(mpp(nextp, nextd * nextd + lenx + 2 * lazx * nextd, lazx + nextd));
}
}
}
return 0;
}
-------------这么快就看完啦^ω^谢谢阅读哟-------------