Bzoj4016

Description

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


题意:如上

解:先按字典序建出最短路树,然后点分治,算出过当前点长为k的简单路径的最大长度和数量。具体通过bfs得出当前点当前子树下包含k个点及以内的最大长度和数量,然后与当前点之前计算完的子树进行计算,得出到当前子树的最大长度和数量。然后递归下去。

        还有一个小小的剪枝,是当当前点的size小于k时直接return,因为此时一定无法更新答案,然后我的代码从1084ms降到了648ms。

代码:

#include<cstdio>
#include<cstring>
#include<algorithm>
#include<cstdlib>
#include<queue>
#include<vector>
#define Rep(i,u) for(register int i=Begin[u],v=to[i];i;i=Next[i],v=to[i])
#define For(i,j,k) for(register int i=(j);i<=(int)k;i++)
#define Forr(i,j,k) for(register int i=(j);i>=(int)k;i--)
#define Set(a,b) memset((a),(b),sizeof(a)); 
#define pb push_back
#define ll unsigned long long
using namespace std;
const int N=30010,M=60010,INF=0x3f3f3f3f;
int Begin[N],to[M],Next[M],w[M],p[N],fa[N],siz[N],n,k,m,e=1,dis[N],dep[N],d[N],tmp[N][2],f[N][2];
struct edge{
    int v,w;
    edge(int v=0,int w=0):v(v),w(w){}
    bool operator <(const edge &r)const{
        return v<r.v;
    }
};
vector<edge>G[N];
queue<int>q;
inline void add(int x,int y,int z){
    to[++e]=y,Next[e]=Begin[x],Begin[x]=e,w[e]=z;
}
void read(int &x){
    x=0;char c=getchar();int f=0;
    while(c<'0'||c>'9'){c=getchar();if(c=='-')f=1;}
    while(c>='0'&&c<='9')x=x*10+c-'0',c=getchar();if (f)x=-x;
}
void spfa(int st){
    Set(d,INF);
    q.push(st),d[st]=0,p[st]=1;
    while(!q.empty()){
        int u=q.front(),S=G[u].size();q.pop();p[u]=0;
        For(i,0,S-1){
            int v=G[u][i].v;
            if (d[v]>d[u]+G[u][i].w){
                d[v]=d[u]+G[u][i].w;
                if(!p[v])p[v]=1,q.push(v);
            }
        }
    }   
}
void gettree(int u){
    int S=G[u].size();
    p[u]=1;
    For(i,0,S-1){
        int v=G[u][i].v;
        if (!p[v]&&d[v]==d[u]+G[u][i].w)
            add(u,v,G[u][i].w),add(v,u,G[u][i].w),gettree(v);
    }
}
void init(){
    read(n),read(m),read(k);k--;
    For(i,1,m){
        int u,v,w;
        read(u),read(v),read(w);
        G[u].pb(edge(v,w)),G[v].pb(edge(u,w));
    }
    For(i,1,n)sort(G[i].begin(),G[i].end());
    spfa(1);Set(p,0);
    gettree(1);Set(p,0);
}
void getcg(int rt,int sz,int &cg){
    int flag(1);
    siz[rt]=1;
    Rep(i,rt)
        if (!p[v]&&fa[rt]!=v){
            fa[v]=rt;
            getcg(v,sz,cg),siz[rt]+=siz[v];
            if (siz[v]>sz>>1)flag=0;
        }
    if(sz-siz[rt]>sz>>1)flag=0;
    if(flag)cg=rt;
}
int ans1,ans2;
void solve(int rt,int sz){
    int cg;
    fa[rt]=0;f[0][1]=1;
    if(sz>=k)getcg(rt,sz,cg);else return;
    siz[fa[cg]]=sz-siz[cg];fa[cg]=0;
    Rep(i,cg)
        if(!p[v]){
            while(!q.empty())q.pop();
            q.push(v),dep[v]=1,dis[v]=w[i],fa[v]=cg;
            while(!q.empty()){
                int u=q.front();q.pop();
                int K=dep[u];
                if(K>k)break;
                if (dis[u]>tmp[K][0])tmp[K][0]=dis[u],tmp[K][1]=0;
                if (dis[u]==tmp[K][0])tmp[K][1]+=1;
                Rep(j,u)
                    if(!p[v]&&fa[u]!=v) 
                        fa[v]=u,q.push(v),
                        dep[v]=K+1,dis[v]=dis[u]+w[j];
            }
            For(j,1,k){
                if(tmp[j][0]+f[k-j][0]>ans1)ans1=f[k-j][0]+tmp[j][0],ans2=0;
                if(tmp[j][0]+f[k-j][0]==ans1)ans2+=tmp[j][1]*f[k-j][1];
            }
            For(j,1,k){
                if(tmp[j][0]>f[j][0])f[j][0]=tmp[j][0],f[j][1]=0;
                if(tmp[j][0]==f[j][0])f[j][1]+=tmp[j][1];
                tmp[j][0]=tmp[j][1]=0;
            }   
        }
    p[cg]=1;
    For(i,0,k)f[i][0]=f[i][1]=0;
    Rep(i,cg)
        if(!p[v])
            solve(v,siz[v]);
}
int main(){
    init();
    solve(1,n);
    printf("%d %d\n",ans1,ans2);
    return 0;
}


  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值