2024牛客暑期多校训练营10 - L. Tada! - 题解

本文分离自《2024牛客暑期多校训练营10 - VP记录》


L. Tada!

看到数据范围 \(1 \le N \le 5, 1 \le M \le 50\),一眼暴力判断/暴搜。

因为操作可逆,所以如果 \(x\) 能在 \(t\) 步内到达 \(y\),那么 \(y\) 也能在 \(t\) 步内通过同样的方式到达 \(x\)

所以对于每一对 \(s,t\),我们只需要找到 \(s\) 能在 \(t\) 步内到达的点集就可以了。

首先,这里有两种浪费旋转次数的方式:

  1. 因为密码锁可以来回转浪费次数,而每一次这么做需要两步,所以如果能 \(s\)\(x\) 步内达到 \(j\),那么 \((x+2)\) 步也可以。

  2. 只要长度 \(n\) 不等于 \(1\),那么就可以将一步拆成两步,例如:正向转 \([l,r]\) 可以转化成先正向转 \([l,r+1]\) 再反向转 \([r+1,r+1]\),效果相同。

通过以上两种方式,在 \(n>1\) 的条件下,如果 \(s\) 可以 \(x\) 步到 \(j\),那么我们就可以让它 \((x+k)\) 步到 \(j\)。换言之,如果可以执行以上两种操作且最少步数 \(x<t\) 时,一定合法。


考虑什么时候无法执行以上两种操作:

  • \(n=1\) 时,因为每次只能转 \(1\) 位,所以最少步数就是 \(\lvert s-j \rvert\),因为此时无法使用方法二(至少要两位),所以只能用第一种,也就必须保证 \(t - \lvert s-j \rvert\) 是偶数,可以通过两步两步地转转到终点。

  • \(s=j\) 时,最少步数将为 \(0\),此时我们没有步数可以拆,所以在最初无法使用方法二,只能使用方法一,而只要使用了一次方法一,就有步数可以拆了,问题转化为通常情况。但是,当 \(t=1\) 时,我们也不能使用第一种方式(至少要转两次)。两种方式都无法使用,又要求必须要转一次,当然不可行。

除此之外,一定能无限制使用这两种方法,即一定能增加确定的步数来让 \(s\)\(j\)


总结一下,以 \(s\) 为起点时,枚举所有可能的终点 \(j\),这个终点在以下三种情况下不合法:

  1. \(s\)\(j\) 所需最少步数大于 \(t\)
  2. \(n=1\) 时,\(s\)\(j\) 的最少步数与 \(t\) 奇偶性不同;
  3. \(t=1\) 时,\(s\)\(j\) 相同。

刚才,我们已经证明了只要以上三种不合法条件均不满足,那么就一定有方式可以用 \(t\) 步抵达 \(j\)

由此可以判断出所有不合法的情况。当所有不合法的答案标记结束后,未被标记的一定是合法的答案,统计个数并分情况输出即可。

至于求最少步数可以用类似 DP 的递归处理,每次一定要把一位转到相同,计算下一位时判断是要和这一位一起转还是自己单独转,再分别对正着转和反着转取最小值。

#include<cstdio>
#include<algorithm>
using namespace std;

const int N=10,M=55,Pow10N=1e5+5;
const int pow10[N]={1,10,100,1000,10000,100000,1000000};
int n,m;

int ShortestPath(int x,int y,int alr=0,int pos=1) //x到y的最短距离,alr是操作上一位的次数,pos是已经处理的位数 
{
	if(pos>n) return 0;
	//规定y转到x为正方向 
	int xe=x%10,ye=y%10;
	int res=0x3f3f3f3f;
	int nd=(xe-ye+10)%10; //把x和y的最低位转平所需的步数
	//自己单转或跟着上一个一起转 
	res=min(res,max(0,min(nd,nd-alr))+ShortestPath(x/10,y/10,nd,pos+1)); //y转到x(与上一次同向) 
	nd=(ye-xe+10)%10;
	res=min(res,max(0,min(nd,nd+alr))+ShortestPath(x/10,y/10,-nd,pos+1)); //x转到y(与上一次反向)
	return res;
}

bool flag[Pow10N];
int main()
{
	int T; scanf("%d",&T);
	while(T--)
	{
		scanf("%d%d",&n,&m);
		for(int i=0;i<=pow10[n];i++)
			flag[i]=false;
		for(int i=1;i<=m;i++)
		{
			int s,t; scanf("%d%d",&s,&t);
			for(int j=0;j<pow10[n];j++)
			{
				if(flag[j]) continue;
				if(ShortestPath(j,s)>t) flag[j]=true;
				if(n==1 && (j-s+t)%2) flag[j]=true;
				if(t==1 && s==j) flag[j]=true;
				//三种不合法情况
			}
		}
		int cnt=0,ans=0;
		for(int i=0;i<pow10[n];i++)
			if(!flag[i]) cnt++,ans=i;
		if(cnt==0) printf("IMPOSSIBLE\n");
		if(cnt==1) printf("%0*d\n",n,ans); //在前面补0
		if(cnt>1) printf("MANY\n");
	}
	return 0;
}
posted @ 2024-10-31 07:37  Jerrycyx  阅读(15)  评论(0编辑  收藏  举报