DP+图论

1-n 长度为[L, R]的路径条数

逛公园(https://ac.nowcoder.com/acm/problem/16416)

题目描述

策策同学特别喜欢逛公园。 公园可以看成一张 N 个点 M 条边构成的有向图,且没有自环和重边。其中 1 号点是公园的入口, N 号点是公园的出口,每条边有一个非负权值,代表策策经过这条边所要花的时间。
策策每天都会去逛公园,他总是从 1 号点进去,从 N 号点出来。
策策喜欢新鲜的事物,他不希望有两天逛公园的路线完全一样,同时策策还是一个特别热爱学习的好孩子,他不希望每天在逛公园这件事上花费太多的时间。如果 1 号点到 N 号点的最短路长为 d,那么策策只会喜欢长度不超过 d + K 的路线。
策策同学想知道总共有多少条满足条件的路线,你能帮帮他吗?
为避免输出过大,答案对 P 取模。
如果有无穷多条合法的路线,请输出 −1。

输入描述:

第一行包含一个整数 T, 代表数据组数。
接下来 T 组数据,对于每组数据:
第一行包含四个整数 N,M,K,P, 每两个整数之间用一个空格隔开。
接下来 M 行,每行三个整数 ai,bi,ci, 代表编号为 ai,bi 的点之间有一条权值为 ci 的有向边,每两个整数之间用一个空格隔开。

输出描述:

输出文件包含 T 行,每行一个整数代表答案。

示例1

输入
2
5 7 2 10
1 2 1
2 4 0
4 5 2
2 3 2
3 4 1
3 5 2
1 5 3
2 2 0 10
1 2 0
2 1 0
输出
复制
3
-1

说明

对于第一组数据,最短路为 3。
1 - 5, 1 - 2 - 4 - 5, 1 - 2 - 3 - 5 为 3 条合法路径。
备注:
对于不同测试点,我们约定各种参数的规模不会超过如下

思路

我的做法没有考虑0环。
我们把1到其他点所有的dis[i]求出来。
用f[i][j]:1到i的长度为dis[i]+j的路径条数。
DP的方向肯定是dis[i]从小到打大的点的顺序进行。

#include<bits/stdc++.h>
#define LL long long
#define pii pair<int, int>
using namespace std;
const int maxn=200005;
const int maxm=1000005;
const int inf=0x7f7f7f7f;
 
struct Dij{
    int dis[maxn], vis[maxn], head[maxn], cut=0;
    int f[55][maxn];
    pii a[200005];
    priority_queue<pii > q;
    struct node{
        int to, w, next;
    }e[maxm*2];
    void init(int n){
        cut=0;
        memset(head, -1, sizeof(int)*(n+3));
        memset(dis, 0x7f, sizeof(int)*(n+3));
        memset(vis, 0, sizeof(int)*(n+3));
    }
    void addcut(int u,int v, int w){
        e[cut].to=v,e[cut].w=w, e[cut].next=head[u],head[u]=cut++;
    }
    void dijkstra(int s, int t){
        q.push({0, s}); dis[s]=0;
        while(!q.empty()){
            pair<int, int> pos=q.top(); q.pop();
            if(vis[pos.second]) continue;
            vis[pos.second]=1;
            for(int i=head[pos.second];i>=0;i=e[i].next){
                if(!vis[e[i].to]&&dis[e[i].to]>dis[pos.second]+e[i].w){
                    dis[e[i].to]=dis[pos.second]+e[i].w;
                    q.push({-dis[e[i].to], e[i].to});
                }
            }
        }
    }
    int getNum(int n, int kk, int p){
        for(int i=1; i<=n; i++){
            a[i]={dis[i], i};
        }
        memset(f, 0, sizeof(f)); f[0][1]=1;
        sort(a+1, a+1+n, [](pii &a, pii &b){return a.first<b.first;});
        for(int k=0; k<=kk; k++){
            for(int j=1; j<=n; j++){
                int u=a[j].second;
                if(dis[u]>=inf) continue;
                for(int i=head[u]; i!=-1; i=e[i].next){
                    int to=e[i].to, w=e[i].w;
                    if(w+dis[u]+k<=dis[to]+kk){
                        f[w+dis[u]+k-dis[to]][to]+=f[k][u];
                        f[w+dis[u]+k-dis[to]][to]%=p;
                    }
                }
            }
        }
        int ans=0;
        for(int i=0; i<=kk; i++){
            ans+=f[i][n]; ans%=p;
        }
        return ans;
    }
}dij;
 
 
int main(){
 
    int t; scanf("%d", &t);
    while(t--){
        int n, m, kk, p; scanf("%d%d%d%d", &n, &m, &kk, &p);
        dij.init(n);
        for(int i=1; i<=m; i++){
            int u, v, w; scanf("%d%d%d", &u, &v, &w);
            dij.addcut(u, v, w);
        }
        dij.dijkstra(1, n);
        printf("%d\n", dij.getNum(n, kk, p));
    }
 
    return 0;
}

P1772 [ZJOI2006]物流运输 DP+最短路

P1772 [ZJOI2006]物流运输(https://www.luogu.com.cn/problem/P1772)

题目描述

物流公司要把一批货物从码头 A 运到码头 B。由于货物量比较大,需要 nn 天才能运完。货物运输过程中一般要转停好几个码头。

物流公司通常会设计一条固定的运输路线,以便对整个运输过程实施严格的管理和跟踪。由于各种因素的存在,有的时候某个码头会无法装卸货物。这时候就必须修改运输路线,让货物能够按时到达目的地。

但是修改路线是—件十分麻烦的事情,会带来额外的成本。因此物流公司希望能够订一个 nn 天的运输计划,使得总成本尽可能地小。

输入格式

第一行是四个整数 n,m,k,en,m,k,e。nn 表示货物运输所需天数,mm 表示码头总数,kk 表示每次修改运输路线所需成本,ee 表示航线条数。

接下来 ee 行每行是一条航线描述,包括了三个整数,依次表示航线连接的两个码头编号以及航线长度。其中码头 A 编号为 11,码头 B 编号为 mm。单位长度的运输费用为 11。航线是双向的。

再接下来一行是一个整数 dd,后面的 dd 行每行是三个整数 p,a,bp,a,b。表示编号为 pp 的码头在 [a,b][a,b] 天之内无法装卸货物。同一个码头有可能在多个时间段内不可用。但任何时间都存在至少一条从码头 A 到码头 B 的运输路线。

输出格式

包括了一个整数表示最小的总成本。
总成本为 nn 天运输路线长度之和 +k\times+k× 改变运输路线的次数。

输入

5 5 10 8
1 2 1
1 3 3
1 4 2
2 3 2
2 4 4
3 4 1
3 5 2
4 5 2
4
2 2 3
3 1 1
3 3 3
4 4 5
输出
32
说明/提示
【数据范围】 对于 100%100% 的数据,1≤n≤100,1≤m≤20。

【样例输入说明】

上图依次表示第 11 至第 55 天的情况,阴影表示不可用的码头。

【样例输出说明】

前三天走 1→4→5,后两天走 1→3→5,这样总成本为 (2+2)×3+(3+2)×2+10=32。

_NOI导刊2010提高(01)

思路

我们开始拿到这道题的思考方向。想想最后的状态。
肯定把n天分成若干段,每一段都是经过一条相同的路径。
我们设:f[i]:前i天的最小费用。
f[i]=f[j]+(i-j)*w(j+1 -> i)+k
w(j->i):时间j到时间i的时1到n最短路。
w求法就是在原图求在i到j时间内(不经过不能装卸的码头)的最短路。

#include <bits/stdc++.h>
#define LL long long
#define pii pair<LL, LL>
using namespace std;

const int maxn=205;
struct Dij{
    int dis[maxn], vis[maxn], head[maxn], cut=0;
    priority_queue<pair<int, int> > q;
    struct node{
        int to, w, next;
    }e[2005*2];
    void init(int n){
        cut=0;
        memset(head, -1, sizeof(int)*(n+3));
        memset(dis, 0x7f, sizeof(int)*(n+3));
        memset(vis, 0, sizeof(int)*(n+3));
    }
    void addcut(int u,int v, int w){
        e[cut].to=v,e[cut].w=w, e[cut].next=head[u],head[u]=cut++;
    }
    void dijkstra(int s, int t){
        q.push({0, s}); dis[s]=0;
        while(!q.empty()){
            pair<int, int> pos=q.top(); q.pop();
            if(vis[pos.second]) continue;
            vis[pos.second]=1;
            for(int i=head[pos.second];i>=0;i=e[i].next){
                if(!vis[e[i].to]&&dis[e[i].to]>dis[pos.second]+e[i].w){
                    dis[e[i].to]=dis[pos.second]+e[i].w;
                    q.push({-dis[e[i].to], e[i].to});
                }
            }
        }
    }
}dij;

LL x[1005], y[1005], z[1005], vis[25][105];
int dis[105][105];
LL f[1005];
LL ok(LL x, LL l, LL r){
    return vis[x][r]-vis[x][l-1]==0;
}

int main() {

    LL n, m, k, e; scanf("%lld%lld%lld%lld", &n, &m, &k, &e);
    for(LL i=1; i<=e; i++){
        LL a, b, c; scanf("%lld%lld%lld", &a, &b, &c);
        x[i]=a, y[i]=b, z[i]=c;
    }
    LL d; scanf("%lld", &d);
    for(LL i=1; i<=d; i++){
        LL p, a, b; scanf("%lld%lld%lld", &p, &a, &b);
        for(LL i=a; i<=b; i++){
            vis[p][i]=1;
        }
    }
    for(LL i=1; i<=m; i++){
        for(LL j=1; j<=n; j++){
            vis[i][j]+=vis[i][j-1];
        }
    }

    for(LL i=1; i<=n; i++){
        for(LL j=i; j<=n; j++){
            dij.init(m);
            for(LL k=1; k<=e; k++){
                if(ok(x[k], i, j)&&ok(y[k], i, j)){
                    dij.addcut(x[k], y[k], z[k]);
                    dij.addcut(y[k], x[k], z[k]);
                }
            }
            dij.dijkstra(1, m);
            dis[i][j]=dij.dis[m];
            //cout<<i<<" "<<j<<" "<<dis[i][j]<<endl;
        }
    }

    memset(f, 0x7f, sizeof(f));
    f[0]=0;
    for(LL i=1; i<=n; i++){
        f[i]=dis[1][i]*i;
        for(LL j=0; j<i; j++){
            if(dis[j+1][i]<f[104]){
                //cout<<i<<" "<<j<<" "<<f[j]+dis[j+1][i]*(i-j)+k<<endl;
                f[i]=min(f[j]+dis[j+1][i]*(i-j)+k, f[i]);
            }
        }
    }
    printf("%lld\n", f[n]);

    return 0;
}

P3758 [TJOI2017]可乐 DP方案数,建虚拟节点,虚边

P3758 [TJOI2017]可乐(https://www.luogu.com.cn/problem/P3758)

题目描述

加里敦星球的人们特别喜欢喝可乐。因而,他们的敌对星球研发出了一个可乐机器人,并且放在了加里敦星球的 11 号城市上。这个可乐机器人有三种行为: 停在原地,去下一个相邻的城市,自爆。它每一秒都会随机触发一种行为。现在给加里敦星球城市图,在第 00 秒时可乐机器人在 11 号城市,问经过了 tt 秒,可乐机器人的行为方案数是多少?

输入格式

第一行输入两个正整数 NN,MM。NN 表示城市个数,MM 表示道路个数。

接下来 MM 行每行两个整数 uu,vv,表示 uu,vv 之间有一条道路。保证两座城市之间只有一条路相连,且没有任何一条道路连接两个相同的城市。

最后一行是一个整数 tt,表示经过的时间。

输出格式

输出可乐机器人的行为方案数,答案可能很大,请输出对 20172017 取模后的结果。

输入

3 2
1 2
2 3
2
输出
8

说明/提示

样例输入输出 1 解释
1 ->爆炸。
1 -> 1 ->爆炸。
1 -> 2 ->爆炸。
1 -> 1 -> 1。
1 -> 1 -> 2。
1 -> 2 -> 1。
1 -> 2 -> 2。
1 -> 2 -> 3。
数据范围与约定
对于 20% 的数据,保证 t≤1000。
对于100%的数据,保证 1<t≤10^6,1≤N≤30,0 < M < 100, 1≤u,v≤N。
思路:还是比较好想, f[i][j]:时间j,可乐机器人在节点i的到结束的方案数
f[u][T]=F[u][T+1]+F[x][T+1]+1 //停在原点 去其他点 爆炸
但是开f[31][1000001];的数组,会MLE。

我们考虑递推。可以滚动数组优化。
f[i][j]:时间j,可乐机器人停在节点i的方案数。
我们考虑停止原地:自己向自己建边。
爆炸:建立一个虚点n+1。所有点向他连接单向边。

#include <bits/stdc++.h>
#define LL long long
using namespace std;

vector<vector<int> > G(35);
int t;
int f[2][35];

int main() {

    int n, m; scanf("%d%d", &n, &m);
    for(int i=1; i<=m; i++){
        int x, y; scanf("%d%d", &x, &y);
        G[x].push_back(y); G[y].push_back(x);
    }
    for(int i=1; i<=n; i++) G[n+1].push_back(i), G[i].push_back(i);

    f[0][1]=1;
    scanf("%d", &t);
    int ans=0;
    for(int j=0; j<t; j++){
        memset(f[(j+1)%2], 0, sizeof(f[(j+1)%2]));
        for(int i=1; i<=n+1; i++){
            for(auto x: G[i]){
                f[(j+1)%2][i]+=f[j%2][x];
                f[(j+1)%2][i]%=2017;
            }
        }
        ans+=f[(j+1)%2][n+1];//每个时间爆炸的方案数
        ans%=2017;
    }
    for(int i=1; i<=n; i++){
        ans+=f[t%2][i];
        ans%=2017;
    }
    printf("%d\n", ans);

    return 0;
}
posted @   liweihang  阅读(232)  评论(0编辑  收藏  举报
编辑推荐:
· 基于Microsoft.Extensions.AI核心库实现RAG应用
· Linux系列:如何用heaptrack跟踪.NET程序的非托管内存泄露
· 开发者必知的日志记录最佳实践
· SQL Server 2025 AI相关能力初探
· Linux系列:如何用 C#调用 C方法造成内存泄露
阅读排行:
· 震惊!C++程序真的从main开始吗?99%的程序员都答错了
· 别再用vector<bool>了!Google高级工程师:这可能是STL最大的设计失误
· 单元测试从入门到精通
· 【硬核科普】Trae如何「偷看」你的代码?零基础破解AI编程运行原理
· 上周热点回顾(3.3-3.9)
Live2D
欢迎阅读『DP+图论』
点击右上角即可分享
微信分享提示