bzoj 4016 [FJOI2014]最短路径树问题(最短路径树+树分治)

4016: [FJOI2014]最短路径树问题

Time Limit: 5 Sec  Memory Limit: 512 MB
Submit: 426  Solved: 147
[Submit][Status][Discuss]

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&
lt;=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的路径。

【思路】

先求出满足要求的最短路树来。

分治:求出过根节点的点对数,其它递归处理。

求过根节点的点对:假设现在处理根节点的S子树,用f[i][0]表示前S-1棵子树中与根相距i个节点(不含根)的最长路径,f[i][1]表示方案数,类似的定义tmp为当前S子树的统计结果。一遍bfs构造出tmp,枚举该子树上的结点数更新答案,然后用tmp更新f。

需要注意的有:

累计答案的诸多小细节。

f[][],tmp[][],queue的清零。

【代码】

  1 #include<cstdio>
  2 #include<vector>
  3 #include<queue>
  4 #include<cstring>
  5 #include<iostream>
  6 #include<algorithm>
  7 using namespace std;
  8
  9 const int N = 30000+10;
 10 const int INF = 1e9+1e9;
 11
 12 struct Edge {
 13     int v,w;
 14     Edge(int v=0,int w=0):v(v),w(w) {}
 15     bool operator < (const Edge& rhs) const  {
 16         return v<rhs.v;
 17     }
 18 };
 19 queue<int> q;
 20 int n,m,K,root,size,d[N],inq[N];
 21 int ans1,ans2,siz[N],mx[N],vis[N],dep[N],dis[N],fa[N];
 22 vector<Edge> g[N],G[N];
 23
 24 void spfa() {
 25     for(int i=2;i<=n;i++) d[i]=INF;
 26     memset(inq,0,sizeof(inq));
 27     inq[1]=1; q.push(1);
 28     while(!q.empty()) {
 29         int u=q.front(); q.pop(); inq[u]=0;
 30         for(int i=0;i<g[u].size();i++) {
 31             int v=g[u][i].v,w=g[u][i].w;
 32             if(d[v]>d[u]+w) {
 33                 d[v]=d[u]+w;
 34                 if(!inq[v]) inq[v]=1,q.push(v);
 35             }
 36         }
 37     }
 38 }
 39 void dfs(int u) {
 40     vis[u]=1;
 41     for(int i=0;i<g[u].size();i++) {
 42         int v=g[u][i].v,w=g[u][i].w;
 43         if(!vis[v] && d[v]==d[u]+w) {
 44             G[u].push_back(Edge(v,w));
 45             G[v].push_back(Edge(u,w));
 46             dfs(v);
 47         }
 48     }
 49 }
 50 void getroot(int u) {
 51     siz[u]=1; mx[u]=0;
 52     for(int i=0;i<G[u].size();i++) {
 53         int v=G[u][i].v;
 54         if(v!=fa[u] && !vis[v]) {
 55             fa[v]=u;
 56             getroot(v);
 57             siz[u]+=siz[v];
 58             if(siz[v]>mx[u]) mx[u]=siz[v];
 59         }
 60     }
 61     mx[u]=max(mx[u],size-siz[u]);
 62     if(mx[u]<mx[root]) root=u;
 63 }
 64 int f[N][2],tmp[N][2];
 65 void solve(int u,int S){
 66     vis[u]=1; f[0][1]=1;
 67     int m=G[u].size();
 68     for(int i=0;i<m;i++) {
 69         int v=G[u][i].v;
 70         if(!vis[v]) {
 71             while(!q.empty()) q.pop();                            //clear
 72              q.push(v),dep[v]=1,dis[v]=G[u][i].w,fa[v]=u;
 73             while(!q.empty()) {
 74                 int now=q.front(); q.pop();
 75                 int k=dep[now];
 76                 if(k>K) break;
 77                 if(dis[now]>tmp[k][0])
 78                     tmp[k][0]=dis[now],tmp[k][1]=0;
 79                 if(dis[now]==tmp[k][0]) tmp[k][1]++;
 80                 for(int j=0;j<G[now].size();j++) {
 81                     int to=G[now][j].v;
 82                     if(!vis[to] && to!=fa[now]) {
 83                         fa[to]=now;
 84                         dep[to]=dep[now]+1;
 85                         dis[to]=dis[now]+G[now][j].w;
 86                         q.push(to);
 87                     }
 88                 }
 89             }
 90             //for(int j=1;j<=K;j++) printf("(%d,%d) ",tmp[j][0],tmp[j][1]);cout<<endl;
 91             for(int j=1;j<=K;j++) {                //tmp位于前可以取[1..K]
 92                 if(tmp[j][0]+f[K-j][0]>ans1)
 93                     ans1=tmp[j][0]+f[K-j][0],ans2=0;
 94                 if(tmp[j][0]+f[K-j][0]==ans1)
 95                         ans2+=tmp[j][1]*f[K-j][1];
 96             }
 97             for(int j=1;j<=K;j++) {
 98                 if(tmp[j][0]>f[j][0]) f[j][0]=tmp[j][0],f[j][1]=0;
 99                 if(tmp[j][0]==f[j][0]) f[j][1]+=tmp[j][1];
100                 tmp[j][1]=tmp[j][0]=0;
101             }
102          }
103      }
104      //cout<<u<<": "<<ans1<<" "<<ans2<<endl;
105      for(int j=0;j<=K;j++) f[j][0]=f[j][1]=0;
106      m=G[u].size();
107      for(int i=0;i<m;i++) {
108          int v=G[u][i].v;
109          if(!vis[v]) {
110              size=siz[v];
111              if(siz[v]>siz[u]) siz[v]=S-siz[v];
112              root=0;
113              if(size>=K) getroot(v);
114              solve(root,siz[v]);
115          }
116      }
117 }
118 void read(int& x) {
119     char c=getchar(); int f=1; x=0;
120     while(!isdigit(c)){if(c==‘-‘) f=-1;c=getchar();}
121     while(isdigit(c)) x=x*10+c-‘0‘,c=getchar();
122     x*=f;
123 }
124 int main() {
125     //freopen("in.in","r",stdin);
126     //freopen("out.out","w",stdout);
127     read(n),read(m),read(K); K--;
128     int u,v,w;
129     for(int i=0;i<m;i++) {
130         read(u),read(v),read(w);
131         g[u].push_back(Edge(v,w));
132         g[v].push_back(Edge(u,w));
133     }
134     for(int i=1;i<=n;i++)
135         sort(g[i].begin(),g[i].end());
136     spfa();
137     memset(vis,0,sizeof(vis));
138     dfs(1);
139     size=n; mx[0]=INF; root=0;
140     memset(vis,0,sizeof(vis));
141     getroot(1) , solve(root,size);
142     printf("%d %d",ans1,ans2);
143     return 0;
144 }
时间: 2024-10-16 00:51:49

bzoj 4016 [FJOI2014]最短路径树问题(最短路径树+树分治)的相关文章

BZOJ 4016: [FJOI2014]最短路径树问题( 最短路 + 点分治 )

先跑出最短路的图, 然后对于每个点按照序号从小到大访问孩子, 就可以搞出符合题目的树了. 然后就是经典的点分治做法了. 时间复杂度O(M log N + N log N) ---------------------------------------------------------------------------- #include<queue> #include<cctype> #include<cstdio> #include<cstring>

“中兴捧月”比赛之——二叉查找树(BST)树的最短路径Java求解

问题描述: BST树,又称二叉查找树,求其到所有叶子节点路径的最小值 测试用例一:  10 5 20 返回15: 测试用例二: 100 20 70 110 120 10 null null 89 null null null null 返回130: 程序代码实现: 1 package examination.written; 2 3 /** 4 * 5 * @author ZhuXY 6 * @time 2016-6-12 下午9:57:53 7 * 8 */ 9 public class BS

【BZOJ】1146: [CTSC2008]网络管理Network(树链剖分+线段树套平衡树+二分 / dfs序+树状数组+主席树)

第一种做法(时间太感人): 这题我真的逗了,调了一下午,疯狂造数据,始终找不到错. 后来发现自己sb了,更新那里没有打id,直接套上u了.我.... 调了一下午啊!一下午的时光啊!本来说好中午A掉去学习第二种做法,噗 好吧,现在第一种做法是hld+seg+bst+二分,常数巨大,log^4级别,目前只会这种. 树剖后仍然用线段树维护dfs序区间,然后在每个区间建一颗平衡树,我用treap,(这题找最大啊,,,囧,并且要注意,这里的rank是比他大的数量,so,我们在二分时判断要判断一个范围,即要

BZOJ 1018: [SHOI2008]堵塞的交通traffic [线段树 区间信息]

1018: [SHOI2008]堵塞的交通traffic Time Limit: 3 Sec  Memory Limit: 162 MBSubmit: 3064  Solved: 1027[Submit][Status][Discuss] Description 有一天,由于某种穿越现象作用,你来到了传说中的小人国.小人国的布局非常奇特,整个国家的交通系统可以被看成是一个2行C列的矩形网格,网格上的每个点代表一个城市,相邻的城市之间有一条道路,所以总共有2C个城市和3C-2条道路. 小人国的交通

【BZOJ】1047: [HAOI2007]理想的正方形(单调队列/~二维rmq+树状数组套树状数组)

http://www.lydsy.com/JudgeOnline/problem.php?id=1047 树状数组套树状数组真心没用QAQ....首先它不能修改..而不修改的可以用单调队列做掉,而且更快,只有O(n^2).而这货是n^2log^2n的建树...虽然查询是log^2n...但是建树那里就tle了.. 那么说题解... 先orz下,好神.. 我怎么没想到单调队列orz 首先我们维护 行 的单调队列,更新每个点在 列 距离内的最小and最大的值 然后我们维护 列 的单调队列,更新每个点

BZOJ 3012 [Usaco2012 Dec]First! wzq脑洞hash树(正解trie树)

博客风格转化计划实验篇2 题意: 给n(n<=30000)个字符串,所有字符串长度加起来不超过300000(字符串只含有小写字母). 求解哪些字符串可以称作第一字符串. 一个字符串能被称为第一字符串的条件为存在一种字典序使它排在第一名. 方法: wzq脑洞hash树-.. 然而并没有trie树优越. 并且我脑洞的这个树好处有啥:暂且不知道. 坏处有啥:很容易被卡,自带常数. 所以为什么要这么写?只是因为我跟wjc说这题我特么一定要用带hash的东西搞过去! *目标达成√ 解析: 对于以下内容,请

BZOJ 3295 CQOI 2011 动态逆序对 线段树套Treap

题目大意:给出一个数列,每次从这个序列中删掉一个数字,问每次删之前逆序对的数量是多少. 思路:这个题用CDQ分治是飞快的,然而我不知道怎么写..于是就朴素的写了树套树.然后就朴素的被卡常了 内层用一个线段树.这个线段树不修改,一开始就要建好,然后线段树的每一个节点维护一个平衡树,存的是线段树存的区间中所有的值. 一开始先算一下逆序对数,然后每次删点的时候,先查询在这个点之前有多少大于他的,后面有多少小于他的,总的逆序对中将这些减掉.这个过程通过树套树不难实现. CODE(交了这个代码T掉的,请选

BZOJ 2738 矩阵乘法 整体二分+二维树状数组

题目大意:给定一个矩阵,多次求某个子矩阵中的第k小 分块解法见 http://blog.csdn.net/popoqqq/article/details/41356899 <论除最小割外题目解法从来与题目名称无关系列> 整体二分 Solve(x,y,S)表示处理答案在[x,y]区间内的询问集合S 预先将所有数按照大小排序 每次将[1,mid]之间的数插入树状数组 然后对于分治内部的每一个询问 去树状数组中查询相应子矩阵的数值 如果小于等于k就划分到左集合S1 否则划分到右集合S2 然后Solv

BZOJ 3110 ZJOI 2013 K大数查询 树套树(权值线段树套区间线段树)

题目大意:有一些位置,这些位置上可以放若干个数字.现在有两种操作. 1.在区间l到r上添加一个数字x 2.求出l到r上的第k大的数字是什么 思路:这种题一看就是树套树,关键是怎么套,怎么写.(话说我也不会来着..)最容易想到的方法就是区间线段树套一个权值线段树,但是区间线段树上的标记就会变得异常复杂.所以我们就反过来套,用权值线段树套区间线段树.这样修改操作在外线段树上就变成了单点修改,外线段树就不用维护标记了.在里面的区间线段树上维护标记就容易多了.具体实现见代码. CODE: #includ