线段树建图+spfa

本文介绍了一种处理复杂图结构的单源最短路径算法,特别针对包含区间加边的有向图。通过构建辅助图和使用SPFA算法,解决从单一起点到所有其他点的最短路径问题。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

题意:
就是给了n个点,给了m个加边的关系,有向边,但是加边不一定是u->v,可能是u->[l, r]就是u到[l, r]区间里面的每一个点都加一条边,或者是[l, r] -> u,就是[l, r]区间到u加一条边。然后求单源最短路。
线段树建图

#include<bits/stdc++.h>
using namespace std;
const int maxn=200100;
const long long int inf=1000000000000000000;
struct node
{
    int v,nxt;
    long long w;
}edge[maxn*68];
int cnt=0;
int sz;
int head[maxn*2];
int id[3][maxn*2];
int A[maxn*2],B[maxn*2];
long long dis[maxn*2];
vector<int>st;
int vis[maxn*2];
void add_Edge(int u,int v,long long w)
{
    edge[cnt].v=v;
    edge[cnt].w=w;
    edge[cnt].nxt=head[u];
    head[u]=cnt++;
}
void build(int rt,int l,int r,int op)
{
    id[op][rt]=++sz;
    if(l==r)
    {
        if(op)
        {
            A[l]=id[op][rt];
        }
        else
        {
            B[l]=id[op][rt];
        }
        return ;
    }
    else
    {
        int mid=(l+r)/2;
        build(rt<<1,l,mid,op);
        build(rt<<1|1,mid+1,r,op);
        if(op)
        {
            add_Edge(id[op][rt<<1],id[op][rt],0);
            add_Edge(id[op][rt<<1|1],id[op][rt],0);
        }
        else
        {
            add_Edge(id[op][rt],id[op][rt<<1],0);
            add_Edge(id[op][rt],id[op][rt<<1|1],0);
        }
    }
}
void update(int rt,int l,int r,int ql,int qr,int op)
{
    if(ql<=l&&r<=qr)
    {
        st.push_back(id[op][rt]);
    }
    else
    {
        int mid=(l+r)/2;
        if(ql>mid)
            update(rt<<1|1,mid+1,r,ql,qr,op);
        else if(mid>=qr)
            update(rt<<1,l,mid,ql,qr,op);
        else
            update(rt<<1,l,mid,ql,mid,op),update(rt<<1|1,mid+1,r,mid+1,qr,op);
    }
}
int spfa(int s)
{
    for(int i=0;i<maxn*2-11;i++)
    {
        dis[i]=inf;
    }
    memset(vis, 0, sizeof(vis));
    dis[s] = 0;
    vis[s] = 1;
    queue<int> q;
    q.push(s);
    while(!q.empty())
    {
        int tem = q.front();
        q.pop();
        vis[tem] = 0;
        for(int i = head[tem]; i != -1; i = edge[i].nxt)
        {
            int to = edge[i].v;
            //printf("-1\n");
            if(dis[to] > dis[tem] + edge[i].w)
            {
                //cout << "yes\n";
                dis[to] = dis[tem] +edge[i].w;
                if(!vis[to])
                {
                    vis[to] = 1;
                    q.push(to);
                }
            }
        }
    }
}
int main ()
{
    int n,q,s,ops;
    while(~scanf("%d%d%d",&n,&q,&s))
    {
        cnt=0;
        memset(head,-1,sizeof(head));
        sz=0; 
        memset(id,0,sizeof(id));
        build(1,1,n,1);
        build(1,1,n,0);

        for(int i=1;i<=n;i++)
        {
            add_Edge(A[i],B[i],0);
            add_Edge(B[i],A[i],0);
        }
        for(int i=1;i<=q;i++)
        {
            scanf("%d",&ops);
            if(ops==1)
            {
                int xx,yy;
                long long zz;

                scanf("%d%d",&xx,&yy);
                cin>>zz;
                add_Edge(A[xx],B[yy],zz);
            }
            else if(ops==2)
            {
                int xx,l,r;
                long long zz;
                scanf("%d%d%d",&xx,&l,&r);
                cin>>zz;
                st.clear();
                update(1,1,n,l,r,0);
                vector<int>::iterator it ;
                for(it=st.begin();it!=st.end();it++)
                {
                    add_Edge(A[xx],*it,zz);
                }
            }
            else if(ops==3)
            {
                int xx,l,r;
                scanf("%d%d%d",&xx,&l,&r);
                long long zz;
                cin>>zz;
                st.clear();
                update(1,1,n,l,r,1);
                vector<int>::iterator it ;
                for(it=st.begin();it!=st.end();it++)
                {
                    add_Edge(*it,B[xx],zz);
                }
            }
        }
        //printf("111\n");
        spfa(B[s]);
        for(int i=1;i<n;i++)
        {
            if(dis[B[i]]>=inf)
            {
                printf("-1 ");
            }
            else
            {
               cout<<dis[B[i]]<<" ";
            }
        }
        if(dis[B[n]]>=inf)
        {
            printf("-1\n");
        }
        else
        {
            cout<<dis[B[n]]<<endl;
        }
    }
} 
<think>题目:洛谷 P1250 种树 题目大意:给定一个区间 [1, n],有 m 个要求,每个要求为在区间 [l, r] 内至少种 t 棵树。问最少需要种多少棵树才能满足所有要求。 核心算法:差分约束系统 + 最长路(或贪心+区间覆盖) 注意:本题可以使用贪心(区间覆盖)的方法,但更通用的方法是差分约束系统。这里提供差分约束系统的解法。 差分约束系统: 1. 将每个要求转化为不等式:设 s[i] 表示前 i 个位置(包括 i)种树的总数,则每个要求 [l, r] 至少 t 棵可以表示为:s[r] - s[l-1] >= t 2. 同时,题目中还有隐含条件: - 每个位置种树数量只能是0或1(因为一个位置只能种一棵树),所以有:0 <= s[i] - s[i-1] <= 1 即:s[i] - s[i-1] >= 0 和 s[i-1] - s[i] >= -1 3. 我们要求的是 s[n] - s[0] 的最小值(即总共种树的数量),根据不等式 s[r]-s[l-1]>=t,我们可以构一个,然后求从0到n的最长路(因为要求的是最小值,而约束条件是大于等于,所以用最长路)。 具体步骤: - : 1. 对于每个要求 [l, r, t]:添加一条边 (l-1) -> r,权值为 t。 2. 对于每个位置 i (从1到n):添加边 (i-1) -> i,权值为0;添加边 i -> (i-1),权值为-1。 - 然后以0为起点,求最长路(注意:中有负权边,所以用SPFA,但这里由于是求最长路,且存在负权,所以需要将距离初始化为负无穷,然后松弛条件为 if (dis[v] < dis[u] + w) 则更新)。 但是,本题中 n 最大为30000,m 最大为5000,使用SPFA求最长路需要注意,因为最长路问题中,如果存在正环则无解,但本题的约束条件不会出现正环(因为边权有界,且的结构是线性的,所以不会出现正环)。 另一种更简单的方法:贪心(区间覆盖) 1. 按区间的右端点从小到大排序。 2. 从前往后处理每个区间,先统计该区间已经种了多少树(从右端点开始向左覆盖,尽量在区间末尾种树,这样可以覆盖更多的区间),如果已经满足要求,则跳过;否则,从右向左在空位置上种树,直到满足要求。 3. 这种方法需要树状数组或线段树来统计区间内已经种了多少树,但本题数据范围较小(n<=30000,m<=5000),所以可行。 这里提供差分约束系统的代码(因为题目要求cpp题解,且差分约束系统是通用解法)。 代码实现(差分约束系统): 注意:我们要求的是 s[n](因为s[0]=0),所以以0为起点,求到n的最长路。 ```cpp #include <iostream> #include <vector> #include <queue> #include <cstring> #include <algorithm> using namespace std; const int MAXN = 30010; const int INF = 0x3f3f3f3f; struct Edge { int to, weight; Edge(int t, int w) : to(t), weight(w) {} }; vector<Edge> graph[MAXN]; int dis[MAXN]; // 存储最长路距离 bool inQueue[MAXN]; void addEdge(int u, int v, int w) { graph[u].push_back(Edge(v, w)); } void spfa(int start, int n) { memset(dis, -INF, sizeof(dis)); // 初始化为负无穷(因为求最长路) dis[start] = 0; queue<int> q; q.push(start); inQueue[start] = true; while (!q.empty()) { int u = q.front(); q.pop(); inQueue[u] = false; for (const Edge& e : graph[u]) { int v = e.to, w = e.weight; if (dis[v] < dis[u] + w) { // 松弛条件:如果通过u到v可以使dis[v]变大 dis[v] = dis[u] + w; if (!inQueue[v]) { inQueue[v] = true; q.push(v); } } } } } int main() { int n, m; cin >> n >> m; // :注意点的范围是0..n for (int i = 1; i <= n; i++) { // s[i] - s[i-1] >= 0 -> s[i] >= s[i-1] addEdge(i-1, i, 0); // s[i-1] - s[i] >= -1 -> s[i-1] >= s[i] - 1 addEdge(i, i-1, -1); } for (int i = 0; i < m; i++) { int l, r, t; cin >> l >> r >> t; // s[r] - s[l-1] >= t addEdge(l-1, r, t); } // 求0到n的最长路 spfa(0, n); cout << dis[n] << endl; return 0; } ``` 注意:上面的差分约束系统解法中,我们添加了0到n的所有边,并且每个要求添加了一条边。然后以0为起点求最长路,dis[n]就是答案。 但是,上面的代码在洛谷上可能会超时(因为SPFA最坏时间复杂度较高),所以我们可以优化:实际上,我们只需要从0开始,然后依次更新每个点,因为的结构是链状的,所以我们可以按顺序更新(类似于动态规划)。但是为了通用性,我们使用SPFA,并且在本题中,由于的特殊性(边权有界,且是线性结构),SPFA可以很快收敛。 另外,我们也可以使用堆优化(但这里SPFA的队列实现已经足够)。 §§相关问题§§ 1. 在差分约束系统中,为什么求最小值用最长路,求最大值用最短路? 2. 本题中,为什么使用SPFA求最长路而不用Dijkstra? 3. 如何用贪心(区间覆盖)的方法解决P1250种树问题? 4. 如果题目要求每个位置可以种多棵树(比如最多k棵),那么差分约束系统应该如何修改? 5. 在差分约束系统中,如何处理无解的情况?</think>### 洛谷 P1250 "种树" C++ 题解 **题目大意**:在区间 [1, n] 上有 m 个要求,每个要求区间 [l, r] 内至少种 t 棵树。每个位置只能种一棵树,求满足所有要求的最小总树数。 **核心算法**:差分约束系统 + SPFA 求最长路 **代码实现**: ```cpp #include <iostream> #include <vector> #include <queue> #include <cstring> #include <algorithm> using namespace std; const int MAXN = 30010; const int INF = 0x3f3f3f3f; struct Edge { int to, weight; Edge(int t, int w) : to(t), weight(w) {} }; vector<Edge> graph[MAXN]; int dis[MAXN]; // 存储最长路距离 bool inQueue[MAXN]; void addEdge(int u, int v, int w) { graph[u].push_back(Edge(v, w)); } void spfa(int start, int n) { memset(dis, -INF, sizeof(dis)); // 初始化为负无穷(最长路) dis[start] = 0; queue<int> q; q.push(start); inQueue[start] = true; while (!q.empty()) { int u = q.front(); q.pop(); inQueue[u] = false; for (const Edge& e : graph[u]) { int v = e.to, w = e.weight; if (dis[v] < dis[u] + w) { // 松弛条件:更新最长路 dis[v] = dis[u] + w; if (!inQueue[v]) { inQueue[v] = true; q.push(v); } } } } } int main() { int n, m; cin >> n >> m; // 立基本约束边 for (int i = 1; i <= n; i++) { // s[i] - s[i-1] >= 0 (每个位置最多种一棵) addEdge(i-1, i, 0); // s[i-1] - s[i] >= -1 (每个位置至少不种或种一棵) addEdge(i, i-1, -1); } // 添加题目要求的约束 for (int i = 0; i < m; i++) { int l, r, t; cin >> l >> r >> t; // s[r] - s[l-1] >= t addEdge(l-1, r, t); } // 求从0到n的最长路 spfa(0, n); cout << dis[n] << endl; return 0; } ``` **算法解析**: 1. **差分约束系统立** - 定义前缀和数组 `s[i]` 表示前 i 个位置种树总数 - 约束条件: - `s[r] - s[l-1] >= t`(题目要求) - `s[i] - s[i-1] >= 0`(每个位置最多一棵) - `s[i-1] - s[i] >= -1`(每个位置种树数为0或1) 2. **论转化** - 每个不等式对应一条有向边:`a - b >= c` → `b → a` 权值为 `c` - 最终目标:求 `s[n] - s[0]` 的最小值 → 求 `s[0]` 到 `s[n]` 的最长路 3. **SPFA算法** - 初始距离设为负无穷,松弛条件为 `dis[v] < dis[u] + w` - 时间复杂度:O(kE),本题中 k 约为常数
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值