加工零件 题解

加工零件

NOIp普及组 2019T4

题目简述:

这道题描述得还是很详尽的,两个样例也都有解释,良心啊(赞qwq)

这里就不再多赘述了,直接来讲我的解题过程ovo


暴力起手

读完样例解释,知道是图论但并不是很懂为什么会用最短路,也没有其他什么思路,于是...来看看数据范围

测试点1~4:1≤n,m≤1000,q=3,L=1

测试点5~8:1≤n,m≤1000,q=3,1≤L≤10
  • 暴力起手

这八个点好小,岂不是送分?那就愉快地暴力起手吧!(其实就是没思路

怎么暴力呢?我们用递归模拟来解决:

  1. 面对每组的Ai、Li,我们先判断是否都等于1,如果是直接输出“No”(其实不用这步)

  2. 如果不是,就清零存答案的ans再进入递归处理

  3. 我们通过链式前向星遍历与Ai相连的所有边:设与Ai相连的点是Vj,我们就将Vj作为新的Ai进入下一层递归,当然,这时Li也应该同时减一

很简单对吧?然后我们将代码实现出来,就能够拿到40pts了,如下:

#include <bits/stdc++.h>
using namespace std;
int n,m,q,u,v,a,l,tot,ans;
int head[100010];

struct node {
	int to,net;
} e[100010];

inline void add(int u,int v) {
	e[++tot].to=v;
	e[tot].net=head[u];
	head[u]=tot;
}

inline void solve(int x,int y) {
	if(ans==1) return ;   //已经有答案就退出,剪枝+5pts 
	if(y==0) {
		if(x==1) ans=1;  //如果1号需要提供原材料,标记ans 
		return;
	}
	for(register int i=head[x];i;i=e[i].net) {
		int v=e[i].to;
		solve(v,y-1);  //进入下一层递归 
	}
}

int main() {
	scanf("%d%d%d",&n,&m,&q);
	for(register int i=1;i<=m;i++) {
		scanf("%d%d",&u,&v);
		add(u,v);
		add(v,u);
	}
	for(register int i=1;i<=q;i++) {
		scanf("%d%d",&a,&l);
		if(a==1&&l==1) puts("No");
		else {
			ans=0;  //清零ans 
			solve(a,l);
			if(ans==1) puts("Yes");
			else puts("No");
		}
		
	}
	return 0; 
}
  • 暴力优化

可以发现,Ai、Li在递归中会出现很多状态的重复计算,那么我们就可以使用记忆化来剪枝啊!这样我们可以拿到60pts

具体实现:用一个数组记录每次递归遇到的新的Ai、Li,下一次遇到相同的就直接输出记录的答案

PS:记忆化代码就不给出了,很简单就能够实现,不多赘述


漫漫正解之路:

再确认暴力程序已经不可优化后,我们来思考一下能拿更多分的做法

所以,来研究样例:

  1. 1->2的最短奇数路径长度为1,所以当2的Li为奇数且不小于1时,1总是要提供原材料

  2. 1->2的最短偶数路径长度为2,所以当2的Li为偶数且不小于2时,1总是要提供原材料

  3. 1->2->3的最短偶数路径长度为2,所以当3的Li为偶数且不小于2时,1总是要提供原材料

  4. 1->2->3的最短奇数路径长度为3,所以当3的Li为奇数且不小于3时,1总是要提供原材料

综上,我们可以将题意转换一下:

求1到Ai的最短奇数路径长度和最短偶数路径长度

若Ai的Li为偶数,且存在1到Ai的最短偶数路径长度dis[Ai][0],满足Li≥dis[Ai][0],那么输出“Yes”

若Ai的Li为奇数,且存在1到Ai的最短奇数路径长度dis[Ai][1],满足Li≥dis[Ai][1],那么也输出“Yes”

那么我们就得到了另外一种思路,并且和最短路联系上了

但是还有几个细节需要注意(我用Dijkstra实现最短路):

  1. 这道题的Dijkstra部分不用vis数组来记录当前点是否入过队

  2. 通过上面的递归做法我们能够知道一个点是需要通过多次的,但vis标记后一个点就只能走一次,明显与题意相违背,所以我们去掉vis数组

  3. 我们提交后会发现没有得到100pts,怎么回事?再想一下,1可能是孤岛!什么意思?就是1没与任何其他的点相连,所以我们特判一下即可

现在给出满分code:

#include <bits/stdc++.h>
using namespace std;
int n,m,q,u,v,a,l,tot,ans,flag;
int dis[200010][2],head[200010];
queue<pair<int,int> > shan;

struct node {
	int to,net;
} e[200010];

inline void add(int u,int v) {
	e[++tot].to=v;
	e[tot].net=head[u];
	head[u]=tot;
}

inline void dijkstra() {  //在普通模板上分成奇偶处理 
	memset(dis,127,sizeof(dis));
	dis[1][0]=0;  //将1的偶数路径赋值为0,奇数路径设为较大值 
	shan.push(make_pair(0,1));  //入队 
	while(!shan.empty()) {
		int x=shan.front().second;
		shan.pop();
		for(register int i=head[x];i;i=e[i].net) {
			int v=e[i].to;
			if(dis[v][0]>dis[x][1]+1) {  //用奇数+1更新偶数 
				dis[v][0]=dis[x][1]+1;
				shan.push(make_pair(dis[v][0],v));
			}
			if(dis[v][1]>dis[x][0]+1) {  //用偶数+1更新奇数 
				dis[v][1]=dis[x][0]+1;
				shan.push(make_pair(dis[v][1],v));
			}
		}
	}
}

int main() {
	scanf("%d%d%d",&n,&m,&q);
	for(register int i=1;i<=m;i++) {
		scanf("%d%d",&u,&v);
	//	if(u==1||v==1) flag=1;
		add(u,v);
		add(v,u);
	}
	dijkstra();
	for(register int i=1;i<=q;i++) {
		scanf("%d%d",&a,&l);
	//	if(flag==0) puts("No");
		if(!head[1]) puts("No");  //也可以引入flag来标记1是否是孤岛 
		else if(l%2==0&&dis[a][0]<=l) puts("Yes");  //判断偶数最短路是否可行 
		else if(l%2==1&&dis[a][1]<=l) puts("Yes");  //判断奇数最短路是否可行 
		else puts("No");	
	}
	return 0; 
}


然后,感谢一下WSRHL两位dalao帮我调试程序orz

最后,如果有任何不懂的地方或有任何我的理解不对的地方,欢迎大家留言啊qvq


猜你喜欢

转载自www.cnblogs.com/Eleven-Qian-Shan/p/13179161.html