题意

\(n\) 个杯子,会告诉你每一个区间 \(i\)\(j\) 需要的花费,从而得知这一区间内所有小球总和的奇偶性,因为我们知道每一个杯子下的小球只有一个或两个,因此这道问题便可以通过最短路的方式来做了

做法

在得到两个共端点的区间奇偶性后,就可以得到非共端点之间的奇偶性,例如,你知道了一到三号和一到四号的奇偶性,显而易见你就可以得到四号的奇偶性,所以,对于每一个 \(c\) ,我们可以建一条边,然后跑最小生成树即可。

如何建边呢?

很显然我们是不能单纯从 \(i\) 指向 \(j\) 的,比如 \(i=j\) 的边就作废了,所以建边策略为从 \(i\) 建一条向 \(j+1\) 的边,将端点数加一,再跑树。

最小生成树

常见的做法有两种, \(Prim\)\(Kruskal\) 。这里介绍的是后者。

将一个连通块当做一个集合。首先将所有的边按从小到大顺序排序,并认为每一个点都是孤立的,分属于 \(n\) 个独立的集合。然后按顺序枚举每一条边。如果这条边连接着两个不同的集合,那么就把这条边加入最小生成树,这两个不同的集合就合并成了一个集合;如果这条边连接的两个点属于同一集合,就跳过。直到选取了 \(n-1\) 条边为止。

简单来说, \(Kruskal\) 每次都选择一条最小的边,而且这条边的两个顶点分属于两个不同的集合。将选取的这条边加入最小生成树,并且合并集合。

代码

#include<bits/stdc++.h>
using namespace std;
int n,vis[2002],sum,tot,fa[2002];
long long ans;
struct node{//邻接表 
	int from,to,w;
}a[2001001];
void add(int q,int m,int bq){//加边 
	a[++tot].to=m;
	a[tot].from=q;
	a[tot].w=bq;
}
int find(int x){//判断联通块 
	if(!fa[x])return x;
	return fa[x]=find(fa[x]);//查询顺便更新该位置标志,会节省时间 
}
bool cmp(node x,node y){//按边权排序 
	return x.w<y.w;
}
inline int read(){
	int s=0;
	char ch=getchar();
	while(ch<'0'||ch>'9')ch=getchar();
	while(ch>='0'&&ch<='9')s=(s<<3)+(s<<1)+ch-'0',ch=getchar();
	return s;
}
int main()
{
	n=read();
	int x;
	for(int i=1;i<=n;i++){
		for(int j=1;j<=n-i+1;j++){
			x=read();
			add(i,i+j,x);
		}
	}
	sort(a+1,a+tot+1,cmp);
	for(int i=1;i<=tot;i++){
		int xx=find(a[i].from),yy=find(a[i].to);
		if(xx==yy)continue;//已为同一联通块 
		if(xx!=yy){
			sum++;//连边 ,边数加一 
			fa[xx]=yy;//合并 
			ans=(long long)ans+a[i].w;
		}
		if(sum==n)break;//已建成最小生成树 
	}
	cout<<ans<<endl;
	return 0;
}

posted on 2021-08-28 21:23  漠寒·  阅读(40)  评论(0编辑  收藏  举报