大力水手问禅师:“大师,很多事情都需要用很大力气才能完成,而我在吃了菠菜之后力气很大,于是就导致我现在非常依赖菠菜。我很讨厌我的现状,有没有办法少吃点菠菜甚至不吃菠菜却仍很有力气?”
禅师浅笑,答:“方法很简单,不过若想我教你,你需先下山徒手修路。”
山下是 n 座村庄从 1 到 n 编号,之间没有路相连。禅师给了大力水手一张草图,这张草图里 n 座村庄被 n−1 条双向道路连接,任意一个村庄都可以通过双向道路到达其它所有村庄。
现在大力水手要根据禅师的意思在村庄间修路。禅师规定大力水手需要在 m 天内完成任务,其中大力水手的修路方式如下:
- 第 i 天,禅师指定了两个村庄 vi 和 ui,在草图上 vi 号村庄到 ui 号村庄的最短路径上的所有村庄(包括 vi 和 ui)中,大力水手需要选出若干对村庄(一个村庄可以被重复选多次,当然大力水手在这天也可以一对村庄都不选),然后在选出的每一对村庄间修建双向道路。
- 在实地考察中大力水手发现,有 p 个限制关系 (ti,ai,bi),表示在第 ti 天无法在 ai 号村庄到 bi 号村庄间修路(路是双向的,所以自然也无法在 bi 号村庄到 ai 号村庄间修路)。
- 每一天都有个修理所需力气值 wi,表示在第 i 天每修建一条道路都要耗费 wi 点力气值。
大力水手开始蛮力干了起来,一罐又一罐地吞食菠菜,结果经常修建一些无用的道路,每天都累得筋疲力尽。
作为一个旁观者,请你帮大力水手求出要想让 m 天后任意一对村庄之间都可以互相到达,所需要的总力气值最少是多少。注意最后修出来的道路不必和草图一致。
输入格式
第一行三个非负整数 n,m,p。保证 n≥1。
接下来一行 n−1 个整数,其中第 i 个整数 fi+1 (1≤fi+1≤i)表示草图中 i+1 号村庄与 fi+1 号村庄间有一条双向道路。
接下来 m 行,第 i 行包含三个整数 vi,ui,wi (1≤vi,ui≤n,vi≠ui,1≤wi≤109)表示第 i 天禅师指定了 vi 号村庄和 ui 号村庄,大力水手修一条路耗费 wi 点力气值。
接下来 p 行,每行包含三个整数 ti,ai,bi 表示一个限制关系。保证 1≤ti≤m,1≤ai,bi≤n,ai≠bi,且草图上 ai 号村庄和 bi 号村庄都在 vti 号村庄到 uti 号村庄的最短路径上。另外,保证输入中不会出现重复的限制关系,即不会有两个限制关系 i,j 满足 ti=tj,ai=aj,bi=bj 或 ti=tj,ai=bj,bi=aj。
输出格式
输出一行一个整数,表示所需要的最小总力气值。保证至少存在一种修路的方法使得任意一对村庄之间都可以互相到达。
C/C++ 输入输出 long long 时请用 %lld
。C++ 可以直接使用 cin/cout 输入输出。
样例一
input
5 2 3 1 1 3 3 2 4 1 5 4 2 1 3 2 1 3 1 1 3 4
output
6
explanation
第一天大力水手本来可以在 (1,2),(1,3),(1,4),(2,3),(2,4),(3,4) 间修路,但是由于第一天不能在 (3,2),(3,1) 和 (3,4) 间修路,所以可能的选择只有 (1,2),(1,4),(2,4)。对于第二天,大力水手可能的选择有 (3,4),(3,5),(4,5)。
一种可能的最优方案是,第一天大力水手在 (1,2),(1,4) 间修路,第二天在 (3,4),(4,5) 间修路,总共耗费 1+1+2+2=6 点力气值。
样例二
见样例数据下载。这个样例中 fi+1=i。
样例三
见样例数据下载。
限制与约定
测试点编号 | n | m | p | 其他 |
---|---|---|---|---|
1 | n≤100 | m≤100 | p≤100 | 无 |
2 | n≤300000 | m≤300000 | p=0 | 无 |
3 | ||||
4 | ||||
5 | n≤300000 | m≤300000 | p≤300000 | 保证对于 1<i≤n, fi+1=i |
6 | ||||
7 | n≤300000 | m≤300000 | p≤300000 | 无 |
8 | ||||
9 | ||||
10 |
时间限制:1s
空间限制:256MB
后记
由于你的帮助,大力水手顺利修完了道路而且使用的力气值是原定计划的 0.01%。
大力水手对禅师说:“我明白了!我以前都是在使用蛮力,从今往后我要多思索,多使用巧力解决问题。”
禅师摆摆手,嘿嘿一笑:“对不起,我只是想请你帮忙修路而已。”
大力水手吃了一罐菠菜,把禅师打死了。
跪zsy,我终于知道怎样更有力气了.
考虑离线处理,先将所有天按w从小到大排序。设k为第i天的所有限制数,设u表示排序后第i天的u,v同理。
1、若dist(u,v)>k : 所有(u,v)的路径上的节点全部能连通。
2、若dist(u,v)<=k:这样总共的路径上点数=sigma(dist(u,v))<=p,我们只需想出一个只与路径上点数相关的算法。
对于1的情况,可以从下向上连边,用findset(tree,x)表示x向上第一个不与x连通的节点,findset(st,x)表示所有与x连通的节点,最多连n-1条边,故暴力连即可。
对于2的情况,我们把(u,v)的路径上的节点扔到一起并重新编号,考虑从每个点暴力向第一个不等于它的没有走过的点,判断是否有边(即是否有边的限制),用findset(path,x)表示大于x的第一个没有被访问过的点。
#include<cstdio> #include<cstring> #include<algorithm> #include<cctype> #define rep(s,t) for(int i=s;i<=t;i++) #define ren for(int i=first[x];i!=-1;i=next[i]) using namespace std; inline int read() { int x=0,f=1;char c=getchar(); for(;!isdigit(c);c=getchar()) if(c==‘-‘) f=-1; for(;isdigit(c);c=getchar()) x=x*10+c-‘0‘; return x*f; } const int maxn=300010; int n,m,p,First[maxn],Next[maxn],to[maxn],e,dep[maxn],fa[maxn],anc[maxn][20],cnt; void AddEdge(int u,int v) { to[++e]=v;Next[e]=First[u];First[u]=e; } int first[maxn],next[maxn],u1[maxn],v1[maxn]; void dfs(int x) { anc[x][0]=fa[x];dep[x]=dep[fa[x]]+1; rep(1,19) anc[x][i]=anc[anc[x][i-1]][i-1]; for(int i=First[x];i;i=Next[i]) dfs(to[i]); } int LCA(int x,int y) { if(dep[x]<dep[y]) swap(x,y); for(int i=19;i>=0;i--) if(dep[x]-dep[y]>=1<<i) x=anc[x][i]; for(int i=19;i>=0;i--) if(anc[x][i]!=anc[y][i]) x=anc[x][i],y=anc[y][i]; return x==y?x:fa[x]; } int lim[maxn],st[maxn],tree[maxn],path[maxn]; void Addop(int day,int u,int v) { lim[day]++; u1[++cnt]=u;v1[cnt]=v;next[cnt]=first[day];first[day]=cnt; } struct Pair { int x,y; bool operator < (const Pair& ths) const { if(x!=ths.x) return x<ths.x; return y<ths.y; } }B[maxn*2]; int ToT,len; struct Day { int u,v,w,id; bool operator < (const Day& ths) const {return w<ths.w;} }A[maxn]; long long ans; int findset(int* pa,int x) {return pa[x]<0?x:pa[x]=findset(pa,pa[x]);} int merge(int u,int v,int w) { if((u=findset(st,u))!=(v=findset(st,v))) ans+=w,st[u]=v; } int list[maxn],id[maxn],T[maxn],L[maxn],R[maxn]; void dfs(int u,int v,int w) { if(u!=v) merge(list[u],list[v],w); path[findset(path,u)]=u+1; for(int i=findset(path,0);i<=len;i=findset(path,i+1)) { int p=lower_bound(T+L[u],T+R[u]+1,i)-T; if(T[p]!=i) dfs(i,v,w); } } int main() { n=read();m=read();p=read(); rep(2,n) AddEdge(fa[i]=read(),i); dfs(1);rep(1,n) st[i]=tree[i]=-1; rep(1,m) A[A[i].id=i].u=read(),A[i].v=read(),A[i].w=read(); sort(A+1,A+m+1); rep(1,p) { int day=read(); Addop(day,read(),read()); } rep(1,m) { int u=A[i].u,v=A[i].v,w=A[i].w,day=A[i].id; int lca=LCA(u,v);len=dep[u]+dep[v]-dep[lca]*2; if(len>lim[day]) { while(dep[u=findset(tree,u)]>dep[lca]) merge(u,tree[u]=fa[u],w); while(dep[v=findset(tree,v)]>dep[lca]) merge(v,tree[v]=fa[v],w); } else { int tot=0;ToT=0; while(u!=lca) id[list[tot]=u]=tot++,u=fa[u]; while(v!=lca) id[list[tot]=v]=tot++,v=fa[v]; id[list[tot]=lca]=tot++; for(int i=first[day];i;i=next[i]) { int u2=id[u1[i]],v2=id[v1[i]]; B[++ToT]=(Pair){u2,v2};B[++ToT]=(Pair){v2,u2}; } for(int i=0;i<=len+1;i++) path[i]=-1; sort(B+1,B+ToT+1);T[1]=B[1].y;L[B[1].x]=1; for(int i=2;i<=ToT+1;i++) { T[i]=B[i].y; if(B[i].x!=B[i-1].x) { R[B[i-1].x]=i-1; if(i!=ToT+1) L[B[i].x]=i; } } for(int i=0;i<=len;i++) if(findset(path,i)==i) dfs(i,i,w); } } printf("%lld\n",ans); return 0; }