蓝桥杯:3000米排名预测(带限制的全排列)递归解法(DFS+状态重置)

蓝桥杯:3000米排名预测(带限制的全排列)递归解法(DFS+状态重置)

问题描述

3000米长跑时,围观党们兴高采烈地预测着最后的排名。因为他们来自不同的班,对所有运动员不一定都了解,于是他们分别对自己了解的一些运动员的实力作出了评估,即对部分运动员做了相对排名的预测,并且告诉了可怜留守的班长。因为无聊,于是他们就组团去打Dota去了。比赛结束后他们向班长询问最后的排名,但班长不记得了,只记得他们中哪些人的预测是正确的,哪些人的预测是错误的。他们想知道比赛的排名可能是什么。
输入格式
  第一行两个整数n, m,n为运动员数量,m为围观党数量。运动员编号从0到n-1。
  接下来m行,每行为一个围观党的相对排名预测。每行第一个数c表示他预测的人数,后面跟着c个0~n-1的不同的数,表示他预测的运动员相对排名,最后还有一个数,0表示这个预测是错误的,1表示是正确的。
输出格式
  第一行一个数k为有多少种排名的可能。
  下面k行,每行一个0~n-1的排列,为某一个可能的排名,相邻的数间用空格隔开。所有排名按字典序依次输出。

样例输入

Input Sample 1:

3 2

2 0 1 1

2 1 2 0

Input Sample 2:

3 2

2 0 1 1

2 2 1 0

样例输出

Output Sample 1:

2

0 2 1

2 0 1

Output Sample 2:

1

0 1 2

数据规模和约定

1<=n<=10, 2<=c<=n, 1<=m<=10,保证数据合法,且答案中排名可能数不超过20000。对于一个排名序列,一个预测是正确的,当且仅当预测的排名的相对顺序是排名序列的一个子序列。一个预测是错误的,当且仅当这个预测不正确。

思路

对于全排列,这题判断全排列串合法否的方式比较繁琐,但是最终用时还是比较少的(上限1s)
在这里插入图片描述
这题限制条件比较多,一个一个来:

  • 将一个长度为n的预测序列分为 n-1 个 “排名对”
  • 即 1 2 3 4 可以分为 3 个排名对:<1,2> <2, 3> < 3, 4>
  • 搜索 “排名对” 中两个元素在当前全排列串中的下标位置 index1, index2
  • 如果 index1 > index2 则说明存在矛盾的排名
  • 如果 index1 <= index2 且两个元素都在都在当前全排列串中则说明该“排名对” 是合法的,即命中
  • 对于所有真假设,要求 所有单个假设的所有 “排名对” 不存在矛盾
  • 对于所有假假设,要求 不能有单个假设的所有 “排名对” 全部命中当前的全排列串

例子

在这里插入图片描述

AC完整代码

#include <iostream>

using namespace std;

#define maxlen 13

int n, m;
int ans[maxlen];		// 答案
int visited[maxlen];	// 访问控制数组

// 约束条件 
// limit[i] 存储第 i 个预测的排名序列
// limit[i][0] 是第i个预测序列的长度
// limit[i]的最后一个数字 limit[i][limit[i][0]+1] 是该预测的真假
int limit[maxlen][maxlen];

// 结果 
int cnt = 0;
int result[20001][maxlen];

// 在ans的前len+1个元素中找x的下标,未找到,返回大值,在后序判断中表示合法
int find(int x, int len)
{
	for(int i=0; i<=len; i++)
	{
		if(ans[i] == x)
		{
			return i;
		}
	}
	
	return maxlen;
}

// 判断第x个元素的加入第len个位置,是否使串产生矛盾 
int can(int x, int len)
{
	ans[len] = x;	
	
	// 猜测为真,要求所有都满足  
	int flag_t = 1;
	for(int i=0; i<m; i++)
	{
		// True of False
		int tof = limit[i][limit[i][0]+1];
		
		if(tof == 1)
		{
			for(int j=1; j<limit[i][0]; j++)
			{	
				int index1 = find(limit[i][j], len);		
				int index2 = find(limit[i][j+1], len);
	
				// 如果出现矛盾的 “排名对”
				if(index1 > index2)
				{
					flag_t = 0;
				}		 
			}
		}
	}
	
	// 猜测为假,至少有一个假假设不能被 全部满足 
	int flag_f = 1;
	for(int i=0; i<m; i++)
	{
		// True of False
		int tof = limit[i][limit[i][0]+1];
		
		if(tof == 0)
		{
			// 每一趟使用一个假设中的所有“排名对”去判断 
			int cnt = 0;
			for(int j=1; j<limit[i][0]; j++)
			{	
				
				int index1 = find(limit[i][j], len);		
				int index2 = find(limit[i][j+1], len);
	
				// 如果一个“排名对”命中 (必须都找到才算命中)
				if(index1<=index2 && index1<maxlen && index2<maxlen)
				{
					cnt += 1;
				}		 
			}
			
			// 对于每一个假设,不能所有“排名对”都命中 
			if(cnt == limit[i][0]-1)
			{
				flag_f = 0;
			}
		}
	}
	
	// 如果所有真假设都符合,且所有假假设不完全符合 
	if(flag_t==1 && flag_f==1)
	{
		return 1;
	}
	else
	{
		return 0;
	}
	
}

// 填全排列串的第 index 下标的数字
void dfs(int index)
{
	if(index == n)
	{
		for(int i=0; i<n; i++)
		{
			result[cnt][i] = ans[i];
		}	
		cnt += 1;
	}
	else
	{
		for(int i=0; i<n; i++)
		{
			// 第一个数字直接进 
			if(index==0 ||(visited[i]==0 && can(i, index)==1))
			{
				visited[i] = 1;
				ans[index] = i;
				dfs(index+1);
				visited[i] = 0;
			}
	    }
		
	}
}

int main()
{
	cin>>n;
	for(int i=0; i<n; i++)
	{
		visited[i] = 0;
	}
	
	cin>>m;
	for(int i=0; i<m; i++)
	{
		// 读取长度 
		cin>>limit[i][0];
		// 读取预测 
		for(int j=1; j<=limit[i][0]; j++)
		{
			cin>>limit[i][j];
		}
		// 读取真假 
		cin>>limit[i][limit[i][0]+1];
	}
	
	dfs(0);
	
	cout<<cnt<<endl;
	for(int i=0; i<cnt; i++)
	{
		for(int j=0; j<n; j++)
		{
			cout<<result[i][j]<<" ";
		}
		cout<<endl;
	}
	
	return 0;
}
发布了38 篇原创文章 · 获赞 1 · 访问量 489

猜你喜欢

转载自blog.csdn.net/weixin_44176696/article/details/104025596