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;
}
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 基于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)