9.8树与图(1)

预期一测\Delta
a1001000
b10060-40
c10080-20
d1001000
e1001000
f000
g1000-100
h1001000
总分700540-160

 问题 A: 医院设置

题意为在一棵树里找一个根,使得每个点到根的距离乘上点权的和最小,用dep数组表示距离,对于每一个点,都把他令为根然后从这个根dfs一遍求出所有可能的答案值就行了

int dfs(int x,int dep){
	v[x]=1;
	int ret=0;
	for(int i=head[x];i;i=nxt[i]){
		int y=to[i];
		if(!v[y]) ret+=dfs(y,dep+1);
	}
	ret+=dep*p[x];
	return ret;
}

问题 B: 黑暗城堡

 Dijkstra(记得用领接矩阵)跑出最短路后,用一个集合记录现已加入城堡的点有哪些,1 是已在集合中,再将刚才的代码复制一遍仿照Dijkstra, 2~n 枚举当前不在集合中的最小的dist[t](也可以理解为排序,从最小的dist开始依次加入集合中,若用前向星/领接表[排序操作]就会很难,代码也更丑,而n<=1000的数据使得n^{2}完全可过),然后找到已在集合中且满足

                                                 dist\left [ t \right ]=dist[j]+map[j][t]

的 j 的个数 k 然后根据乘法原理把每一步的 k 累乘起来即为答案

int main() {
	scanf("%d%d",&n,&m);
	memset(a,0x3f,sizeof(a));
	for (int i=1;i<=n;i++) a[i][i]=0;
	for (int i=1;i<=m;i++) {
		int x,y,z;
		scanf("%d %d %d",&x,&y,&z);
		a[x][y]=a[y][x]=min(a[x][y],z);
	}
	memset(d,0x3f,sizeof(d));
	d[1]=0;
	for(int i=1;i<n;i++) {
		int t=0;
		for(int j=1;j<=n;j++)
			if (!v[j] && (!t || d[j]<d[t])) t=j;
		v[t]=1;
		for(int j=1;j<=n;j++)
			d[j]=min(d[j],d[t]+a[t][j]);
	}
	memset(v,0,sizeof(v));//v的回收利用
	v[1]=1;
	ll ans=1;
	for (int i=1;i<n;i++) {//这不就是复制粘贴吗
		int t=0,k=0;
		for (int j=2;j<=n;j++)
			if(!v[j] && (!t || d[j]<d[t])) t=j;
		for (int j=1;j<=n;j++)
			if(v[j] && d[j]+a[j][t]==d[t]) k++;
		v[t]=1;
		(ans*=k)%=P;
	}
	printf("%d",ans);
	return 0;
}

问题 C: 繁忙的都市(city)

 最小生成树模板,在用Kruskal算法求最小生成树是将每一条加入树的边取最大值就行了,因为一棵最小生成树必定包含无向图中最小的那些边。

#include<bits/stdc++.h>
using namespace std;
struct E{
	int x,y,z; 
}e[6010];
bool operator < (E x,E y){return x.z<y.z;}
int fa[6010];
int get(int x){return fa[x]==x ? x : fa[x]=get(fa[x]);}
int n,m,ans;
int main(){
	scanf("%d%d",&n,&m);
	for(int i=1;i<=m;i++) scanf("%d%d%d",&e[i].x,&e[i].y,&e[i].z);
	sort(e+1,e+m+1);
	for(int i=1;i<=n;i++) fa[i]=i;
	for(int i=1;i<=m;i++){
		int x=e[i].x,y=e[i].y,z=e[i].z;
		x=get(x),y=get(y);
		if(x==y) continue;
		ans=max(ans,z);
		fa[x]=y;
	}
	cout<<n-1<<" "<<ans;
}

问题 D: 走廊泼水节

 在Kruskal算法求最小生成树的过程中,对于将要合并的两个点x,y所在的集合S_{x}S_{y}来说因为要保证最后的最小生成树不变,那两个集合需要新建的边都至少要大于x和y之间的边。所以只需把答案加上\left ( z+1 \right )*\left ( \left | S_{x} \right |*\left | S_{y} \right |-1 \right ),z为x和y之间的边的边权,|S|表示该集合的大小(点的数量)。

#include<bits/stdc++.h>
using namespace std;
struct E{
	int x,y,z; 
}e[6010];
bool operator < (E x,E y){return x.z<y.z;}
int fa[6010],s[6010];
int get(int x){return fa[x]==x ? x : fa[x]=get(fa[x]);}
int T,n;
long long ans;
int main(){
	cin>>T;
	while(T--){
		scanf("%d",&n);
		for(int i=1;i<n;i++)scanf("%d%d%d",&e[i].x,&e[i].y,&e[i].z);
		sort(e+1,e+n);
		for(int i=1;i<=n;i++){
			fa[i]=i;
			s[i]=1;
		}
		for(int i=1;i<n;i++){
			int x=e[i].x,y=e[i].y,z=e[i].z;
			x=get(x),y=get(y);
			if(x==y) continue;
			ans+=(long long)(z+1)*(long long)(s[y]*s[x]-1);
			fa[x]=y;
			s[y]+=s[x];
		}
		cout<<ans<<endl;
		ans=0;
	}
}

问题 E: 最优贸易

 先以 1 为起点,SPFA一遍求出一个数组 D ,表示表示从节点 1 到 当前节点的路径中,能经过的最小点权值,由于不满足Dijkstra的贪心性质,需要选择SPFA算法,同理再以 n 为起点,求最大值数组 F 然后遍历所有的 F-D 就行了。

#include<bits/stdc++.h>
using namespace std;
const int N=1e5+6,M=1e6+6;
int n,m,tot,val[N],ans;
int h[N],to[M],nxt[M],d[N];
int fh[N],fto[M],fnxt[M],f[N];
priority_queue<pair<int,int> >q,fq;
int main(){
	scanf("%d%d",&n,&m);
	for(int i=1;i<=n;i++) scanf("%d",val+i);
	for(int i=1;i<=m;i++){
		int x,y,z;
		scanf("%d%d%d",&x,&y,&z);
		to[++tot]=y;nxt[tot]=h[x];h[x]=tot;
		fto[tot]=x;fnxt[tot]=fh[y];fh[y]=tot;
		if(z==2){
			to[++tot]=x;nxt[tot]=h[y];h[y]=tot;
			fto[tot]=y;fnxt[tot]=fh[x];fh[x]=tot;
		}
	}
	memset(d,0x3f,sizeof(d));
	d[1]=val[1];
	q.push(make_pair(-d[1],1));
	while(q.size()){
		int x=q.top().second;
		q.pop();
		for(int i=h[x];i;i=nxt[i]){
			int y=to[i];
			if(d[y]>d[x]){
				d[y]=d[x];
				d[y]=min(d[y],val[y]);
				q.push(make_pair(-d[y],y));
			}
		}
	}
	memset(f,0xcf,sizeof(f));
	f[n]=val[n];
	fq.push(make_pair(f[n],n));
	while(fq.size()){
		int x=fq.top().second;
		fq.pop();
		for(int i=fh[x];i;i=fnxt[i]){
			int y=fto[i];
			if(f[y]<f[x]){
				f[y]=f[x];
				f[y]=max(f[y],val[y]);
				fq.push(make_pair(f[y],y));
			}
		}
	}
	for(int i=1;i<=n;i++) ans=max(ans,f[i]-d[i]);
	cout<<ans;
	return 0;
}

问题 F: 奖金

对于每一个 a,b 建一条从 b 到 a 的单向边,并记录 a 的儿子数加一,然后将所有没有儿子的点放入队列,从队首儿子将每个爸爸的儿子数减一,当某个点 x 的儿子数变为零,不仅意味 x 的add 可以更新而且只需由这一个儿子更新,因为既然是最后一个儿子,那么这个儿子的后代的数量一定比 x 的其他儿子更大,才会使这个儿子为最后一个,因为儿子数都是一个一个减的,越往后,儿子数一定更多,就越往后才会成为 x 的最后一个儿子

#include<bits/stdc++.h>
using namespace std;
#define N 10010
int head[N],nxt[N*2],to[N*2],len[N*2],cnt;
void add(int x,int y){
	to[++cnt]=y;
	nxt[cnt]=head[x];
	head[x]=cnt;
}
int son[N],ad[N],tot;
int n,m;
queue<int> q;
int main(){
	scanf("%d%d",&n,&m);
	for(int i=1;i<=m;i++){
		int x,y;
		scanf("%d%d",&x,&y);
		add(y,x);
		son[x]++;
	}
	for(int x=1;x<=n;x++)
		if(son[x]==0) 
			q.push(x);
	tot=n;
	while(!q.empty()){
		int x=q.front();q.pop();
		tot--;
		for(int i=head[x];i;i=nxt[i]){
			int y=to[i];
			son[y]--;
			if(!son[y]){
				ad[y]=max(ad[y],ad[x]+1);
				q.push(y);
			}
		}
	}
	if(tot){
		puts("Poor Xed");
		return 0;
	}
	int ans=100*n;
	for(int i=1;i<=n;i++)
		ans+=ad[i];
	cout<<ans;
}

问题 G: 对称二叉树(tree_c)

 由题意得一个编号为偶数的点必是一个左儿子,他的编号加一必是同一个爸爸的右儿子,所以只需对于每一个左儿子判断它的兄弟与它是否同真同假就行了。

int main(){
	scanf("%s",c);
	n=strlen(c);
	for(int i=0;i<n;i++){
		a[i+1]=1;
		if(c[i]=='#') a[i+1]=0;
	}
	for(int i=2;i<=n;i++){
		if((i&1)==0){
			if(a[i]^a[i^1]){
				puts("No");
				return 0;
			}
		}
	}
	puts("Yes");
	return 0;
}

问题 H: 小球(drop)

由于数据太小一个球一个球模拟都可以,又因为是满二叉树,所以一个节点 i 的左儿子就是 i*2 右儿子就是i*2+1;

scanf("%d%d",&n,&k);
n--;
n=1<<n;
int f,s;
for(int i=1;i<=k;i++){
	f=1;
	while(1){
		if(f>=n) break;
		s=f<<1;
		if(a[f]) s|=1;
		a[f]^=1;
		f=s;
	}
}
cout<<f;

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值