[FJOI2014]最短路径树问题

Description

给一个包含n个点,m条边的无向连通图。从顶点1出发,往其余所有点分别走一次并返回。
往某一个点走时,选择总长度最短的路径走。若有多条长度最短的路径,则选择经过的顶点序列字典序最小的那条路径(如路径A为1,32,11,路径B为1,3,2,11,路径B字典序较小。注意是序列的字典序的最小,而非路径中节点编号相连的字符串字典序最小)。到达该点后按原路返回,然后往其他点走,直到所有点都走过。
可以知道,经过的边会构成一棵最短路径树。请问,在这棵最短路径树上,最长的包含K个点的简单路径长度为多长?长度为该最长长度的不同路径有多少条?
这里的简单路径是指:对于一个点最多只经过一次的路径。不同路径是指路径两端端点至少有一个不同,点A到点B的路径和点B到点A视为同一条路径。

Input

第一行输入三个正整数n,m,K,表示有n个点m条边,要求的路径需要经过K个点。接下来输入m行,每行三个正整数Ai,Bi,Ci(1<=Ai,Bi<=n,1<=Ci<=10000),表示Ai和Bi间有一条长度为Ci的边。数据保证输入的是连通的无向图。

Output

输出一行两个整数,以一个空格隔开,第一个整数表示包含K个点的路径最长为多长,第二个整数表示这样的不同的最长路径有多少条。

Sample Input

6 6 4
1 2 1
2 3 1
3 4 1
2 5 1
3 6 1
5 6 1

Sample Output

3 4

HINT

对于所有数据n<=30000,m<=60000,2<=K<=n。

数据保证最短路径树上至少存在一条长度为K的路径。

2016.12.7新加数据一组by - wyx-150137
题解:点分治+DP
令f[i]为当前处理过的子树中深度为i的 距重心最远的距离
dis[i]为正在处理的子树中深度为i的 距重心最远的距离
用f[k-i-1]+dis[i]来更新答案 顺便记录方案数即可

#include <iostream>
#include <cstdio>
#include <cstring>
#include <vector>
#include <queue>
using namespace std;
const int maxn=30000+10;
vector<int>A[maxn];
vector<int>C[maxn];
vector<int>B[maxn];
vector<int>D[maxn];
int dis[maxn];
int vis[maxn],p[maxn];
int f[maxn],tmp[maxn][2];
int n,m,k;
int g[maxn][2];
int sum,root;
int ans1=0,ans2=0;
int size[maxn];
int dep[maxn];
int tot=0;
struct T{
    int dis,x;
    bool operator <(const T &a)const{
        if(dis==a.dis)
            return x>a.dis;
        return dis>a.dis;
    }
};
priority_queue<T>q;
inline void spfa(){
    for(int i=1;i<=n;i++)
        dis[i]=0x7fffffff;
    dis[1]=0;
    T cl;
    cl.dis=0,cl.x=1;
    q.push(cl);
    while(!q.empty()){
        T dd=q.top();
        q.pop();
        if(vis[dd.x])
            continue;
        //printf("%d\n",dd.x);
        vis[dd.x]=1;
        for(int i=0;i<B[dd.x].size();i++)
            if(dis[dd.x]+D[dd.x][i]<dis[B[dd.x][i]]){
                dis[B[dd.x][i]]=dis[dd.x]+D[dd.x][i];
                p[B[dd.x][i]]=dd.x;
                T el;
                el.x=B[dd.x][i];
                el.dis=dis[B[dd.x][i]];
                q.push(el);
            }
    }
    for(int i=1;i<=n;i++){
        //printf("%d ",dis[i]);
        if(p[i]){
            A[p[i]].push_back(i);
            A[i].push_back(p[i]);
            C[p[i]].push_back(dis[i]-dis[p[i]]);
            C[i].push_back(dis[i]-dis[p[i]]);
        }
    }
}
inline void getroot(int x,int fa){
    size[x]=1;
    f[x]=0;
    for(int i=0;i<A[x].size();i++){
        int u=A[x][i];
        if(vis[u]||u==fa)
            continue;
        getroot(u,x);
        f[x]=max(f[x],size[u]);
        size[x]+=size[u];
    }
    f[x]=max(f[x],sum-size[x]);
    if(f[x]<f[root])
        root=x;
}
inline void get_dep(int x,int d,int w,int fa){
    dep[x]=d;
    dis[x]=w;
    if(dis[x]>tmp[dep[x]][0])
        tmp[dep[x]][0]=dis[x],tmp[dep[x]][1]=1;
    else if(dis[x]==tmp[dep[x]][0])
        tmp[dep[x]][1]++;
    size[x]=1;
    for(int i=0;i<A[x].size();i++){
        int u=A[x][i];
        if(vis[u]||u==fa)
            continue;
        get_dep(u,d+1,w+C[x][i],x);
        size[x]+=size[u];
    }
}
inline void calc(int x){
    for(int i=0;i<A[x].size();i++){
        int u=A[x][i];
        if(vis[u])
            continue;
        tot=0;
        get_dep(u,1,C[x][i],x);
        for(int j=1;j<k;j++){
            //printf("%d ",g[k-dep[tmp[j]]-1][0]+dis[tmp[j]]);
            if(g[k-j-1][0]+tmp[j][0]>ans1){
                ans1=g[k-j-1][0]+tmp[j][0];
                ans2=g[k-j-1][1]*tmp[j][1];
            }
            else if(g[k-j-1][0]+tmp[j][0]==ans1)
                ans2+=g[k-j-1][1]*tmp[j][1];
        }
        for(int j=1;j<k;j++){
            if(tmp[j][0]>g[j][0]){
                g[j][0]=tmp[j][0];
                g[j][1]=tmp[j][1];
            }
            else if(tmp[j][0]==g[j][0])
                g[j][1]+=tmp[j][1];
            tmp[j][0]=tmp[j][1]=0;
        }
    }
}
inline void work(int x){
    //printf("%d\n",x);
    for(int i=0;i<=k;i++)
        g[i][1]=g[i][0]=0;
    g[0][1]=1;
    vis[x]=1;
    calc(x);
    for(int i=0;i<A[x].size();i++){
        int u=A[x][i];
        if(vis[u])
            continue;
        root=0,sum=size[u];
        if(sum>=k){
            getroot(u,x);
            work(root);
        }
    }
}
int main(){
    //freopen("dat.in","r",stdin);
    //freopen("dat.out","w",stdout);
    scanf("%d %d %d",&n,&m,&k);
    int x,y,z;
    while(m--){
        scanf("%d %d %d",&x,&y,&z);
        B[x].push_back(y);
        D[x].push_back(z);
        B[y].push_back(x);
        D[y].push_back(z);
    }
    spfa();
    memset(vis,0,sizeof(vis));
    root=0,sum=n;
    f[0]=0x7fffffff;
    getroot(1,1);
    work(root);
    printf("%d %d\n",ans1,ans2);
return 0;
}



评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值