本文分離自《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\) 步內到達的點集就可以了。
首先,這裡有兩種浪費旋轉次數的方式:
-
因為密碼鎖可以來回轉浪費次數,而每一次這麼做需要兩步,所以如果能 \(s\) 在 \(x\) 步內達到 \(j\),那麼 \((x+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\),這個終點在以下三種情況下不合法:
- 從 \(s\) 到 \(j\) 所需最少步數大於 \(t\);
- \(n=1\) 時,\(s\) 到 \(j\) 的最少步數與 \(t\) 奇偶性不同;
- \(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;
}