bzoj 1977 (浅谈如何hack掉hzwer学长)(严格次小生成树)(LCA+kruskal)

传送门

题解:以下内容出自题解没错但是代码有错却过了bzoj评测的hwzer学长先求出最小生成树,要严格次小。枚举每一条非树边找俩顶点树链上的最大边(如果最大边相同与非树边边权相同则找次大边)然后更新最小增量。最大边和次大边可以通过树上倍增求出。

下证hzwer学长和其他一些同学的错误,以hzwer的代码为例:

void cal(int x,int f,int v)
{
    int mx1=0,mx2=0;
    int t=deep[x]-deep[f];
    for(int i=0;i<=16;i++)
    {
        if(t&(1<<i))
        {
           if(d1[x][i]>mx1)
           {
           	   mx2=mx1;
           	   mx1=d1[x][i];
           }
           mx2=max(mx2,d2[x][i]);
           x=fa[x][i];
        }
    }
    if(mx1!=v)mn=min(mn,v-mx1);
    else mn=min(mn,v-mx2);
}
先用文字说一遍:当之前已算过的最大值大于当期的最大值时,当前最大值可以用来更新次大值,他并没有这么做。
再用代码解释一遍:当mx1>d1[x][i]时,d1[x][i]可以用来更新mx2,而他并没有这么做。

再说说如何hack掉这类代码:

方案1.出一组如下数据(就是将样例的点的编号进行一些交换):


5 6
1 2 2
1 5 4
2 3 1
3 4 3
1 4 3
5 4 6

答案是11,典型错误是12。

方案2.直接将hzwer的dfs(1)改为dfs(3),次小生成树的大小与根的选取无关,但是它跑出了错误的答案(12)。


P.S.WA了一大晚上因为kruskal没sort。。。但是在调试过程中有了这么多新发现也算值了。

下面是严格的正解

#include<cstdio>
#include<cstring>
#include<iostream>
#include<algorithm>
#include<utility>
using namespace std;
typedef long long ll;
#define pii pair<int ,int >
#define mp(x,y) make_pair(x,y)
const int MAXN=1e5+4,MAXM=3e5+4;
int n,m,mm=0,root;
int head[MAXN],etot=0;
struct EDGE {
    int v,nxt,w;
}e[MAXN<<1];
int dep[MAXN],mx[18][MAXN],ms[18][MAXN],f[18][MAXN];
ll mst=0;
int delta=0x3f3f3f3f;
inline int read() {
	int x=0;char c=getchar();
	while (c<'0'||c>'9') c=getchar();
	while (c>='0'&&c<='9') x=x*10+c-'0',c=getchar();
	return x;
}
inline void adde(int u,int v,int w) {
	e[etot].nxt=head[u],e[etot].v=v,e[etot].w=w,head[u]=etot++;
	e[etot].nxt=head[v],e[etot].v=u,e[etot].w=w,head[v]=etot++;
}
/*---------------kruskal---------------*/
struct M_EDGE {
	int u,v,w;
	friend bool operator <(const M_EDGE &a,const M_EDGE &b) {
		return a.w<b.w;
	}
}d[MAXM];
int fa[MAXN];
bool sel[MAXM];
inline int find(int x) {
	return fa[x]==x?x:fa[x]=find(fa[x]);
}
inline void kruskal() {
	sort(d+1,d+m+1);
	memset(sel,false,sizeof(sel));
	int blo=n;
	for (register int i=1;i<=m&&blo>1;++i) {
		int u=find(d[i].u),v=find(d[i].v);
		if (u^v) {
			mst+=d[i].w;
			fa[u]=v,--blo;
			adde(d[i].u,d[i].v,d[i].w);
			sel[i]=true;
			root=d[i].u;
		}
	}
}
/*---------------LCA---------------*/
void dfs(int p,int father) {
	f[0][p]=father,dep[p]=dep[father]+1;
	for (int i=head[p];~i;i=e[i].nxt) {
		int v=e[i].v;
		if (v^father) {
			mx[0][v]=e[i].w;
			dfs(v,p);
		}
	}
}
inline void da() {
	for (int j=1;(1<<j)<=n;++j)
		for (register int i=1;i<=n;++i) {
			mx[j][i]=max(mx[j-1][i],mx[j-1][f[j-1][i]]);
			ms[j][i]=max(ms[j-1][i],ms[j-1][f[j-1][i]]);
			if (mx[j-1][i]^mx[j-1][f[j-1][i]]) ms[j][i]=max(ms[j][i],min(mx[j-1][i],mx[j-1][f[j-1][i]]));
			f[j][i]=f[j-1][f[j-1][i]];
		}
}
inline void update(int x,int y,int weight) {
	int m1=0,m2=0,a,b,c,d;
	if (dep[x]<dep[y]) x^=y^=x^=y;
	int t=dep[x]-dep[y];
	for (int i=0;i<=17;++i)
		if (t&(1<<i)) {
			a=mx[i][x],b=ms[i][x],c=m1,d=m2;
			m1=max(a,c),m2=max(b,d);
			if (a^c) m2=max(m2,min(a,c));
			x=f[i][x];
		}
	if (x==y) {
		if (m1^weight) delta=min(delta,weight-m1);
		delta=min(delta,weight-m2);
		return ;
	}
	for (int i=17;~i;--i)
		if (f[i][x]^f[i][y]) {
			a=mx[i][x],b=ms[i][x],c=m1,d=m2;
			m1=max(a,c),m2=max(b,d);
			if (a^c) m2=max(m2,min(a,c));
			a=mx[i][y],b=ms[i][y],c=m1,d=m2;
			m1=max(a,c),m2=max(b,d);
			if (a^c) m2=max(m2,min(a,c));
			x=f[i][x],y=f[i][y];
		}
	a=mx[0][x],b=ms[0][x],c=m1,d=m2;
	m1=max(a,c),m2=max(b,d);
	if (a^c) m2=max(m2,min(a,c));
	a=mx[0][y],b=ms[0][y],c=m1,d=m2;
	m1=max(a,c),m2=max(b,d);
	if (a^c) m2=max(m2,min(a,c));
	if (m1^weight) delta=min(delta,weight-m1);
	delta=min(delta,weight-m2);
}
int main() {
//	freopen("bzoj 1977.in","r",stdin);
	memset(head,-1,sizeof(head));
	n=read(),m=read();
	for (register int i=1;i<=m;++i) d[i].u=read(),d[i].v=read(),d[i].w=read();
	for (register int i=1;i<=n;++i) fa[i]=i;
	kruskal();
	memset(ms,0,sizeof(ms));
	memset(mx,0,sizeof(mx));
	dep[0]=0;
	dfs(3,0);//dfs谁都可以
	da();
	for (int i=1;i<=m;++i) {
		if (sel[i]) continue;
		update(d[i].u,d[i].v,d[i].w);
	}
	printf("%lld\n",mst+delta);
	return 0;
}


评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值