Problem
Solution
注意到k只有20,那么对于这个图,很多点是由普通边连接的,那么我们可以先强制把所有的特殊边加入,然后把普通边做一次mst,则其他未加入的边肯定是不会做贡献的,把这些由普通边连起来的点缩在一起的,不妨称为新图。
新图只有 t o t ( t o t ≤ k + 1 ) tot(tot\leq k+1) tot(tot≤k+1)个点,但是由于特殊边的选择方案可能导致树的形态发生改变,所以我们就枚举这k条边选择的方案,然后如果成环就直接continue,如果还未联通就再加入普通边,因此我们需要在新图上再做一遍普通边的mst。
然后由于这个图是最小生成树,所以对于一条特殊边连接了x,y,那么它的费用必须小于等于所有可以覆盖x,y的普通边的费用,而这些边只可能是新图普通边的mst做贡献。为什么?考虑在新图上做kruskal的过程,每条特殊边都肯定会被最小的一条边覆盖到。所以做多次路径覆盖即可。
时间复杂度
O
(
m
log
m
+
2
k
k
2
)
O(m\log m+2^k k^2)
O(mlogm+2kk2),这个上界比较松,所以稍微注意点常数是可以通过的。如果你追求正确的复杂度,20个点的树上做树剖/倍增了解一下?
Code
#include <algorithm>
#include <cstring>
#include <cstdio>
#define rg register
using namespace std;
typedef long long ll;
const int maxn=100010,maxm=300010;
template <typename Tp> inline int getmin(Tp &x,Tp y){return y<x?x=y,1:0;}
template <typename Tp> inline int getmax(Tp &x,Tp y){return y>x?x=y,1:0;}
template <typename Tp> inline void read(Tp &x)
{
x=0;int f=0;char ch=getchar();
while(ch!='-'&&(ch<'0'||ch>'9')) ch=getchar();
if(ch=='-') f=1,ch=getchar();
while(ch>='0'&&ch<='9') x=x*10+ch-'0',ch=getchar();
if(f) x=-x;
}
struct data{int v,nxt;}edge[60];
struct Edge{
int u,v,w;
bool operator < (const Edge &t)const{return w<t.w;}
}a[maxm<<1],b[30],c[30];
struct UFS{
int f[maxn];
void clear(int n){for(rg int i=1;i<=n;i++) f[i]=i;}
inline int find(int x){return f[x]==x?x:f[x]=find(f[x]);}
int merge(int x,int y)
{
int fx=find(x),fy=find(y);
if(fx==fy) return 0;
return f[fy]=fx;
}
}GS,NS,TS;
int n,m,k,p,tot,lim,head[30],f[30],dep[30],mx[30],us[30],vis[maxn];
ll ans,num[30],sz[30];
inline void insert(int u,int v)
{
edge[++p]=(data){v,head[u]};head[u]=p;
edge[++p]=(data){u,head[v]};head[v]=p;
}
void input()
{
read(n);read(m);read(k);lim=1<<k;
for(rg int i=1;i<=m;i++){read(a[i].u);read(a[i].v);read(a[i].w);}
for(rg int i=1;i<=k;i++){read(b[i].u);read(b[i].v);}
}
void kruskal()
{
int x,tmp=0;
sort(a+1,a+m+1);
GS.clear(n);NS.clear(n);
for(rg int i=1;i<=k;i++) GS.merge(b[i].u,b[i].v);
for(rg int i=1;i<=m;i++)
if(GS.merge(a[i].u,a[i].v))
NS.merge(a[i].u,a[i].v);
for(rg int i=1;i<=n;i++)
if(!vis[NS.find(i)]) vis[NS.find(i)]=++tot;
for(rg int i=1;i<=k;i++)
b[i].u=vis[NS.find(b[i].u)],b[i].v=vis[NS.find(b[i].v)];
for(rg int i=1;i<=n;i++){read(x);num[vis[NS.find(i)]]+=x;}
for(rg int i=1;i<=n;i++) TS.f[i]=NS.f[i];
for(rg int i=1;i<=m;i++)
if(NS.find(a[i].u)^NS.find(a[i].v))
{
c[++tmp].w=a[i].w;
c[tmp].u=vis[TS.find(a[i].u)];c[tmp].v=vis[TS.find(a[i].v)];
NS.merge(a[i].u,a[i].v);
if(tmp==tot-1) break;
}
}
void dfs(int x)
{
dep[x]=dep[f[x]]+1;
for(int i=head[x];i;i=edge[i].nxt)
if(edge[i].v^f[x])
{
f[edge[i].v]=x;
dfs(edge[i].v);
}
}
ll dp(int x)
{
ll res=0ll;sz[x]=num[x];
for(int i=head[x];i;i=edge[i].nxt)
if(edge[i].v^f[x])
{
res+=dp(edge[i].v);
sz[x]+=sz[edge[i].v];
}
return res+(us[x]?sz[x]*mx[x]:0ll);
}
void color(int x,int y,int w)
{
if(dep[x]<dep[y]) swap(x,y);
while(dep[x]>dep[y]){getmin(mx[x],w);x=f[x];}
while(x^y){getmin(mx[x],w);x=f[x];getmin(mx[y],w),y=f[y];}
}
int main()
{
#ifndef ONLINE_JUDGE
freopen("in.txt","r",stdin);
#endif
int cnt;
input();
kruskal();
for(rg int s=1;s<lim;s++)
{
cnt=p=0;TS.clear(tot);
memset(head,0,sizeof(head));
memset(mx,0x3f,sizeof(mx));memset(us,0,sizeof(us));
for(int i=1;i<=k;i++)
if(s&(1<<i-1))
{
if(!TS.merge(b[i].u,b[i].v)){cnt=-1;break;}
else{++cnt;insert(b[i].u,b[i].v);}
}
if(cnt==-1) continue;
for(int i=1;i<tot&&cnt<tot-1;i++)
if(TS.merge(c[i].u,c[i].v)) insert(c[i].u,c[i].v);
dfs(1);
for(int i=1;i<tot;i++) color(c[i].u,c[i].v,c[i].w);
for(int i=1;i<=k;i++)
if(s&(1<<i-1))
{
if(f[b[i].u]==b[i].v) us[b[i].u]=1;
else us[b[i].v]=1;
}
getmax(ans,dp(1));
}
printf("%lld\n",ans);
return 0;
}