预备队第九周训练题集

P1119 灾后重建


题目背景
B 地区在地震过后,所有村庄都造成了一定的损毁,而这场地震却没对公路造成什么影响。但是在村庄重建好之前,所有与未重建完成的村庄的公路均无法通车。换句话说,只有连接着两个重建完成的村庄的公路才能通车,只能到达重建完成的村庄。
题目描述
给出 B 地区的村庄数 N,村庄编号从 0 到 N−1,和所有 M 条公路的长度,公路是双向的。并给出第 i 个村庄重建完成的时间 ti ,你可以认为是同时开始重建并在第 ti 天重建完成,并且在当天即可通车。若 ti 为 0 则说明地震未对此地区造成损坏,一开始就可以通车。之后有 Q 个询问 (x,y,t),对于每个询问你要回答在第 t 天,从村庄 x 到村庄 y 的最短路径长度为多少。如果无法找到从 x 村庄到 y 村庄的路径,经过若干个已重建完成的村庄,或者村庄 x 或村庄 y 在第 t 天仍未重建完成,则需要输出 −1。
输入格式
第一行包含两个正整数 N,M,表示了村庄的数目与公路的数量。
第二行包含 N 个非负整数 t0,t1,⋯,tN−1 ,表示了每个村庄重建完成的时间,数据保证了 t0≤t1≤⋯≤tN−1。
接下来 M 行,每行 3 个非负整数 i,j,w,w 为不超过 10000 的正整数,表示了有一条连接村庄 i 与村庄 j 的道路,长度为 w,保证 i≠j,且对于任意一对村庄只会存在一条道路。
接下来一行也就是 M+3 行包含一个正整数 Q,表示 Q 个询问。
接下来 Q 行,每行 3 个非负整数 x,y,t,询问在第 t 天,从村庄 x 到村庄 y 的最短路径长度为多少,数据保证了 t 是不下降的。
输出格式
共 Q 行,对每一个询问 (x,y,t) 输出对应的答案,即在第 t 天,从村庄 x 到村庄 y 的最短路径长度为多少。如果在第 t 天无法找到从 x 村庄到 y 村庄的路径,经过若干个已重建完成的村庄,或者村庄 x 或村庄 y 在第 t 天仍未修复完成,则输出 −1。
#include <bits/stdc++.h>
#define MAX_ 0x7f7f7f
#define N 205 
using namespace std;
int n,m,q;
int day[N];
int e[N][N];    //储存边

void update(int k) 
{
	for(int i=0;i<n;i++)
	{
		for(int j=0;j<n;j++)
		{
			if(e[i][j]>e[i][k]+e[k][j])
			e[i][j]=e[i][k]+e[k][j];
		}
	}
}

int main()
{
	cin>>n>>m;
	for(int i=0;i<n;i++)      cin>>day[i];
	for(int i=0;i<n;i++)
	   for(int j=0;j<n;j++)	
       {
            if(j==i)  e[i][j]=0;
            else
            e[i][j]=MAX_; 
       }
	for(int i=1;i<=m;i++)    
	{
		int x,y,z;
		cin>>x>>y>>z;
		e[x][y]=e[y][x]=z;
	}
	cin>>q;
    int now=0;
	for(int i=1;i<=q;i++)
	{
		int a,b,c;
		cin>>a>>b>>c;
		while(day[now]<=c&&now<n)
		{
			update(now);
			now++;
		}
		if(day[a]>c||day[b]>c)     cout<<-1<<endl;
		else
		{
			if(e[a][b]==MAX_)    cout<<-1<<endl;
			else     cout<<e[a][b]<<endl;
		}
	}
	return 0;
}

一开始想的是floyd的算法,但是想试试dijstra,写法TLE了。。。

用Floyd的做法,要注意的是e[i][j]当i=j的时候,要初始化成0,其他情况初始化成MAX_。还要注意的是,由于t是不下降的,所以对于每个询问,以now为中转接点已经更新过的,不要再重新更新,不然就会TLE。 

P8604 [蓝桥杯 2013 国 C] 危险系数

题目背景
抗日战争时期,冀中平原的地道战曾发挥重要作用。
题目描述
地道的多个站点间有通道连接,形成了庞大的网络。但也有隐患,当敌人发现了某个站点后,其它站点间可能因此会失去联系。
我们来定义一个危险系数 DF(x,y):对于两个站点 x 和 y(x≠y), 如果能找到一个站点 z,当 z 被敌人破坏后,x 和 y 不连通,那么我们称 z 为关于 x,y 的关键点。相应的,对于任意一对站点 x 和 y,危险系数 DF(x,y) 就表示为这两点之间的关键点个数。
本题的任务是:已知网络结构,求两站点之间的危险系数。
输入格式
输入数据第一行包含 2 个整数 n(2≤n≤1000),m(0≤m≤2000),分别代表站点数,通道数。
接下来 m 行,每行两个整数 u,v(1≤u,v≤n,u≠v) 代表一条通道。
最后 1 行,两个数 u,v,代表询问两点之间的危险系数 DF(u,v)。
输出格式
一个整数,如果询问的两点不连通则输出 −1。
#include <bits/stdc++.h>
#define MAX_ 0x7f7f7f
#define N 100005 
using namespace std;

struct edge
{
	int to;
	int nxt; 
}e[N*4];

int n,m,x,y;     
int cntt=0,ans=0,res=0;
int head[N],vis[N],cnt[N];
stack<int> s;

void add(int u,int v)
{
	e[++cntt].to=v;
	e[cntt].nxt=head[u];
	head[u]=cntt;
}

void DFS(int ss)
{
	if(ss==y)
	{
		ans++;
        for(int i=1;i<=n;i++)
        {
            if(vis[i])   cnt[i]++;
        }
		return;
	}
	while(!s.empty())
	{
		int tmp=s.top();
		s.pop();
		vis[tmp]=1;
		for(int i=head[tmp];i;i=e[i].nxt)
		{
			int temp=e[i].to;
			if(vis[temp])   continue;
			vis[temp]=1;
			s.push(temp);
			DFS(temp);
		}
		vis[tmp]=0;
	}
}

int main()
{
	cin>>n>>m;
	for(int i=1;i<=m;i++)
	{
		int a,b;
		cin>>a>>b;
		add(a,b);
		add(b,a);
	}
	cin>>x>>y; 
	s.push(x);
	DFS(x);
    for(int i=1;i<=n;i++)
    {
        if(cnt[i]==ans) res++;
    }
    if(ans)  cout<<res-2;
    else cout<<-1;
	return 0;
} 

这道题我一开始的想法是,求最短路径,最短路径-1即为关键点的个数,美美40分。。。
后来我才发现,关键点的判定标准是:cnt[i]的数量是否与可行路径数相等。cnt[i]的计算方式为:
每产生一条新的路径,这条路径上vis为1的点i的cnt[i]+1 

P1330 封锁阳光大学

题目描述
曹是一只爱刷街的老曹,暑假期间,他每天都欢快地在阳光大学的校园里刷街。河蟹看到欢快的曹,感到不爽。河蟹决定封锁阳光大学,不让曹刷街。

阳光大学的校园是一张由 n 个点构成的无向图,n 个点之间由 m 条道路连接。每只河蟹可以对一个点进行封锁,当某个点被封锁后,与这个点相连的道路就被封锁了,曹就无法在这些道路上刷街了。非常悲剧的一点是,河蟹是一种不和谐的生物,当两只河蟹封锁了相邻的两个点时,他们会发生冲突。

询问:最少需要多少只河蟹,可以封锁所有道路并且不发生冲突。

输入格式
第一行两个正整数,表示节点数和边数。 接下来 m 行,每行两个整数 u,v,表示点 u 到点 v 之间有道路相连。

输出格式
仅一行如果河蟹无法封锁所有道路,则输出 Impossible,否则输出一个整数,表示最少需要多少只河蟹。

#include <bits/stdc++.h>
#define MAX_ 0x7f7f7f
#define N 100005 
using namespace std;

struct edge
{
	int to;
	int nxt;
	int num;
}e[N*4];

int n,m;     //n个村庄,m条路
int cnt=0;
int ans;
int head[N],vis[N],sum[3];
queue<int> q;

void add(int u,int v)
{
	e[++cnt].to=v;
	e[cnt].nxt=head[u];
	head[u]=cnt;
}

bool BFS(int s)
{
	vis[s]=1;
	sum[1]=1,sum[2]=0;
	q.push(s);
	while(!q.empty())    
	{
		int tmp=q.front();
		q.pop();
		for(int i=head[tmp];i;i=e[i].nxt)
		{
			if(vis[e[i].to]==vis[tmp])    return 0;
			else if(vis[e[i].to]==vis[tmp]%2+1)    continue;
			else
			{
				vis[e[i].to]=vis[tmp]%2+1;
				sum[vis[tmp]%2+1]++;
				q.push(e[i].to);
			}
		}
	}
	return 1;
}

int main()
{
	cin>>n>>m;
	for(int i=1;i<=m;i++)
	{
		int a,b;
		cin>>a>>b;
		add(a,b);
		add(b,a);
	}
	for(int i=1;i<=n;i++)
	{
		if(!vis[i]) 
		{
			if(!BFS(i))    
			{
				cout<<"Impossible";
				return 0;
			} 
			else   	ans+=min(sum[1],sum[2]);
		}
	}
	cout<<ans;
	return 0;
}

这道题一开始我想的是利用优先队列,先把所有的点推入队列。从临边数最多的点开始,只要是与它相邻的就标记,同时ans+1,记录总边数的res再加上它有多少条相邻的边。优先队列里遍历过的点就不再遍历,最后看一下res的值和m的值是否相等,但是喜提40分。。。
无奈看题解吧,题解的思路是用两种颜色对相邻的两个点进行标记,同时利用广搜,没有被染过色的就把它染成与上一个点不一样的颜色,推入队列。对于染过色的,如果这个点染的是与上一个点不同的颜色就continue,否则直接return 0.  

P3916 图的遍历


题目描述
给出 N 个点,M 条边的有向图,对于每个点 v,求 A(v) 表示从点 v 出发,能到达的编号最大的点。

输入格式
第 1 行 2 个整数 N,M,表示点数和边数。

接下来 M 行,每行 2 个整数 Ui ,Vi,表示边 (Ui,Vi)。点用 1,2,…,N 编号。

输出格式
一行 N 个整数 A(1),A(2),…,A(N)。

#include <bits/stdc++.h>
#define MAX_ 0x7f7f7f
#define N 100005 
using namespace std;

struct edge
{
	int to;
	int nxt;
}e[N];

int n,m;     //n个村庄,m条路
int cnt=0;
int head[N],vis[N],ans[N];
stack<int> qq;

void add(int u,int v)
{
	e[++cnt].to=v;
	e[cnt].nxt=head[u];
	head[u]=cnt;
}

void DFS(int s)
{
	while(!qq.empty())
	{
		int tmp=qq.top();
		qq.pop();
		for(int i=head[tmp];i;i=e[i].nxt) 
		{
			int temp=e[i].to;
			if(ans[temp]<ans[tmp])    ans[temp]=ans[tmp];
			if(vis[temp])        continue;	
			if(!vis[temp])
			{
				vis[temp]=1;
				qq.push(temp);
				DFS(temp);
			}
		}	
	}
}

int main()
{
	cin>>n>>m;
	for(int i=1;i<=m;i++)
	{
		int a,b;
		cin>>a>>b;
		add(b,a);
	}
	for(int i=1;i<=n;i++)    ans[i]=i;
	for(int i=n;i>=1;i--)
	{
		if(!vis[i])   
		{
			qq.push(i);
			vis[i]=1;
			DFS(i);
		}
	}
	for(int i=1;i<=n;i++)
	{
		cout<<ans[i]<<" ";
	}
	return 0; 
}

这道题一开始的时候我是正着存图的,设置了如果s=n或者e[head[s]].nxt=0的时候回溯,顺便更新一下ans[tmp]的值,然后。。。WA了5个点。。。我又去研究了一下题解,发现如果反着存图,并且由大到小遍历,那么小的就一定可以更新到大的,并且不用设置递归出口。 

P3367 【模板】并查集

题目描述
如题,现在有一个并查集,你需要完成合并和查询操作。
输入格式
第一行包含两个整数 N,M ,表示共有 N 个元素和 M 个操作。
接下来 M 行,每行包含三个整数 Zi,Xi,Yi。当 Zi=1 时,将 Xi 与 Yi 所在的集合合并。
当Zi=2 时,输出 Xi与 Yi是否在同一集合内,是的输出 Y ;否则输出 N 。
输出格式
对于每一个 Zi=2 的操作,都有一行输出,每行包含一个大写字母,为 Y 或者 N 。
#include <bits/stdc++.h>
#define MAXN 10010
using namespace std;

int n,m;
int pa[MAXN];

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

int main()
{
	cin>>n>>m;
	for(int i=1;i<=n;i++)   pa[i]=i;
	for(int i=1;i<=m;i++)
	{
		int a,b,c;
		cin>>a>>b>>c;
		if(a==1)
		{
			int tmp=find(c);
			pa[find(b)]=tmp;
		}
		else
		{
			int res1=find(b);
			int res2=find(c);
			if(res1==res2)    cout<<"Y"<<endl;
			else   cout<<"N"<<endl;
		}
	}
	return 0;
}

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值