9.8集训小结

今天是图论专题

拓扑排序可以拿来求自环(我是伞兵

Dj的入队操作要放在if(dis[v]>dis[now]+tr[i].val)里面

T1:

第一题

样例输入:

5
13 2 3
 4 0 0
12 4 5
20 0 0
40 0 0

样例输出:

81

题解:

        法1:

        这是一道简单的二叉树应用问题,问题中的结点数并不多,数据规模也不大,采用邻接矩阵存储,用Floyed算法(上网查阅)求出任意两结点之间的最短路径,然后穷举医院可能建立的n个结点位置,找出一个最小距离的位置即可。当然也可以用双链表结构或带父结点信息的数组存储结构来解决,但实际操作稍微麻烦了一点。

        法2:

        这是一个朴素得不能再朴素的方法,由于节点只有100个,我们反手枚举每个点作为医院的情况跑一遍DFS,求最小的那一个,完事!

#include<cstdio>

using namespace std;

struct Node{
	int v,nxt;
}tr[10010];
int Head[10010];
int kok;

int n;
int val[10010];
int x,y;
int minn,ans;

inline void add(int u,int v)
{
	kok++;
	
	tr[kok].v=v;
	tr[kok].nxt=Head[u];
	
	Head[u]=kok;
}

inline void dfs(int now,int fath,int depth)
{
	ans+=depth*val[now];
	for(int i=Head[now];i;i=tr[i].nxt){
		int v=tr[i].v;
		if(v==fath)continue;
		dfs(v,now,depth+1);
	}
}

int main(void)
{
	scanf("%d",&n);
	for(int i=1;i<=n;i++){
		scanf("%d",&val[i]);
		scanf("%d%d",&x,&y);
		if(x!=0){
			add(x,i);
			add(i,x);
		}
		if(y!=0){
			add(y,i);
			add(i,y);
		}
	}
	minn=99999999;
	for(int i=1;i<=n;i++){
		ans=0;
		dfs(i,0,0);
		if(minn>ans)minn=ans;
	}
	printf("%d",minn);
}

/*

7
1 2 3
1 4 5
1 6 7
1 0 0
60 0 0
1 0 0
1 0 0
*/

T2:

第二题

样例输入:

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

样例输出:

6

题解:

        考的时候看到这道题直接傻眼,稍微思考了一下果断放弃,但是事实上,这道题远远没有想象的那么困难。简化了题意就是,求有多少建图情况使得1到每个点的最小值不变(这有简化吗?)

进一步简化就是,先dj跑一边每个点的最小路径然后计算: 

        对于一个点有多少个相连的  \large j  满足:\large dis[i]=dis[j]+edge(i,j)

edge(i,j)表示i,j之间的边长。而最终答案就是每个点的个数相乘(排列组合的乘法原理,这些j更换后对dis没有影响,借此便可以算出有几种方案)

#include<cstdio>
#include<algorithm>
#include<cstring>
#include<string>
#include<queue>

using namespace std;

struct Node{
	int v,nxt;
	int val;
}tr[1000010];
int Head[1000010];
int kok;

inline void add(int u,int v,int w)
{
	kok++;
	
	tr[kok].v=v;
	tr[kok].val=w;
	tr[kok].nxt=Head[u];
	
	Head[u]=kok;
}

int n,m;
int dis[1010];
long long cnt[1010];
bool vis[1010];
long long ans;

priority_queue< pair<int , int> >q; 

inline void djs()
{
	memset(dis,0x7f,sizeof(dis));
	memset(vis,false,sizeof(vis));
	dis[1]=0;
	q.push(make_pair(0,1));
	while(!q.empty()){
		int now=q.top().second;q.pop();
		if(vis[now]==true)continue;
		vis[now]=true;
		for(int i=Head[now];i;i=tr[i].nxt){
			int v=tr[i].v;
			if(dis[v]>dis[now]+tr[i].val){
				dis[v]=dis[now]+tr[i].val;
				cnt[v]=1ll;
				q.push(make_pair(-dis[v],v));
			}else if(dis[v]==dis[now]+tr[i].val)
				cnt[v]+=1ll;
		}
	}
}

int main(void)
{
	scanf("%d%d",&n,&m);
	int u,v,w;
	for(int i=1;i<=m;i++){
		scanf("%d%d%d",&u,&v,&w);
		add(u,v,w);
		add(v,u,w);
	}
	djs();
	ans=1ll;
	long long Mod=(long long)(1ll<<31ll)-1ll;
	cnt[1]=1ll;
	for(int i=1;i<=n;i++){
		ans=1ll*(ans*cnt[i])%Mod;
	}
	printf("%lld",ans);
}

T3:

第三题

样例输入:

4 5
1 2 3
1 4 5
2 4 7
2 3 6
3 4 8

样例输出:

3 6

题解:

        这题其实就是求图里面的最小生成树,就可以求出答案,使道路中分值最大值尽量小

证明:

        求最小生成树一定会将每条边以边权排个序,依次选合法的边建立就像下图:

如果存在一条边使得最大的边权更小,假如是下图中的红色虚线

 如果能使得答案更优,那它肯定比生成树中的最大边小,但是求出的生成树已经是最小的了,所以这样的边一定会被最小生成树给覆盖,或者大于已被选的边(下图蓝边)

 代码:

#include<cstdio>
#include<algorithm>

using namespace std;

struct Node{
	int u,v,val;
}tr[90010];

int n,m;
int fa[3000],size[3000];
int cnt,maxx=0;

inline int find(int x)
{
	if(fa[x]==x)return x;
	return fa[x]=find(fa[x]);
}

inline bool query(int x,int y)
{
	x=find(x),y=find(y);
	if(x==y)return false;
	if(size[x]>size[y]){
		size[x]+=size[y];
		fa[y]=x;
	}else{
		size[y]+=size[x];
		fa[x]=y;
	}
	return true;
}

inline bool tmp(Node o1,Node o2)
{
	return o1.val<o2.val;
}

int main(void)
{
	scanf("%d%d",&n,&m);
	for(int i=1;i<=n;i++)size[i]=1,fa[i]=i;
	for(int i=1;i<=m;i++){
		scanf("%d%d%d",&tr[i].u,&tr[i].v,&tr[i].val);
	}
	sort(tr+1,tr+1+m,tmp);
	for(int i=1;i<=m;i++){
		if(cnt==n-1)break;
		if(query(tr[i].u,tr[i].v)==true){
			maxx=max(maxx,tr[i].val);
			cnt++;
		}
	}
	printf("%d %d",cnt,maxx);
}

 T4:

第四题

样例输入:

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

样例输出:

4
17 

 题解:

        由于最后只剩下15分钟打这道题,所以读题时理解出了问题,是要求两点新建的边要比两点之间的所有路径要大,而不是大于所有与两点相连的边

        然后这又是一道最小生成树,是的你没听错,严格来说也不算是,毕竟他给的就是一棵树,所以我们把所有边排个序,依次加边。对于每加一条边,这条边edge(i,j)会将两个连通块相接,那么对于任何任何一对处在处在不同连通块中的两点,都可以建一条大于edge(i,j)的边对答案贡献。而能建立的边的个数就是两个连通块的大小-1。这一系列加边和求连通块的操作就可以用并查集来实现

#include<cstdio>
#include<algorithm>

using namespace std;

struct Node{
	int u,v;
	long long w;
}tr[6010];

int T;
int n;

int fa[6010];
long long size[6010];
int x,y;
long long ans;

inline bool tmp(Node xx,Node yy){return xx.w<yy.w;}

inline int find(int t)
{
	if(fa[t]==t)return t;
	return fa[t]=find(fa[t]);
}

int main(void)
{
	scanf("%d",&T);
	while(T--){
		scanf("%d",&n);
		for(int i=1;i<n;i++)scanf("%d%d%lld",&tr[i].u,&tr[i].v,&tr[i].w);
		for(int i=1;i<=n;i++)fa[i]=i,size[i]=1;
		sort(tr+1,tr+n,tmp);
		ans=0ll;
		for(int i=1;i<n;i++){
			x=find(tr[i].u);
			y=find(tr[i].v);
			if(x==y)continue;
			ans+=1ll*(tr[i].w+1ll)*(size[x]*size[y]-1ll);
			fa[y]=x;
			size[x]+=size[y];
		}
		printf("%lld\n",ans);
	}
}

T5:

第五题

样例输入:

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

样例输出:

5

题解:

        这是一道做过的板子题,但是没有做对需要反思。

        当时做的时候认为是直接跑一遍树形DP,记录1到 i 的最大值和最小值,但这样要出问题,因为一是会出现无法达到的情况,而是单项会使答案不是最优的,问题出在卖出价的求法(即maxx数组出了问题

        正解就是,建两个图,一个反向,一个正向,跑两遍spfa求出F和D数组

D[i]表示从1到i一路上最小值

F[i]表示从i到n的最大值

答案就是\large max(F[i]-D[i]) 

特别注意建两个图的时候,tot和kok,Head_1和Head_2别搞混了,别问我为什么晓得

#include<queue>
#include<cstdio>
#include<cstring>
#include<string>
#include<algorithm>
#define maxn 100010

using namespace std;

struct Node{
	int v,nxt;
}tr[maxn*2],ntr[maxn*2];

int Head_1[maxn];
int Head_2[maxn];
int tot=0,kok;
int n,m;
int val[maxn];
int D[maxn],F[maxn];
bool vis[maxn];

queue<int> q;

inline void Add(int u,int v)
{
	tot++;
	
	tr[tot].v=v;
	tr[tot].nxt=Head_1[u];
	
	Head_1[u]=tot;
}

inline void ppV(int u,int v)
{
	kok++;
	
	ntr[kok].v=v;
	ntr[kok].nxt=Head_2[u];
	
	Head_2[u]=kok;
}

inline void djst_min()
{
	memset(D,0x7f,sizeof(D));
	memset(vis,false,sizeof(vis));
	
	D[1]=val[1];
	q.push(1);
	while(!q.empty()){
		int now=q.front();q.pop();
		vis[now]=true;
		for(int i=Head_1[now];i;i=tr[i].nxt){
			
			int v=tr[i].v;
			
			if(D[v]>min(D[now],val[v]))D[v]=min(D[now],val[v]);
			
			if(vis[v]==false)
			q.push(v);
		}
	}
}

inline void djst_max()
{
	memset(F,-0x7f,sizeof(F));
	memset(vis,false,sizeof(vis));
	
	F[n]=val[n];
	q.push(n);
	while(!q.empty()){
		int now=q.front();q.pop();
		vis[now]=true;
		for(int i=Head_2[now];i;i=ntr[i].nxt){
			
			int v=ntr[i].v;
			
			F[v]=val[v];
			
			if(F[v]<max(F[now],val[v]))F[v]=max(F[now],val[v]);
			
			if(vis[v]==false)
			q.push(v);
		}
	}
}

int main(void)
{
	scanf("%d%d",&n,&m);
	for(int i=1;i<=n;i++)scanf("%d",&val[i]);
	for(int i=1;i<=m;i++){
		int u,v,w;
		scanf("%d%d%d",&u,&v,&w);
		if(w==1){
			Add(u,v);
			ppV(v,u);
		}
		else{
			Add(u,v);Add(v,u);
			ppV(v,u);ppV(u,v);
		}
	}
	djst_max();
	djst_min();
	int ans=-0x7ffffff;
	for(int i=1;i<=n;i++)ans=max(ans,F[i]-D[i]);
	printf("%d",ans);
}

T6:

第六题

样例输入:

2 1
1 2

样例输出:

201

题解:

         一开始以为是单向建图后直接跑答案,再特判一下是否会有环完事。然后就有惊无险的WA了两个点。事实上这道题需要拓扑排序,想想确实,每次更新入度为零的节点和本题吻合

注意1:拓扑排序可以拿来求自环(我是伞兵

注意2:dj和spfa的入队操作要放在if(dis[v]>dis[now]+tr[i].val)里面

#include<cstdio>
#include<algorithm>
#include<cstring>
#include<string>
#include<queue>

using namespace std;

struct Node{
	int v,nxt;
}tr[40010];
int Head[40010];
int kok;

int n,m,k;
int ans;
int x,y;
int root[10010],cnt;
bool vis[10010];
int srk[10010];
int val[10010];
int rit[10010];

queue<int>q;

inline void add(int u,int v)
{
	kok++;
	
	tr[kok].v=v;
	tr[kok].nxt=Head[u];
	
	Head[u]=kok;
}

inline void BFS()
{
	memset(vis,0,sizeof(vis));
	for(int i=1;i<=n;i++){
		if(rit[i]==0){
			q.push(i);
			val[i]=100;
		}
	}
	k=0;
	while(!q.empty()){
		int now=q.front();q.pop();
		if(vis[now]==true)continue;
		vis[now]=true;
		srk[now]+=1;
		k++;
		for(int i=Head[now];i;i=tr[i].nxt){
			int v=tr[i].v;
			val[v]=max(val[v],val[now]+1);
			rit[v]--;
			if(rit[v]==0)q.push(v);
		}
	}
}

int main(void)
{
	scanf("%d%d",&n,&m);
	for(int i=1;i<=m;i++){
		scanf("%d%d",&x,&y);
		add(y,x);
		rit[x]++;
	}
	cnt=0;
	BFS();
	ans=0;
	if(k!=n){
		printf("Poor Xed");
		return 0;
	}
	for(int i=1;i<=n;i++)ans+=val[i];
	printf("%d",ans);
}

T7:

第七题

样例输入:

ABCDE

样例输出:

Yes

题解: 

        由于这道题好心的使用顺序结构输入,就让这道题简单不少,的两个儿子编号一定为\large i*2\large i*2+1,判断节点\large i的两个儿子是否都为’#‘或者都不为’#‘,真就继续,为假就直接输出No结束。

#include<cstdio>
#include<iostream>
#include<cstring>
#include<string>

using namespace std;

int son[100000],len,j;
char s[1000000];

int  main(void)
{
	cin>>s;
	len=(int)strlen(s);
	for(int i=1;i<len;i++){
		if(s[i]!='#'){
			if(s[(i+1)/2-1]!='#')
				son[(i+1)/2-1]++;
			else{
				j=(i+1)/2-1;
				while(s[j]=='#')j++;
				son[j]++;
			}
		}
	}
	for(int i=0;i<len;i++){
		if(son[i]%2==1){
			printf("No");
			return 0;
		}
	}
	printf("Yes");
	return 0;
}

 T8:

第八题

样例输入:

4 2

样例输出:

12

题解: 

        先人脑跑了一遍发现,可以从n-1推到n的情况,当最深层被填满的时候会发现除了最后一层,其它节点都会变回操作之前的样子,相当于上一层的落到左儿子,然后另一半落到右儿子

得到:

 n=1     n=2     n=3     n=4
f[1]=1  f[1]=2  f[1]=4  f[1]=8
           f[2]=3  f[2]=6  f[2]=12
                      f[3]=5  f[3]=10
                      f[4]=7  f[4]=14
                                 f[5]=9
                                 f[6]=13
                                 f[7]=11
                                 f[8]=15

规律出现了

#include<cstdio>

using namespace std;

int f[600000];
int n,I,len;

int main(void)
{
	scanf("%d%d",&n,&I);
	if(n==1){
		printf("1");
		return 0;
	}
	f[1]=2;f[2]=3;len=2;
	for(int k=3;k<=n;k++){
		for(int i=1;i<=len;i++){
			f[i]*=2;
		}
		for(int i=len+1;i<=len*2;i++){
			f[i]=f[i-len]+1;
		}
		len*=2;
	}
	printf("%d",f[I]);
}

 总结:

        今天的测试发现了一些知识误区和知识漏洞,有收获,明天加油

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值