2024牛客暑期多校訓練營10 - L. Tada! - 題解

Jerrycyx發表於2024-10-31

本文分離自《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);
		if(cnt>1) printf("MANY\n");
	}
	return 0;
}

相關文章