4.10 模拟赛 最小生成树 克鲁斯卡尔思想 优化建图

avatar
avatar

这道题超级的妙。

30分可以发现题目中的建图最多建n条 剩下的都没用 所以暴力建nQ条边跑克鲁斯卡尔即可。

对于Q<=50 发现此时边数为为2e7 跑克鲁斯卡尔必然GG.

考虑prim求最小生成树 复杂度nlogn+2e7.

这样就可以暴力得到50分了。

最后考虑一下(ai-bi)<=1的情况。

可以发现此时连边有特点 对于ai<bi 连边是 2,1 3,2 4,3....f[i][0]表示i这个点和其父亲连的边权即可。

对于ai==bi 和上面情况几乎一样。

对于ai>bi 其中一种情况和上面一样 还会发现有 4,2 5,3 6,4这种连边方式 f[i][1]表示i这个点和其父亲的父亲的连的边权即可。

利用f数组进行更新其他的f值 可以发现存在环 所以dp两次即可 我当时傻了 直接开堆放f数组 最小的来更新了 也行。

然后再做最小生成树 复杂度nlogn.

80分代码:

const int maxn=200010;
int n,flag,Q,vis[maxn];
vector<pii>g[maxn];
struct wy{int x,y,z;}t[maxn];
int d[maxn];ll sum;
priority_queue<pii>q;
int f[maxn][2];
inline void add(int x,int y,int z)
{
	if(z>=INF)return;
	if(x==y)return;
	g[x].pb(mk(y,z));
	g[y].pb(mk(x,z));
}
inline void prim()
{
	rep(1,n,i)d[i]=INF,vis[i]=0;
	d[1]=0;q.push(mk(-d[1],1));
	while(q.size())
	{
		int x=q.top().S;q.pop();
		if(vis[x])continue;
		vis[x]=1;sum+=d[x];
		for(int j=0;j<g[x].size();++j)
		{
			int tn=g[x][j].F,e=g[x][j].S;
			if(d[tn]>e)
			{
				d[tn]=e;
				q.push(mk(-d[tn],tn));
			}
		}
	}
}
int main()
{
	freopen("spanning.in","r",stdin);
	freopen("spanning.out","w",stdout);
	get(n);get(Q);
	rep(1,Q,i)
	{
		int x,y,z;
		get(x);get(y);get(z);
		t[i]=(wy){x,y,z};
		if(abs(x-y)>1)flag=1;
	}
	if(!flag)
	{
		rep(0,n-1,i)f[i][0]=f[i][1]=INF;
		rep(1,Q,i)
		{
			int x,y,z;
			x=t[i].x;y=t[i].y;z=t[i].z;
			if(x==y){f[(x+1)%n][0]=min(f[(x+1)%n][0],z+1);continue;}
			if(x<y)f[y][0]=min(f[y][0],z);
			else
			{
				f[x][0]=min(f[x][0],z);
				f[(x+1)%n][1]=min(f[(x+1)%n][1],z+1);
			}
		}
		rep(0,n-1,i)
		{
			if(f[i][1]!=INF)q.push(mk(-f[i][1],i));
			if(f[i][0]!=INF)q.push(mk(-f[i][0],i));
		}
		while(q.size())
		{
			int x=q.top().S;
			int ww=-q.top().F;
			q.pop();
			if(ww==f[x][0])
			{
				int tn=(x+1)%n;
				if(f[tn][0]>f[x][0]+2)
				{
					f[tn][0]=f[x][0]+2;
					q.push(mk(-f[tn][0],tn));
				}
			}
			if(ww==f[x][1])
			{
				int tn=(x+1)%n;
				if(f[tn][1]>f[x][1]+2)
				{
					f[tn][1]=f[x][1]+2;
					q.push(mk(-f[tn][1],tn));
				}
			}
		}
		rep(1,n-1,i)
		{
			add(i+1,i,f[i][0]);
			if(i>=2)add(i+1,i-1,f[i][1]);
		}
		add(1,n,f[0][0]);
		add(1,n-1,f[0][1]);
		add(2,n,f[1][1]);
		prim();putl(sum);
		return 0;
	}
	if(flag)
	{
		if((ll)n*Q<=10000000)
		{
			rep(1,Q,i)
			{
				int x=t[i].x;int y=t[i].y;int z=t[i].z;
				int s1=0,s2=0;add(x+1,y+1,z);
				rep(1,n*2-1,j)
				{
					if(j&1)++s1;else ++s2;
					add((s1+x)%n+1,(s2+y)%n+1,z+j);
				}
			}
			sum=0;prim();putl(sum);
		}
	}
	return 0;
}

考虑100分 我们还是可以发现这是一个优化建图的问题。

考虑克鲁斯卡尔的过程 对于边a,b,c a向b连了一条c的边 对于下一条边 a+1,b,c+1 对于这条边我们可以设想一下 在跑克鲁斯卡尔的时候 由于上一条边比当前边权小 上一条边被便利的时候 a,b必然形成了一个联通快。

此时 对于a+1,b,c+1这条边就等价于 a+1,a,c+1 而下条边 a+1,b+1,c+2由于同样的原因可以转换为 b,b+1,c+2.

设f[i]表示i点和其父亲连边的边权最小值 dp一下即可 可以发现这样做所有边都被简化了。

然后跑克鲁斯卡尔即可 总边数Q+n.复杂度(Q+n)log 。

const int MAXN=400010;
int n,cnt,Q;ll sum;
struct wy
{
	int x,y,z;
}t[MAXN<<1];
int f[MAXN];
inline int cmp(wy a,wy b){return a.z<b.z;}
inline int getfather(int x){return x==f[x]?x:f[x]=getfather(f[x]);}
int main()
{
	freopen("spanning.in","r",stdin);
	freopen("spanning.out","w",stdout);
	memset(f,0x3f,sizeof(f));
	get(n);get(Q);
	rep(1,Q,i)
	{
		int x,y,z;
		get(x);get(y);get(z);
		t[++cnt]=(wy){x+1,y+1,z};
		f[(x+1)%n]=min(f[(x+1)%n],z+1);
		f[(y+1)%n]=min(f[(y+1)%n],z+2);
	}
	rep(1,n-1,i)f[i]=min(f[i-1]+2,f[i]);
	f[0]=min(f[n-1]+2,f[0]);
	rep(1,n-1,i)f[i]=min(f[i-1]+2,f[i]);
	rep(1,n-1,i)t[++cnt]=(wy){i,i+1,f[i]};
	t[++cnt]=(wy){1,n,f[0]};
	rep(1,n,i)f[i]=i;
	sort(t+1,t+1+cnt,cmp);
	rep(1,cnt,i)
	{
		int xx=getfather(t[i].x);
		int yy=getfather(t[i].y);
		if(xx==yy)continue;
		f[xx]=yy;sum+=t[i].z;
	}
	putl(sum);
	return 0;
}
posted @ 2020-04-11 19:55  chdy  阅读(192)  评论(0编辑  收藏  举报