题解 P4180 【【模板】严格次小生成树[BJWC2010]】

Jμdge

2018-04-19 09:38:57

Solution

然鹅刚刚有同学指出我的代码有bug,样例都过不了(然鹅还是AC了我也是...) 于是看了看,确实某个地方有点草率,于是把自己修改正确了的代码放到第一个,错的第二个,还有另一个放在第三个。。。 (楼下一堆大佬有没有出错的我就不知道了,但是貌似出错的程序也是能过的)。于是来发篇程序里头注解比较多的题解 。如果是那种思路的话还是看楼下大佬的吧,我的题解一般来讲是帮助童鞋们理解程序构造云云的...(QAQ 不喜勿喷) 但是这次还是好好分析一下吧~~(哪怕是按着代码思路来一遍)~~。 首先的首先,这个blog大家可以先看看(对于次小生成树的分析比我详细,看完这篇blog再看本蒟蒻的题解效果更佳。QAQ```_(:з」∠)_```): [某大佬的blog](https://blog.csdn.net/zhongshijunacm/article/details/12992571) ------------ ``` 首先的话,想必做到这题的童鞋都知道最小生成树mst怎么写了吧。 那么其实次小生成树也是可以分为两种的(严格和非严格),怎么说呢... as we all know,mst是指一个图中所有包含全部顶点的树中边权和最小的树, 那么我们所说的严格次小生成树就是边权之和严格大于(也就是不能等于),但是又是满足该条件的生成树里边权之、和最小的树。 比如说某图的最小生成树的边权和为88,还有其他的几个生成树的边权和分别为88、88、90、100、103、111, 那么边权和为90的生成树就是该图的严格次小生成树, 而另外另一棵边权和为88的生成树就是该图的非严格次小生成树(此时这棵树的边权和 与 mst的边权和相等了呀)。 那么易知当一个图的mst唯一时,该图的严格次小生成树与非严格次小生成树就区别不大了。 那么怎么求一个图的次小生成树捏?以下是步骤: 1.首先读边什么的就不说了,那么第一步就是用kruskal跑一遍mst,并且把用到的边打上标记,然后还要记录下mst的边权和; 2.其次再把这些打上标记的边存到该边指向的两个顶点的后面(两个顶点都要存),以便接下来深搜建树用。 3.深搜建树,并维护好当前节点的一系列信息(比如说当前点祖先的信息啦、从当前点到某个祖先的路径中的最大边权和次大边权啦之类的) 4.再然后就是枚举无用边(最小树中没有用到的边),然后倍增找当前枚举到的边所指向的两个端点的lca并且求出路径中最大边权和次大边权, 当前枚举到的边减去找出的最大(或许是次大)的边权就是该边的贡献,然后最小贡献记录进ans里。 5.printf 边权和res + (非零的)最小贡献ans ,完毕 ``` ------------ ``` //by Judge #include<iostream> #include<algorithm> #include<vector> #include<cstdio> typedef long long ll; using namespace std; const int M=1e5+100; ll n,m,res,ans=0x3f3f3f3f,mx; int f[M],fa[25][M],dep[M]; ll d[2][25][M]; bool used[3*M],vis[M]; vector<int> a[M]; struct Edge{ int from, to; ll val; bool operator < (const Edge y){ return val < y.val; } }e[3*M]; int F(int x){ if(f[x]==x) return x; return f[x]=F(f[x]); } void kruskal(){ //kruskal 算最大生成树(已保证任意两点之间最小限重最优) sort(e,e+m); int lef=n-1; for(int i=1;i<=n;++i) f[i]=i; for(int i=0;i<m && lef;++i){ int x=F(e[i].from),y=F(e[i].to); if(x!=y){ f[x]=y; res+=e[i].val; used[i]=1; --lef; mx=max(mx , e[i].val); } } } void dfs(int x){ //深搜建树(可能不止一棵,因为数据未保证是连通图) vis[x]=true; for(int i=1;i<=23;++i){ fa[i][x]=fa[i-1][fa[i-1][x]]; ll t1=d[0][i-1][x], t2=d[0][i-1][fa[i-1][x]]; d[0][i][x]=max(t1 , t2); d[1][i][x]=max(d[1][i-1][x] , d[1][i-1][fa[i-1][x]]); if(t1!=t2) d[1][i][x]=max(d[1][i][x] , min(t1 , t2)); } for(int i=0;i<a[x].size();++i){ int t=e[a[x][i]].to+e[a[x][i]].from-x; if(vis[t]) continue; //vis为1表示是父节点 dep[t]=dep[x]+1; fa[0][t]=x; d[0][0][t]=e[a[x][i]].val; dfs(t); } } int lca(int u,int v){ if(dep[u]<dep[v]) swap(u,v); if(dep[u]!=dep[v]){ //将深度做相等 for(int i=23,h=dep[u]-dep[v];i>=0;--i) if(h&(1<<i)) u=fa[i][u]; } if(u==v) return u; //如果已经在一个节点上就直接返回 for(int i=23;i>=0;--i) if(fa[i][u]!=fa[i][v]) u=fa[i][u] , v=fa[i][v]; return fa[0][u]; } ll get(int u,int v,int c){ int fht=lca(u,v); ll m1=0,m2=0; for(int i=23,h1=dep[u]-dep[fht],h2=dep[v]-dep[fht];i>=0;--i){ if(h1&(1<<i)){ if(d[0][i][u]>m1) m2=m1,m1=d[0][i][u]; else if(d[0][i][u]>m2) m2=d[0][i][u]; else m2=max(m2 , d[1][i][u]); } if(h2&(1<<i)){ if(d[0][i][v]>m1) m2=m1,m1=d[0][i][v]; else if(d[0][i][v]>m2) m2=d[0][i][v]; else m2=max(m2 , d[1][i][v]); } } if(m1==c) return c-m2; else return c-m1; } int main(){ scanf("%d%d",&n,&m); for(int i=0;i<m;++i){ int u,v; ll w; scanf("%d%d%lld",&u,&v,&w); e[i].from=u; e[i].to=v; e[i].val=w; } kruskal(); for(int i=0;i<m;++i) if(used[i]){ a[e[i].from].push_back(i); a[e[i].to].push_back(i); } dep[1]=1; dfs(1); for(int i=0;i<m;++i) if(!used[i]){ if(e[i].val-mx>ans) break; ll t=get(e[i].from , e[i].to , e[i].val); ans=min(ans , t); } return printf("%lld\n",res+ans),0; } ``` 原来错的我也不删了,同学们可以开个文本比对看看哪里不一样 ```cpp //by Judge #include<iostream> #include<algorithm> #include<vector> #include<cstdio> typedef long long ll; using namespace std; const int M=1e5+100; ll n,m,res,ans=0x3f3f3f3f,mx; int f[M],fa[25][M],dep[M]; ll d[2][25][M]; bool used[3*M],vis[M]; vector<int> a[M]; struct Edge{ int from, to; ll val; bool operator < (const Edge y){ return val < y.val; } }e[3*M]; int F(int x){ //并查集标准模板 if(f[x]==x) return x; return f[x]=F(f[x]); } void kruskal(){ //kruskal 算mst, 基本就是模板 sort(e,e+m); int lef=n-1; for(int i=1;i<=n;++i) f[i]=i; for(int i=0;i<m && lef;++i){ int x=F(e[i].from),y=F(e[i].to); if(x!=y){ f[x]=y; res+=e[i].val; used[i]=1; --lef; mx=max(mx , e[i].val); //这里处理出的mx作剪枝用 } } } void dfs(int x){ //深搜建树,从1开始 vis[x]=true; for(int i=1;i<=23;++i){ fa[i][x]=fa[i-1][fa[i-1][x]]; ll t1=d[0][i-1][x], t2=d[0][i-1][fa[i-1][x]]; d[0][i][x]=max(t1 , t2); d[1][i][x]=max(d[1][i-1][x] , d[1][i-1][fa[i-1][x]]); if(t1!=t2) d[1][i][x]=max(d[1][i][x] , min(t1 , t2)); } for(int i=0;i<a[x].size();++i){ int t=e[a[x][i]].to+e[a[x][i]].from-x; if(vis[t]) continue; //vis为1表示是父节点 dep[t]=dep[x]+1; fa[0][t]=x; d[0][0][t]=e[a[x][i]].val; dfs(t); } } int lca(int u,int v){ if(dep[u]<dep[v]) swap(u,v); if(dep[u]!=dep[v]){ //将深度做相等 for(int i=23,h=dep[u]-dep[v];i>=0;--i) if(h&(1<<i)) u=fa[i][u]; } if(u==v) return u; //如果已经在一个节点上就直接返回 for(int i=23;i>=0;--i) if(fa[i][u]!=fa[i][v]) u=fa[i][u] , v=fa[i][v]; return fa[0][u]; } ll get(int u,int v,int c){ int fht=lca(u,v); ll m1=-1,m2=-1; //m1是最大边权,m2是次大边权 // 倍增求最大边权和次大边权 for(int i=23,h1=dep[u]-dep[fht],h2=dep[v]-dep[fht];i>=0;--i){ if(h1&(1<<i)){ if(d[0][i][u]>m1) m2=m1,m1=d[0][i][u]; m2=max(m2 , d[1][i][u]); } if(h2&(1<<i)){ if(d[0][i][v]>m1) m2=m1,m1=d[0][i][v]; m2=max(m2 , d[1][i][v]); } } if(m1!=c) return c-m1; //最大边权和当前关键边的权值相等返回次大边权的贡献(因为本题的次小生成树是严格的) if(m2!=-1) return c-m2; //不然直接返回最大边权的贡献(这里是hack的关键哦,因为m1==c且m2==-1时代表该环只有一种权值) //另外这个分函数里也可以把-1都改成0,别问我为什么,数据水你问数据去。 return 0; //不然嘛...返回个0(当然很明显,无解的情况也能由此判断,加些语句就ok了) } int main(){ scanf("%d%d",&n,&m); for(int i=0;i<m;++i){ //读边 int u,v; ll w; scanf("%d%d%lld",&u,&v,&w); e[i]=(Edge){u , v , w}; } kruskal(); //跑一边最小生成树 for(int i=0;i<m;++i) if(used[i]){ //记录下mst用到的边,分别加到两个点后面 a[e[i].from].push_back(i); a[e[i].to].push_back(i); } dep[1]=1; dfs(1); //深搜根节点(1) for(int i=0;i<m;++i) if(!used[i]){ //然后枚举无用边作为关键边 if(e[i].val-mx>ans) break; //剪枝,贡献不可能再小的时候直接break ll t=get(e[i].from , e[i].to , e[i].val); //求出贡献 if(t) ans=min(ans , t); //最小贡献存进ans } printf("%lld\n",res+ans); //输出最小生成树的边权和加上(非零的)最小贡献就是次小生成树 return 0; } ``` ------------ 然后发一波老师被我hack掉的代码~ (滑稽) ------------ ```cpp #include<cstdio> #include<cstring> #include<iostream> #include<algorithm> const int maxn=100010,maxm=300010,maxk=22; using namespace std; typedef long long ll; int n,m,pre[maxm],now[maxn],son[maxm],val[maxm],delta=(int)1e9+7,tot;ll ans; int fa[maxn][maxk],fim[maxn][maxk],sem[maxn][maxk],dep[maxn],f[maxn];bool in[maxm]; struct Edge{int x,y,v;}E[maxm]; bool operator <(Edge a,Edge b){return a.v<b.v;} void add(int a,int b,int c){pre[++tot]=now[a],now[a]=tot,son[tot]=b,val[tot]=c;} int getfa(int x){return f[x]==x?x:f[x]=getfa(f[x]);} void dfs(int x){ for (int i=1;i<=18;i++){ fa[x][i]=fa[fa[x][i-1]][i-1]; int t1=fim[x][i-1],t2=fim[fa[x][i-1]][i-1]; fim[x][i]=max(t1,t2); sem[x][i]=max(sem[x][i-1],sem[fa[x][i-1]][i-1]); if (t1!=t2) sem[x][i]=max(sem[x][i],min(t1,t2)); } for (int y=now[x];y;y=pre[y]) if (son[y]!=fa[x][0]){ dep[son[y]]=dep[x]+1,fa[son[y]][0]=x; fim[son[y]][0]=val[y],dfs(son[y]); } } void kruskal(){ sort(E+1,E+1+m);int cnt=0; for (int i=1;i<=n;i++) f[i]=i; for (int i=1;i<=m&&cnt<n-1;i++){ int x=E[i].x,y=E[i].y,v=E[i].v; if (getfa(x)==getfa(y)) continue; cnt++,f[getfa(x)]=getfa(y),ans+=v,in[i]=1; add(x,y,v),add(y,x,v); } } int lca(int x,int y){ if (dep[x]<dep[y]) swap(x,y); for (int h=dep[x]-dep[y],i=18;i>=0&&h;i--) if (h&(1<<i)) x=fa[x][i]; if (x==y) return x; for (int i=18;i>=0;i--) if (fa[x][i]!=fa[y][i]) x=fa[x][i],y=fa[y][i]; return fa[x][0]; } void query(int x,int u,int v){ int max1=0,max2=0; for (int i=18,h=dep[x]-dep[u];i>=0;i--) if (h&(1<<i)){ if (fim[x][i]>max1) max2=max1,max1=fim[x][i]; max2=max(max2,sem[x][i]),h-=(1<<i); } if (v==max1) delta=min(delta,v-max2); else delta=min(delta,v-max1); } void solve(int id){ int x=E[id].x,y=E[id].y,v=E[id].v,u=lca(x,y); query(x,u,v),query(y,u,v); } int main(){ scanf("%d%d",&n,&m); for (int i=1,x,y,z;i<=m;i++) scanf("%d%d%d",&x,&y,&z),E[i]=(Edge){x,y,z}; kruskal(),dfs(1); for (int i=1;i<=m;i++) if (!in[i]) solve(i); printf("%lld\n",ans+delta); return 0; } ``` ------------ 这就hack了?如果你不信的话这里有组数据: ``` input:复制(手动) 5 6 1 2 3 1 3 3 2 3 3 3 4 3 3 5 3 4 5 8 the answer: 粘贴(滑稽) 17 wrong answer: 15 ```