第46屆ICPC 東亞洲區域賽(澳門)

本文探讨了三个算法问题,分别是基于二维格点的高度路径规划、数组恒定性的判断及最小边权环查找。对于路径规划,通过构建路径并比较爬坡与下坡次数来确定路径;数组恒定性通过分析操作次数判断是否可达;最小边权环利用贪心策略选取编号小的边。这些题目涉及图论、最短路径和环检测等算法知识。
摘要由CSDN通过智能技术生成

题目:A.So I'll Max Out My Constructive Algor...

题目链接:登录—专业IT笔试面试备考平台_牛客网

样例输入: 

1
2
4 3
2 1

样例输出:

4 3 1 2

题意:给定一个n*n的二维格点,每个格点有一个高度,任意两个格点的高度都是互不相同的,现在让我们找到一条路径使得这条路径经过所有的点一次,而且要保证路径中爬坡的次数要小于等于下坡的次数。

分析:其实我们发现题目中要求的条件非常弱,因为路径中爬坡的次数要么小于等于下坡的次数,要么就是大于下坡的次数,只有这两种情况,我们可以随便找一条路径,如果这条路径满足题目要求那么我们就输出这条路径,否则就反向输出这条路径

#include<cstdio>
#include<iostream>
#include<algorithm>
#include<cstring>
#include<map>
#include<queue>
#include<vector>
#include<cmath>
using namespace std;
const int N=103;
int a[N][N];
int main()
{
	int T;
	cin>>T;
	while(T--)
	{
		int n;
		scanf("%d",&n);
		for(int i=1;i<=n;i++)
		for(int j=1;j<=n;j++)
			scanf("%d",&a[i][j]);
		vector<int>p;
		for(int i=1;i<=n;i++)
		{
			if(i&1)
			for(int j=1;j<=n;j++)
				p.push_back(a[i][j]);
			else
			for(int j=n;j>=1;j--)
				p.push_back(a[i][j]);
		}
		long long cnt=0;
		for(int i=1;i<p.size();i++)
			cnt+=p[i]>p[i-1];
		if(cnt<=(1ll*n*n-1)/2)
			for(int i=0;i<p.size();i++)
			{
				printf("%d",p[i]);
				if(i!=p.size()-1)
					printf(" "); 
			}
		else
			for(int i=p.size()-1;i>=0;i--)
			{
				printf("%d",p[i]);
				if(i!=0)
					printf(" "); 
			}
		puts("");
	}
	return 0;
}

F.Sandpile on Clique

题目链接:登录—专业IT笔试面试备考平台_牛客网

样例1输入:

5
5 0 3 0 3

样例1输出:

3 3 1 3 1

样例2输入:

2
1 0

样例2输出:

Recurrent

简化题意:给定n个数,如果有一个数的值大于等于n-1,那么它就要将值减n-1,然后其余所有数的值都加1,问最后能不能使得数组恒定。如果能输出恒定数组,否则输出Recurrent

分析:如果数组最终能恒定那么操作的次数一定不会大于n-1,如果数组进行了n次操作,而一共有n个数,那么至少有一个数会+(n-1),因为n次操作代表平均每个数进行一次操作,也就是值减少n-1,那么也就是说一定存在一个数进行操作的次数是小于等于1的。那么它的值增加至少n-1,那么它又可以继续操作,按照这个方法推下去发现可以一直进行操作。

当然还有一种理解方式,就是我们可以发现无论对于哪个数进行一次操作,所有位置的数都会发生改变,那么如果某个位置的数出现了之前出现过的数,那么就说明存在循环节,因为这个时候除了这个位置以外的其他数也一定与之前完全相同,因为我们没办法只操作n-1个数,因为每个数最后的合理取值只有0~n-2,所以n-1就是最大循环节。

也就是判断总的操作次数是否大于等于n,如何判断当前数组是否还能继续操作呢?其实就是判断数组中的最大值是否大于等于n-1,这个我们可以直接用线段树来维护,对某个位置进行操作就等价于将这个点的值减少n-1,其余点的值+1,这个复杂度都是O(logn)的,所以总的复杂度就是O(nlogn)。

细节见代码:

#include<cstdio>
#include<iostream>
#include<algorithm>
#include<cstring>
#include<map>
#include<queue>
#include<vector>
#include<cmath>
using namespace std;
const int N=2e5+10;
int fu[N],du[N];
bool vis[N];//vis[i]=true/false表示第i号点在/不在环中 
struct node{
	int u,v;
}p[N];
int find(int x)
{
	if(x!=fu[x]) return fu[x]=find(fu[x]);
	return fu[x];
}
vector<int>pp[N];
int main()
{
	int T;
	cin>>T;
	while(T--)
	{
		int n,m;
		scanf("%d%d",&n,&m);
		for(int i=1;i<=n;i++)
			fu[i]=i,vis[i]=false,du[i]=0,pp[i].clear();
		bool flag=false;
		for(int i=1;i<=m;i++)
			scanf("%d%d",&p[i].u,&p[i].v);
		for(int i=1;i<=m;i++)
		{
			int f1=find(p[i].u),f2=find(p[i].v);
			du[p[i].u]++;du[p[i].v]++;
			pp[p[i].v].push_back(p[i].u);
			pp[p[i].u].push_back(p[i].v);
			if(f1!=f2) fu[f1]=f2;
			else
			{
				flag=true;//找到环 
				queue<int>q;
				for(int j=1;j<=n;j++)
				{
					if(du[j]) vis[j]=true;
					if(du[j]==1) q.push(j);
				}
				while(!q.empty())
				{
					int t=q.front();
					vis[t]=false;
					q.pop();
					for(int j=0;j<pp[t].size();j++)
					{
						du[pp[t][j]]--;
						if(du[pp[t][j]]==1) q.push(pp[t][j]);
					}
				}
				for(int j=1;j<=i;j++)
					if(vis[p[j].u]&&vis[p[j].v]) printf("%d ",j);
				puts("");
				break;
			}
		}
		if(!flag) puts("-1");
	}
	return 0;
}

K.Link-Cut Tree

题目链接:登录—专业IT笔试面试备考平台_牛客网

样例输入:

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

样例输出:

2 4 5 6
-1

题意:给定一张图,包含n个点和m条边,第i条边的边权是2^i,让我们在图中找到一个边权和最小的环,如果能找到就输出这个环上的边的编号,否则输出-1.

分析:这道题直接贪心就行,我们知道\sum_{i=1}^{n}2^i=2^(n+1)-2,也就是说前i条边的边权和不如第i+1条边的边权和大。那么很容易能够想到我们应该尽量用编号小的边组成环,那么我们直接利用kruscal求最小生成树的思想直接从编号小的边开始建图,直至出现环为止,这个时候我们就形成了一个新的带环图,然后我们用拓扑排序把不在环上的点全部删掉,然后利用环上的点求一下环上的边即可。求的方法很简单,就是直接看一下当前在图上的边有哪些,选出来边的两端的点都是环上的点的边输出即可

细节见代码:

#include<cstdio>
#include<iostream>
#include<algorithm>
#include<cstring>
#include<map>
#include<queue>
#include<vector>
#include<cmath>
using namespace std;
const int N=2e5+10;
int fu[N],du[N];
bool vis[N];//vis[i]=true/false表示第i号点在/不在环中 
struct node{
	int u,v;
}p[N];
int find(int x)
{
	if(x!=fu[x]) return fu[x]=find(fu[x]);
	return fu[x];
}
vector<int>pp[N];
int main()
{
	int T;
	cin>>T;
	while(T--)
	{
		int n,m;
		scanf("%d%d",&n,&m);
		for(int i=1;i<=n;i++)
			fu[i]=i,vis[i]=false,du[i]=0,pp[i].clear();
		bool flag=false;
		for(int i=1;i<=m;i++)
			scanf("%d%d",&p[i].u,&p[i].v);
		for(int i=1;i<=m;i++)
		{
			int f1=find(p[i].u),f2=find(p[i].v);
			du[p[i].u]++;du[p[i].v]++;
			pp[p[i].v].push_back(p[i].u);
			pp[p[i].u].push_back(p[i].v);
			if(f1!=f2) fu[f1]=f2;
			else
			{
				flag=true;//找到环 
				queue<int>q;
				for(int j=1;j<=n;j++)
				{
					if(du[j]) vis[j]=true;
					if(du[j]==1) q.push(j);
				}
				while(!q.empty())
				{
					int t=q.front();
					vis[t]=false;
					q.pop();
					for(int j=0;j<pp[t].size();j++)
					{
						du[pp[t][j]]--;
						if(du[pp[t][j]]==1) q.push(pp[t][j]);
					}
				}
				for(int j=1;j<=i;j++)
					if(vis[p[j].u]&&vis[p[j].v]) printf("%d ",j);
				puts("");
				break;
			}
		}
		if(!flag) puts("-1");
	}
	return 0;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值