codeforces786B Legacy 线段树优化建图
题目大意:给出n个点,1~n编号,给出m个建边操作,以如下方式建边:(a,b),(c,d)表示,对于任意两个点x,y,如果a<=x<=b,c<=y<=d,那么在xy之间连边。给定源点求单源最短路
n<=500000,m<=100000
边1表示权值为1的边,边0表示权值为0的边
最暴力的建图需要 n2m 条边……
题解:这道题目如果老老实实的去建图的话,边会多到爆炸。所以我们需要另外想一个建图方法。
由于边的起点或者终点涉及到了区间的情况,我们可以使用线段树建图的套路。
对于整段区间,我们用把它划分成一颗线段树。
对于整个图,我们可以构造两颗线段树A和B。
在A线段树上,我们从底部往上连权值为0的边。对于B线段树来说我们从顶部往下连边权值为0的边。
然后我们把B线段树底部的所有点向A线段树底部的点连权值为0得边。
对于type=1的边,我们从A线段树底部的点向B线段树底部的点做权值为w的边。
对于type=2的边,我们从A线段树底部的点向B线段树对应的区间段连权值为w的一些边。
对于type=3的边,我们从A线段树对应的区间段向B线段树底部对应的点连权值为w的一些边。
然后跑最短路就行了。
花个丑图。。。。
如果我们把区间拆成点来加边,那么复杂度显然会高达O(n^2logn)的。所以显然不能这样来加边,这里给了一堆线段?我们可以想怎么把线段和点连接起来呢?显然线段树可以轻易的办到,由于是有向边所以这里需要两颗线段树,线段树的节点是2*n的,2颗就是4*n,j加上原来的n个点,所以节点数是5n。然后我们加边原来最坏的O(n),现在可以变成logn了,那么我们的复杂度可以 O(nlognlogn)了。
可以用两个辅助点来表示一次建边操作,两个辅助点分别表示(a,b)->(c,d)/(c,d)->(a,b)。对于(a,b)->(c,d)来说,(a,b)内的点向辅助点连边1,辅助点向(c,d)内的点连边0。
可是这样做仍然需要
nm
条边。
区间加边想到线段树优化建图
线段树的每个节点代表一个区间,一棵线段树表示不了进出关系,所以建两棵线段树。
出线段树每个点向父节点连边0,表示如果能从这个区间出发也就可以从父区间出发。
入线段树每个点向子节点连边0,表示如果能到达这个区间也就可以到达子区间。
入线段树每个点向出线段树的平行结点连边0,表示如果能到达这个区间也可以从这个区间出发。
然后每个操作在线段树上连一连就好了..
最后答案即为出线段树的每个叶子的最短路
然一开始要把A、B树的叶子节点记录下来方便后面加边使用
边数最坏情况应该是每一层左右均取两个区间,一直递归下去,有 层,因此边数大约是
#include <iostream>
#include <cstring>
#include <cstdio>
#include <vector>
#include <queue>
#include <algorithm>
using namespace std;
typedef long long ll;
const ll inf = 0x3f3f3f3f3f3f3f3fLL;
const int maxn = 1e5 + 7;
const int maxv = maxn*5;
int n, m, s;
vector< pair<int, ll> > vt[maxv]; //很难计算边数的话, 用vec很好, 一共5n个点, 2个线段树(2*n)+n个点
int id[2][maxn<<2], idx;
ll dis[maxv];
bool vis[maxv];
struct node
{
int to;
ll w;
node(){}
node(int tt, ll ww) : to(tt), w(ww) {}
bool operator < (const node &a) const
{
return w > a.w;
}
};
void addedge(int u, int v, ll w)
{
vt[u].push_back(make_pair(v, w));
}
void build(int rt, int l, int r, int type)
{
id[type][rt] = ++idx; //给线段树每个点的编号
if(l == r)
{
if(type == 0) addedge(id[type][rt], l, 0); //通过这n个点,把两颗线段树的叶子节点连通起来
else addedge(l, id[type][rt], 0);
return ;
}
int mid = (l+r)>>1;
build(rt<<1, l, mid, type);
build(rt<<1|1, mid+1, r, type);
if(type == 0)
{
addedge(id[type][rt], id[type][rt<<1], 0); //入树,从上往下连边
addedge(id[type][rt], id[type][rt<<1|1], 0);
}
else
{
addedge(id[type][rt<<1], id[type][rt], 0); //出树, 从下往上连边权为0的
addedge(id[type][rt<<1|1], id[type][rt], 0);
}
}
void update(int rt, int l, int r, int i, int j, int type, int u, ll cost)
{
if(i <= l && r <= j)
{
if(type == 0) //如果是 u -> [l,r] 就是出树的叶子(通过1-n个辅助点)连向入树
addedge(u, id[type][rt], cost);
else
addedge(id[type][rt], u, cost); //反之,反之
return;
}
int mid = (l+r)>>1;
if(i <= mid) update(rt<<1, l, mid, i, j, type, u, cost);
if(j > mid) update(rt<<1|1, mid+1, r, i, j, type, u, cost);
}
void dij(int s) //边很多用dir+heap优化很快的
{
for(int i = 1; i <= 5*n; i++) vis[i] = 0, dis[i] = inf;
dis[s] = 0;
priority_queue<node> q;
q.push(node(s, 0));
// priority_queue<pair<ll, int> > q;
// q.push({0, s});
while(!q.empty())
{
int u = q.top().to;
// int u = q.top().second;
q.pop();
if(vis[u]) continue;
vis[u] = 1;
for(int i = 0; i < vt[u].size(); i++)
{
int to = vt[u][i].first;
int w = vt[u][i].second;
if(dis[to] > dis[u] + w)
{
dis[to] = dis[u] + w;
q.push(node(to, dis[to]));
// q.push(make_pair(-dis[to], to)); //pair优化的话注意这样。。
}
}
}
}
int main()
{
while(~scanf("%d%d%d", &n, &m, &s))
{
for(int i = 0; i <= 5*n; i++) vt[i].clear();
memset(id, 0, sizeof(id));
idx = n;
build(1, 1, n, 0);
build(1, 1, n, 1);
while(m--)
{
int cmd, u;
scanf("%d%d", &cmd, &u);
if(cmd == 1)
{
int v;
ll cost;
scanf("%d%lld", &v, &cost);
addedge(u, v, cost); //直接给两颗线段树借助的点连起来就好了,等于给两颗线段树叶子直接连起来
}
else
{
int l, r;
ll cost;
scanf("%d%d%lld", &l, &r, &cost);
update(1, 1, n, l, r, cmd-2, u, cost);
}
}
dij(s);
for(int i = 1; i <= n; i++)
{
if(dis[i] == inf) dis[i] = -1;
printf("%lld%c", dis[i], i == n ? '\n' : ' ');
}
}
return 0;
}
/*
10 8 7
1 10 7 366692903
1 4 8 920363557
2 7 5 10 423509459
2 2 5 7 431247033
2 7 3 5 288617239
2 7 3 3 175870925
3 9 3 8 651538651
3 4 2 5 826387883
*/
BZOJ 2763: [JLOI2011]飞行路线 分层图最短路
题意:给一些无向图, 求从s到t的最短路, 其中有k次可以免费(减少一半,等等条件);
思路: 分层图最短路就好了,代码很易懂
原图既然是一层的。
我们把它拆成k+1层。
每一条边既能连本层,也能连到下一层。
然后直接裸上Dijikstra即可。
代码1:最裸的分层图最短路,真的是分层图
#include <iostream>
#include <cstring>
#include <cstdio>
#include <algorithm>
#include <queue>
#include <vector>
using namespace std;
const int INF = 1e9;
const int maxn = 1200100;
int n, m, k, s, t, cnt, edgcnt;
int head[maxn], dis[maxn], book[maxn];
struct node
{
int to, val, next;
}edge[maxn<<1];
struct Point
{
int val, to;
Point(){}
Point(int tt, int vv) : to(tt), val(vv){}
bool operator < (const Point &a) const
{
return val > a.val;
}
};
void addEdge(int u, int v, int w)
{
edge[edgcnt].to = v;
edge[edgcnt].val = w;
edge[edgcnt].next = head[u];
head[u] = edgcnt++;
}
void init()
{
edgcnt = 0;
memset(head, -1, sizeof(head));
}
void dij()
{
for(int i = 0; i < maxn; i++) dis[i] = INF;
memset(book, 0, sizeof(book));
priority_queue<Point> q;
q.push(Point(s, 0));
dis[s] = 0;
while(!q.empty())
{
int u = q.top().to;
q.pop();
if(book[u]) continue;
book[u] = 1;
for(int i = head[u]; i != -1; i = edge[i].next)
{
int to = edge[i].to;
int w = edge[i].val;
if(dis[u] + w < dis[to])
{
dis[to] = dis[u] + w;
q.push(Point(to, dis[to]));
}
}
}
int ans = 0x3f3f3f3f;
for(int i = 0; i <= k; i++)
ans = min(ans, dis[t+i*n]);
printf("%d\n", ans);
}
int main()
{
while(~scanf("%d%d%d", &n, &m, &k))
{
scanf("%d%d", &s, &t);
init();
s++, t++;
while(m--)
{
int x, y, z;
scanf("%d%d%d", &x, &y, &z);
x++, y++;
for(int i = 0; i <= k; i++)
{
addEdge(x+i*n, y+i*n, z);
addEdge(y+i*n, x+i*n, z);
if(i != k)
{
addEdge(x+i*n, y+(i+1)*n, 0);
addEdge(y+i*n, x+(i+1)*n, 0);
}
}
}
dij();
}
return 0;
}
上面那个 建了k+1层,其实只建一层就好了,跟dp一样
dis[i][j]->dis[t][j]+e[i][t]
dis[i][j]->dis[t][j+1] if j
#include <iostream>
#include <cstring>
#include <cstdio>
#include <algorithm>
#include <queue>
#include <vector>
using namespace std;
const int maxn = 1e4 + 5;
const int maxm = 5e4 + 5;
int n, m, k, s, t, cnt, edgcnt;
int head[maxn], dis[20][maxn], book[20][maxn];
struct node
{
int to, val, next;
}edge[maxm<<1];
struct Point
{
int val, to, num;
Point(){}
Point(int tt, int vv, int nn) : to(tt), val(vv), num(nn){}
bool operator < (const Point &a) const
{
return val > a.val;
}
};
void addEdge(int u, int v, int w)
{
edge[edgcnt].to = v;
edge[edgcnt].val = w;
edge[edgcnt].next = head[u];
head[u] = edgcnt++;
}
void init()
{
edgcnt = 0;
memset(head, -1, sizeof(head));
}
void dij()
{
memset(dis, 0x3f3f3f, sizeof(dis));
memset(book, 0, sizeof(book));
priority_queue<Point> q;
q.push(Point(s, 0, 0));
dis[0][s] = 0;
while(!q.empty())
{
Point p = q.top();
q.pop();
int u = p.to, d = p.val, num = p.num;
if(book[num][u]) continue;
book[num][u] = 1;
if(u == t)
{
printf("%d\n", d);
return;
}
for(int i = head[u]; i != -1; i = edge[i].next)
{
int to = edge[i].to;
int w = edge[i].val;
if(dis[num][u] + w < dis[num][to])
{
dis[num][to] = dis[num][u] + w;
q.push(Point(to, dis[num][to], num));
}
if(num == k) continue;
if(dis[num+1][to] > dis[num][u]) //如果是减少一半就世界+w/2就好了
{
dis[num+1][to] = dis[num][u];
q.push(Point(to, dis[num+1][to], num+1));
}
}
}
// printf("%d\n", dis[k][t]);
}
int main()
{
while(~scanf("%d%d%d", &n, &m, &k))
{
scanf("%d%d", &s, &t);
init();
while(m--)
{
int x, y, z;
scanf("%d%d%d", &x, &y, &z);
addEdge(x, y, z);
addEdge(y, x, z);
}
dij();
}
return 0;
}
HDU 5669 Road(线段树建树(区间连区间))(分层图最短路)
题意:给一个n个点的图,标号为1到n,进行m次连边(a,b,c,d,w), 无向图:
有K次机会可以消除一条边的权值(即走过但不算),问1到n的最短路。
n≤5×104,m≤104,0≤K≤10,w≤103
思路:
我们有两颗线段树,一颗连出去,一颗连入,分别称为出线段树和入线段树。出线段树的子节点连到父亲,入线段树的父亲连到子节点。然而如果只是这样的话,我们只能走一条边——从入线段树的某个点走到出线段树的点之后就回不来了!
所以我们把入线段树的每个点连到出线段树的相同位置的点即可。
每次连边是否需要 条边呢?其实不需要,我们可以对每次连边建一个中间节点,出入线段树分别连出,从这个点连入即可。
这样总点数是 ,总边数最大为 ,所以使用优先队列优化Dijkstra求最短路,总复杂度为 。
#include <iostream>
#include <cstring>
#include <cstdio>
#include <vector>
#include <queue>
#include <algorithm>
using namespace std;
typedef long long ll;
const ll inf = 0x3f3f3f3f3f3f3f3fLL;
const int maxn = 1e5 + 7;
const int maxv = maxn*5;
int n, m, s, k;
vector< pair<int, ll> > vt[maxv];
int id[2][maxn<<2], idx;
ll dis[20][maxv];
bool book[20][maxv];
struct node
{
int to, num;
ll w;
node(){}
node(int tt, ll ww, int nn) : to(tt), w(ww) , num(nn){}
bool operator < (const node &a) const
{
return w > a.w;
}
};
void addedge(int u, int v, ll w)
{
vt[u].push_back(make_pair(v, w));
}
void build(int rt, int l, int r, int type)
{
id[type][rt] = ++idx;
if(l == r)
{
if(type == 0) addedge(id[type][rt], l, 0);
else addedge(l, id[type][rt], 0);
return ;
}
int mid = (l+r)>>1;
build(rt<<1, l, mid, type);
build(rt<<1|1, mid+1, r, type);
if(type == 0) //入树
{
addedge(id[type][rt], id[type][rt<<1], 0);
addedge(id[type][rt], id[type][rt<<1|1], 0);
}
else //出树
{
addedge(id[type][rt<<1], id[type][rt], 0);
addedge(id[type][rt<<1|1], id[type][rt], 0);
}
}
void updatel(int rt, int l, int r, int i, int j, ll cost) //出树,这两个一定要分开,咱是没想到合并的方法
{
if(i <= l && r <= j)
{
addedge(id[1][rt], idx, cost); //idx是辅助节点, 通过辅助节点连边, 出树权值为cost
return;
}
int mid = (l+r)>>1;
if(i <= mid) updatel(rt<<1, l, mid, i, j, cost);
if(j > mid) updatel(rt<<1|1, mid+1, r, i, j, cost);
}
void updater(int rt, int l, int r, int i, int j, ll cost) //入树
{
if(i <= l && r <= j)
{
addedge(idx, id[0][rt], 0); //辅助节点连入树相应节点, 入树权值0
return;
}
int mid = (l+r)>>1;
if(i <= mid) updater(rt<<1, l, mid, i, j, cost);
if(j > mid) updater(rt<<1|1, mid+1, r, i, j, cost);
}
void dij() //分层图最短路
{
memset(book, 0, sizeof(book));
memset(dis, inf, sizeof(dis));
dis[0][1] = 0;
priority_queue<node> q;
q.push(node(1, 0, 0));
while(!q.empty())
{
node p = q.top();
q.pop();
int u = p.to, num = p.num;
ll d = p.w;
if(book[num][u]) continue;
book[num][u] = 1;
for(int i = 0; i < vt[u].size(); i++)
{
int to = vt[u][i].first;
int w = vt[u][i].second;
if(dis[num][to] > dis[num][u] + w)
{
dis[num][to] = dis[num][u] + w;
q.push(node(to, dis[num][to], num));
}
if(num < k && dis[num+1][to] > dis[num][u])
{
dis[num+1][to] = dis[num][u];
q.push(node(to, dis[num+1][to], num+1));
}
}
}
}
int main()
{
int _;
cin >> _;
while(_--)
{
for(int i = 0; i < maxn; i++)
vt[i].clear();
scanf("%d%d%d", &n, &m, &k);
idx = n; //初始n,代表1-n
build(1, 1, n, 0);
build(1, 1, n, 1);
while(m--)
{
idx++;
int l1, l2, r1, r2;
ll cost;
scanf("%d%d%d%d%lld", &l1, &r1, &l2, &r2, &cost);
updatel(1, 1, n, l1, r1, cost); //无向图, 所以两次
updater(1, 1, n, l2, r2, 0);
idx++; //要新建辅助节点
updatel(1, 1, n, l2, r2, cost);
updater(1, 1, n, l1, r1, 0);
}
dij();
if(dis[k][n] == inf)
puts("CreationAugust is a sb!");
else
printf("%lld\n", dis[k][n]);
}
return 0;
}
POJ 3635 Full Tank? 无向图最短路+类分层图DP
题意:给出一张图,n<=1000,m<=10000. 有一辆车想从图的一个地方到达另外一个地方,每个点是一个卖油的地方,每个地方买的有价格不一样,车的最大装油量是c,求初始点到终止点的最小花费。
思路:
1.一看到d[i][j]这样的状态表示,马上想到分层图最短路,有点类似DP的思想,按油量分层,d[i][j]表示到节点i还有j个油的最小花费(不是最短路),两种决策,加一个油或者直接走,感觉用Dijkstra写比较好注意状态判重,多判一次也无所谓了
3.最直接的想法是 每到一个点都加上要走到下一个点所需要的油量。但是走的路不同,到底怎么处理加多少的问题呢?
因此想到分解状态,即拆点。每到一个点都+1单位的油量,然后把这个状态加入队列。另外如果现在油箱内的油足够达到下一点,则更新状态,把新状态加入优先队列。dp[i][j]表示到第i个城市剩余油量为j的花费。
#include <iostream>
#include <cstdio>
#include <cstring>
#include <queue>
#include <vector>
#include <algorithm>
using namespace std;
const int maxn = 1e3 + 5;
const int maxe = 7e4 + 5;
int p[maxn], c, n, m, x, y, z, edgecnt;
int dp[maxn][105], book[maxn][105], head[maxn];
struct Point
{
int to, w, next;
}edge[maxe];
struct node
{
int to, w, num;
node(){}
node(int tt, int ww, int nn) : to(tt), w(ww), num(nn){}
bool operator < (const node &a) const
{
return w > a.w;
}
};
void addedge(int u, int v, int w)
{
edge[edgecnt].to = v;
edge[edgecnt].w = w;
edge[edgecnt].next = head[u];
head[u] = edgecnt++;
}
void init()
{
edgecnt = 0;
memset(head, -1, sizeof(head));
}
void dij(int s, int t)
{
priority_queue<node> q;
memset(dp, 0x3f3f3f3f, sizeof(dp));
memset(book, 0, sizeof(book));
dp[s][0] = 0;
q.push(node(s, 0, 0));
while(!q.empty())
{
node tmp = q.top();
q.pop();
int u = tmp.to, val = tmp.w, num = tmp.num;
if(u == t)
{
printf("%d\n", val); //这一定是最近的
return;
}
if(book[u][num]) continue;
book[u][num] = 1;
for(int i = head[u]; i != -1; i = edge[i].next)
{
int to = edge[i].to;
int w = edge[i].w;
if(num < c && dp[u][num+1] > dp[u][num] + p[u]) // 留在原地加油,相当于到了下一层图了,只不过这个题是留在原点的下层
{
dp[u][num+1] = dp[u][num] + p[u];
q.push(node(u, dp[u][num+1], num+1));
}
if(num >= w && dp[to][num-w] > dp[u][num]) //在这一层到另一个点,主意要判断num >= w
{
dp[to][num-w] = dp[u][num];
q.push(node(to, dp[to][num-w], num-w));
}
}
}
printf("impossible\n");
return;
}
int main()
{
while(~scanf("%d%d", &n, &m))
{
init();
for(int i = 0; i < n; i++)
scanf("%d", &p[i]);
while(m--)
{
scanf("%d%d%d", &x, &y, &z);
addedge(x, y, z);
addedge(y, x, z);
}
int q, s, t;
scanf("%d", &q);
while(q--)
{
scanf("%d%d%d", &c, &s, &t);
dij(s, t);
}
}
return 0;
}
POJ 3249 Test for Job(拓扑排序+dp)
给出一张有向图,每个点有点权,现要从一条入度为0的点到一个出度为0的点,问最大点权和 (有负权)
(1<=n<=1000000,0<=m<=1000000)
思路:很直观的思路就是 建立超级源超级汇跑一边 spfa就好了,因为有负权,只能spfa,所以这题很容易就被卡死
最坏的情况下,n次spfa,也就是说O(n^2),绝对超时。例如:10000个点(入度为0)都指向一条长度为90000的链的首端,spfa做,
需要9*10^8次。
因为是dag,所以拓扑排序,在DAG上做DP就好了, 复杂度om吧 每条边都被访问一次
#include <iostream>
#include <cstdio>
#include <cstring>
#include <algorithm>
#include <queue>
using namespace std;
const int maxn = 1e5 + 7;
const int maxe = 1e6 + 7;
typedef long long ll;
const ll inf = 1e18;
int n, m, in[maxn], out[maxn], edgecnt, head[maxn];
ll dp[maxn], val[maxn];
struct node
{
int to, next;
}edge[maxe];
void addedge(int u, int v)
{
edge[edgecnt].to = v;
edge[edgecnt].next = head[u];
head[u] = edgecnt++;
}
int Q[maxn];
void top()
{
int rear = 0, frnt = 0;
for(int i = 1; i <= n; i++)
{
dp[i] = -inf;
if(!in[i])
Q[rear++] = i, dp[i] = val[i];
}
while(frnt != rear)
{
int u = Q[frnt++];
for(int i = head[u]; i != -1; i = edge[i].next)
{
int to = edge[i].to;
dp[to] = max(dp[u] + val[to], dp[to]);
in[to]--;
if(!in[to]) Q[rear++] = to;
}
}
}
int main()
{
while(~scanf("%d%d", &n, &m))
{
edgecnt = 0;
memset(head, -1, sizeof(head));
memset(in, 0, sizeof(in));
memset(out, 0, sizeof(out));
for(int i = 1; i <= n; i++)
scanf("%lld", &val[i]);
int x, y;
while(m--)
{
scanf("%d%d", &x, &y);
addedge(x, y);
in[y]++;
out[x]++;
}
top();
ll ans = -inf;
for(int i = 1; i <= n; i++)
{
if(!out[i])
ans = max(ans, dp[i]);
}
printf("%lld\n", ans);
}
return 0;
}
codeforce 721C Journey (DAG上的dp+记录路径)
题目大意:
有一个dag图,代表一个城市。城市里有若干景点,以及连接景点的单向道路,每条道路都有一个通过的时间ti。你从景点1出发,到景点n,在有限的时间T内,至多能游览几个景点?
思路: DAG一看就跑拓扑。。首先很像dp,就想怎么构造dp呢, 这题的状态一共3个,当前的城市, 当前走了几个, 当前的时间花了多少, 时间一看1e9,知道是前两个作为下标, 也就是dp[i][j]代表当前在第i个城市, 走了j个城市了的最小时间花费。。转移也比较简单 dp[to][j] = dp[u][j-1] + w, 只不过是在DAG上做dp, 一开始写了个裸的dfs(真的zz),t了, 其实拓扑一下就好了, 记得一开始要把所有点都扔进去, 我一开始只扔了1,wa在63,白痴错误。。只把初始状态 dp[1][1] = 0就好了,记录路径比较好, path[to][j]代表到达这个状态的上一个点是什么,j肯定-1就好了, 所以只记录哪个点到达这个状态就好了, 然后倒着找dp[n][j]的最大的j,就是最优状态的最后一个,倒着往前推就好了
#include <iostream>
#include <cstring>
#include <cstdio>
#include <queue>
#include <vector>
#include <algorithm>
using namespace std;
typedef long long ll;
const ll inf = 1e18;
const int maxn = 5e3 + 5;
short path[maxn][maxn], sta[maxn], Q[maxn], deg[maxn], n;
ll val, dp[maxn][maxn];
struct node
{
int v;
ll w;
node(){}
node(int vv, ll ww) : v(vv), w(ww){}
};
vector<node> vt[maxn];
void Top()
{
int frnt, rear;
frnt = rear = 0;
for(int i = 1; i <= n; i++)
if(!deg[i])
Q[rear++] = i;
while(frnt != rear)
{
int u = Q[frnt++];
for(int i = 0; i < vt[u].size(); i++)
{
int to = vt[u][i].v;
ll w = vt[u][i].w;
if(--deg[to] == 0) Q[rear++] = to;
for(int j = 2; j <= n; j++)
{
if(dp[to][j] > dp[u][j-1] + w)
{
dp[to][j] = dp[u][j-1] + w;
path[to][j] = u;
}
}
}
}
}
int main()
{
int m, t, u, v;
for(int i = 0; i < maxn; i++)
for(int j = 0; j < maxn; j++)
dp[i][j] = inf;
scanf("%d%d%d", &n, &m, &t);
for(int i = 1; i <= m; i++)
{
scanf("%d%d%lld", &u, &v, &val);
deg[v]++;
vt[u].push_back(node(v, val));
}
dp[1][1] = 0;
Top();
int pos = 1, top = 0, index = n;
for(int i = n; i >= 1; i--)
if(dp[n][i] <= t)
{
pos = i;
break;
}
printf("%d\n", pos);
sta[top++] = n;
while(pos >= 2)
{
sta[top++] = path[index][pos];
index = path[index][pos--];
}
for(int i = top-1; i >= 0; i--)
printf("%d%c", sta[i], i == 0 ? '\n' : ' ');
return 0;
}
51nod 1693
思路:
做法1:
总结:
在连接i与i*k的边时,只连接k为质数时的点。很容易发现这样是正确的
这也给我们打开了思路,以后遇到乘法的,可以通过质数拆开。
然后是第二个优化,竟然最后k只用到了2,3,5,7,11,13这几个质数。所以被卡题时可以去尝试,看能不能再简化一下数据
另外建边的时候, 如果直接一开始就addedge的话,容易被卡常, 这里每一步的价值都知道了, 那就在spfa里“建边”就好了,另外这题spfa更快
#include <iostream>
#include <cstdio>
#include <cstring>
#include <algorithm>
using namespace std;
const int maxn = 1e6 + 500;
const int INF = 1e9;
int p[10] = {2, 3, 5, 7, 11, 13};
int Q[maxn], dis[maxn], book[maxn], n;
void spfa()
{
for(int i = 0; i <= n+50; i++)
dis[i] = INF, book[i] = 0;
int rear, frnt;
rear = frnt = 0;
Q[rear++] = 1;
book[1] = 1;
dis[1] = 0;
while(rear != frnt)
{
int u = Q[frnt++];
if(frnt == maxn) frnt = 0;
book[u] = 0;
for(int i = 0; i < 6; i++)
{
if(u*p[i] <= n+13 && dis[u*p[i]] > dis[u] + p[i])
{
dis[u*p[i]] = dis[u] + p[i];
if(!book[u*p[i]])
{
Q[rear++] = u*p[i];
book[u*p[i]] = 1;
if(rear == maxn) rear = 0;
}
}
}
if(u > 1 && dis[u-1] > dis[u] + 1)
{
dis[u-1] = dis[u] + 1;
if(!book[u-1])
{
book[u-1] = 1;
Q[rear++] = u-1;
if(rear == maxn) rear = 0;
}
}
}
}
int main()
{
scanf("%d", &n);
spfa();
printf("%d\n", dis[n]);
return 0;
}