NOIP2017逛公园

本文详细解析了一道名为“逛公园”的算法题目,通过与luogu1608路径统计的对比,介绍了如何利用动态规划与记忆化搜索解决路径数量计算的问题。文章深入探讨了状态表示、状态转移方程以及避免重复计算的方法。

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

  在WA了接近20遍后,本蒟蒻终于切掉了逛公园。。

  逛公园的题意为求出图中从1到n路径长度<=d(最短路)+k 的

路径条数。

  这道题十分的像luogu 1608 路径统计,虽然算法完全不一样,

但思想却十分相似。由于k十分的小,我们可以考虑枚举k,设

f[i][j] 为从 1 到 i 比最短路大j的路径的条数。则最终的答案就是

(f[n][0]+f[n][1]+…+f[n][k]) 如何求出发f[i][j]呢?可以从出发点开始dp,

但dp的状态似乎十分难表示,我们可以从终点开始倒

着进行记忆化这时我们会发现每次要扩展的状态特别

容易表示。我们可以用spfa 进行预处理,用w[i]表

示从1到i的最短路。我们先来看一个式子:

w[u]-(w[v]+e[u].val)+j (以下u,v都是反向建图后的:u代表起点,v代表终点,e[u].val代表边权)这句话的

含义为: (w[v]+e[u].val)-w[u]代表了如果选择由u转移

至v超过了 u–v 之间最短路的值,则:

f[e[i].to][j-w[u]-(w[v]+e[u].val)]就是u转移到v后符合要

求的路径的条数 (相当于从u转移到v消耗了多少k)

如果 j-w[u]-(w[v]+e[u].val) 为负数,就证明当前情况下

u无法转移到v。排除这种情况后,我们就可以递归求f[u][j]了,f[u][j]=sigma f[e[i].to][j-w[u]-(w[v]+e[u].val)];

#### 这种写法仍然会RE,我们需要记下状态,当重复搜到

#### 该状态时,直接return就可以了。

# 附上代码:

 #include<iostream>
#include<cstdio>
#include<queue>
#include<algorithm>
#include<cstring>
#include<string>
#include<stack>
using namespace std;
stack<int>st;
vector<int> pic[100001];
const int maxn=100001;
struct hzw{
    int to;
    int next;
    int v;
}e[200001];
int cur,head[200001],w[maxn];
bool pan[maxn],ss[maxn];
int dfn[maxn],low[maxn],num[maxn];
int dfn2[maxn],low2[maxn],ss2[maxn];
bool v[100001];
int add(int a,int b,int c){
    e[cur].to=b;
    e[cur].next=head[a];
    e[cur].v=c;
    head[a]=cur++;
}
int head2[maxn*2],cur2;
struct ak{
    int to2;
    int next2;
    int v2;
}e2[maxn*2];
int add2(int a,int b,int c){
    e2[cur2].to2=b;
    e2[cur2].next2=head2[a];
    e2[cur2].v2=c;
    head2[a]=cur2++;
}
int n,m,k,p,d;
bool end;
void spfa(int s){
    bool vis[100001];
    memset (vis,0,sizeof (vis));
    memset (w,0x3f,sizeof (w));
    queue<int>q;
    q.push(s);
    w[s]=0;
    vis[s]=true;
    while (!q.empty()){
        int u=q.front();
        q.pop();
        vis[u]=false;
        for (int i=head[u];i!=-1;i=e[i].next){
            int a=e[i].to;
            int b=e[i].v;
            if (w[a]>w[u]+b){
                w[a]=w[u]+b;
                if (!(vis[a])){
                    q.push(a);
                    vis[a]=true;
                }
            }
        }
    }
}
void tarjan(int s) {//本人蠢笨,用tarjan判断的0环。
//其实开一个数组在dfs中记录状态的访问情况,
//如果在一次dfs中一种状态被再次访问,就可以直接判断0环了。
    dfn[s]=++cur;
    low[s]=cur;
    st.push(s);
    ss[s]=1;
    int tm=pic[s].size();
    for (int i=0; i<tm; ++i) {
        int a=pic[s][i]; 
        if (!dfn[a]){
            tarjan(a);
            low[s]=min(low[s],low[a]);
        }
        else if (ss[a]){
            low[s]=min(low[s],dfn[a]);
        }
    }
    if (dfn[s]==low[s]) {
        int check=0;
        int tmp=st.top();
        while (tmp!=s) {
            check=true;
            ss[tmp]=0;
            if (w[tmp]<=d+k) end=true;
            st.pop();
            tmp=st.top();
        }
        if (check){
           tmp=st.top();
           if (w[tmp]<=d+k) end=true;
           ss[tmp]=0;
           st.pop();
        }
        else{
           tmp=st.top();
           ss[tmp]=0;
           st.pop();
        }
    }
}
long long one;
long long f[maxn][55],vis[maxn][55]; 
inline long long  dfs(int u,int kk){ 
      if (vis[u][kk]) return f[u][kk];
      vis[u][kk]=true;       
      for (int i=head2[u];i!=-1;i=e2[i].next2) {
        long long sum=( w[u]-(w[e2[i].to2]+e2[i].v2) )+kk;
        if (sum<0) continue;
        f[u][kk]=(f[u][kk]+dfs(e2[i].to2,sum))%p;  
       // 递归求出合法的路径条数
     }
     return f[u][kk];
}
int main(){
//  freopen ("park9.in","r",stdin);
//  freopen ("park.out","w",stdout);
    int t;
    cin>>t;
    for (int sss=1;sss<=t;++sss){   
        memset (head,-1,sizeof (head));
        memset(head2,-1,sizeof(head2));
        memset(low,0,sizeof(low));
        memset(dfn,0,sizeof(dfn));
        memset(pic,0,sizeof(pic));
        memset(f,0,sizeof(f));
        memset(vis,0,sizeof(vis));
        one=0;
        end=0;
        cur=0;
        cur2=0;
        memset (w,0,sizeof (w));
        cur=0;
        cin>>n>>m>>k>>p;
        int cnt=0;
        for (int i=1;i<=m;++i){
            int xx,yy,zz;
            scanf("%d%d%d",&xx,&yy,&zz);
            if (zz==0) {
               pic[xx].push_back(yy);
               num[++cnt]=xx;
            }
            add(xx,yy,zz);
            add2(yy,xx,zz);
        }
        spfa (1);
        for (int i=1;i<=cnt;++i){
            cur=0;
            if (!dfn[num[i]]) tarjan(num[i]);
        }
        if (end){
            cout<<"-1"<<endl;
            continue;
        }
        else {
            long long ans=0;
            f[1][0]=1;
            for (int i=0;i<=k;++i) {
            //枚举k
                ans+=dfs(n,i);
                ans%=p;
            }
            cout<<ans<<endl;
        }
    }
    return 0;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值