HDNOIP201206施工方案 |
难度级别:A; 运行时间限制:1000ms; 运行空间限制:51200KB; 代码长度限制:2000000B |
试题描述 |
c国边防军在边境某处的阵地是由n个地堡组成的。工兵连受命来到阵地要进行两期施工。第一期的任务是挖掘暗道让所有地堡互联互通。 现已勘测设计了m条互不相交的暗道挖掘方案,如果这m条暗道都实施挖掘,肯定能达到互联互通的目的。 事实上,适当选择其中n-1个方案挖掘,就能实现互联互通,即从每个地堡出发都能到达其他任何一个地堡(允许经过别的地堡)。 连长精心谋算,在m个设计规划中选取了挖掘总距离最短且能保证互联互通的若干个暗道规划实施了挖掘, 完成了第一期的施工任务后又接受了第二期的施工任务,要求选择一个地堡进行扩建改造,使其能向每个地堡提供弹药。 为了让弹药供应更及时、更快捷,从改扩建的地堡到最远地堡的距离(称为最远输送距离)应当尽量小。 你的任务是先求出第一期施工挖掘的总距离,再求改扩建地堡最远输送距离的最小值。 |
输入 |
其中第一行是n和m,m>=n,下面的m行每行3个数xi、yi、zi,表示xi到yi的距离是zi,zi<1000000且m个距离互不相等 |
输出 |
两行各一个整数,第一行是第一期的挖掘总距离,第二行是最远输送距离的最小值。 |
输入示例 |
4 5 1 2 1 2 3 2 3 4 3 4 1 4 3 1 5 |
输出示例 |
6 3 |
其他说明 |
第一期挖掘1到2、2到3和3到4的3条暗道,第二期选择3号地堡进行改扩建,最远输送距离是3;60%的数据 n<10且m<20,80%的数据 n<1000且m<2000,100%的数据 n<100000且m<200000 |
第一问水水哒,直接MST即可。
第二问有两种做法:
做法一:可以猜想改建的地堡在直径上,因此两遍BFS即可
这个解法为什么是对的呢?我有一个绝妙的证明,但这里地方太小写不下。
#include<cstdio>
#include<cctype>
#include<queue>
#include<cstring>
#include<algorithm>
#define rep(s,t) for(int i=s;i<=t;i++)
#define ren for(int i=first[x];i;i=next[i])
using namespace std;
inline int read() {
int x=,f=;char c=getchar();
for(;!isdigit(c);c=getchar()) if(c=='-') f=-;
for(;isdigit(c);c=getchar()) x=x*+c-'';
return x*f;
}
typedef long long LL;
const int maxn=;
const int maxm=;
struct Edge {
int u,v,w;
bool operator < (const Edge& ths) const {return w<ths.w;}
}e[maxm];
int n,m,pa[maxn];
int findset(int x) {return x==pa[x]?x:pa[x]=findset(pa[x]);}
LL ans1;
int first[maxn],to[maxn<<],next[maxn<<],dis[maxn<<],ToT;
void AddEdge(int u,int v,int w) {
to[++ToT]=v;dis[ToT]=w;next[ToT]=first[u];first[u]=ToT;
to[++ToT]=u;dis[ToT]=w;next[ToT]=first[v];first[v]=ToT;
}
int d[maxn],vis[maxn],p[maxn];
int bfs(int x) {
memset(vis,,sizeof(vis));
queue<int> Q;Q.push(x);vis[x]=;d[x]=;
while(!Q.empty()) {
x=Q.front();Q.pop();
ren if(!vis[to[i]]) vis[to[i]]=,Q.push(to[i]),d[to[i]]=d[x]+dis[i],p[to[i]]=x;
}
int ret=;
rep(,n) if(d[i]>d[ret]) ret=i;
return ret;
}
int main() {
n=read();m=read();
rep(,m) e[i].u=read(),e[i].v=read(),e[i].w=read();
rep(,n) pa[i]=i;
sort(e+,e+m+);
rep(,m) {
int x=findset(e[i].u),y=findset(e[i].v);
if(x!=y) pa[x]=y,AddEdge(e[i].u,e[i].v,e[i].w),ans1+=e[i].w;
}
printf("%lld\n",ans1);
int s=bfs(),t=bfs(s),x=t,ans2=1e9;
while(x!=s) {
ans2=min(ans2,max(d[t]-d[x],d[x]));
x=p[x];
}
printf("%d\n",ans2);
return ;
}
做法二:水水哒点分治就好啦。我们可以计算出每个点到其他点的最远距离,记为dp[i]。
当根节点为x时,我们可以更新所有子树的dp值与根节点的dp值,实现要正着反着各扫一遍。
#include<cstdio>
#include<cctype>
#include<queue>
#include<cstring>
#include<algorithm>
#define rep(s,t) for(int i=s;i<=t;i++)
#define ren for(int i=first[x];i;i=next[i])
using namespace std;
inline int read() {
int x=,f=;char c=getchar();
for(;!isdigit(c);c=getchar()) if(c=='-') f=-;
for(;isdigit(c);c=getchar()) x=x*+c-'';
return x*f;
}
typedef long long LL;
const int maxn=;
const int maxm=;
struct Edge {
int u,v,w;
bool operator < (const Edge& ths) const {return w<ths.w;}
}e[maxm];
int n,m,pa[maxn];
int findset(int x) {return x==pa[x]?x:pa[x]=findset(pa[x]);}
LL ans1;
int first[maxn],to[maxn<<],next[maxn<<],dis[maxn<<],ToT;
void AddEdge(int u,int v,int w) {
to[++ToT]=v;dis[ToT]=w;next[ToT]=first[u];first[u]=ToT;
to[++ToT]=u;dis[ToT]=w;next[ToT]=first[v];first[v]=ToT;
}
int f[maxn],vis[maxn],dp[maxn],s[maxn],root,size;
void getroot(int x,int fa) {
s[x]=;int maxs=;
ren if(!vis[to[i]]&&to[i]!=fa) {
getroot(to[i],x);
s[x]+=s[to[i]];maxs=max(maxs,s[to[i]]);
}
f[x]=max(maxs,size-s[x]);
if(f[x]<f[root]) root=x;
}
int mxdep,dep2,A[maxn];
void dfs(int x,int fa,int dep) {
dp[x]=max(dp[x],mxdep+dep);dep2=max(dep,dep2);
ren if(!vis[to[i]]&&to[i]!=fa) dfs(to[i],x,dep+dis[i]);
}
void solve(int x) {
vis[x]=;mxdep=;int TAT=;
ren if(!vis[to[i]]) {
A[TAT++]=i;dep2=;
dfs(to[i],,dis[i]);
mxdep=max(mxdep,dep2);
}
dp[x]=max(dp[x],mxdep);mxdep=;
while(TAT--) {
dep2=;dfs(to[A[TAT]],,dis[A[TAT]]);
mxdep=max(mxdep,dep2);
}
ren if(!vis[to[i]]) {
f[]=size=s[to[i]];getroot(to[i],root=);
solve(root);
}
}
int main() {
n=read();m=read();
rep(,m) e[i].u=read(),e[i].v=read(),e[i].w=read();
rep(,n) pa[i]=i;
sort(e+,e+m+);
rep(,m) {
int x=findset(e[i].u),y=findset(e[i].v);
if(x!=y) pa[x]=y,AddEdge(e[i].u,e[i].v,e[i].w),ans1+=e[i].w;
}
printf("%lld\n",ans1);
f[]=size=n;getroot(,root=);
solve(root);
int ans2=1e9;
rep(,n) ans2=min(ans2,dp[i]);
printf("%d\n",ans2);
return ;
}
其实我认为第二个做法才是正解