序列(搜尋剪枝)
一個 \(1\) 到 \(n\) 的排列 \(x\) ,每次你可以選擇任意一個滿足 \(1\le i\le n\) 的 \(i\) ,將 \(x_1\)到\(x_i\) 翻轉。你需要求出將排列變為升序的最小操作次數。有多組資料。
啟發式搜尋
下述的有序既可以是後一個比前一個大1,也可以是後一個比前一個小1
因為數字從1到n,不可能有重複數字出現,所以如果 \(x_i\) 與 \(x_{i-1}\) 滿足有序, \(x_i\) 與 \(x_{i+1}\) 也滿足有序,那麼 \(x_{i-1}\) 到 \(x_{i+1}\) 均滿足有序
而每次翻轉,如果一段連續的區間完整的包含在操作區間裡面,或是完全不在這之中,是不會影響其連續性的
可以推出,當滿足有序的相鄰點對數目為 \(n-1\) 時,這個序列要麼是單調遞增序列,要麼是單調遞減序列
那麼搜尋過程中就可以把滿足有序的相鄰點對數目這個作為判定是否找到答案的判定變數
設這個變數為 \(s\)
每次翻轉會影響到與左右點的有序性的點只有 \(x_1\) 和 \(x_i\)
分別是
\(A.\ x_1\) 與 \(x_{i+1}\) 是否能夠組成新的有序的使答案更接近我們要求的答案
\(B.\ x_i\) 與 \(x_{i+1}\) 原本是否已經組成了有序的而現在這個操作將其破壞掉了
根據這個我們會發現,對於列舉的一次操作 \(x_1-x_i\) 翻轉,可能會有:
\(1.\ s=s-1,A==false\ \&\&\ B==true\)
\(2.\ s=s,A==true\ \&\&\ B==true\)
\(3.\ s=s+1,A==true\ \&\&\ B==false\)
對於結果1,我們是肯定不會選的 因為這隻會讓我們一直以來的努力全部木大
而結果2和結果3,都是有可能選的
到這裡程式碼就比較顯然了
接下去就需要考慮剪枝了
機房裡剛學了一個月的巨佬跟我講了一個特別優秀的剪枝
迭代加深之後,根據當前的 \(s\) 可以知道最優步數:即當前步數加上 \(s\) ,然後就可以根據這個期望步數剪枝(因為有迭代加深限制步數)
程式碼裡還有我自己寫的一堆奇奇怪怪的 剪枝 卡常小技巧
#include <bits/stdc++.h> #define LL long long using namespace std; const int MAX=2e6+3; int a[MAX]; int b[MAX]; // int c[MAX]={0,5,2,5,8,7,6,5}; int n; int ans; inline void jh(int &x,int &y){int z=x;x=y;y=z;} inline void dfs(int x,int y,int z) { if(x==0) { if(a[1]==n) { if(y!=z)ans=min(ans,y+1); } else ans=y; return; // if(ans==6)for(int i=1;i<=y;i++)printf("%d ",b[i]);printf("\n"); } if(y==z||y>=ans)return; for(int i=2;i<=n;i++) { if(i==b[y])continue; int now=x; if(abs(a[1]-a[i+1])==1)now--; if(abs(a[i]-a[i+1])==1)now++; if(now>=x)continue; if(now+y+1>z)continue; b[y+1]=i; // bool bj=0; // for(int j=1;j<=y+1;j++)if(b[j]!=c[j])bj=true; // if(!bj)printf("i: %d , x:%d , y:%d\n",i,now,y+1); // if(!bj)for(int j=1;j<=n;j++)printf("%d ",a[j]); // if(!bj)printf("-->\n"); for(int j=1;j<=(i>>1);j++)jh(a[j],a[i-j+1]); // if(!bj)for(int j=1;j<=n;j++)printf("%d ",a[j]); // if(!bj)printf("\n\n"); dfs(now,y+1,z); if(ans!=0x3f3f3f3f)return; for(int j=1;j<=(i>>1);j++)jh(a[j],a[i-j+1]); } for(int i=2;i<=n;i++) { if(i==b[y])continue; int now=x; if(abs(a[1]-a[i+1])==1)now--; if(abs(a[i]-a[i+1])==1)now++; if(now!=x)continue; if(now+y+1>z)continue; b[y+1]=i; // bool bj=0; // for(int j=1;j<=y+1;j++)if(b[j]!=c[j])bj=true; // if(!bj)printf("i: %d , x:%d , y:%d\n",i,now,y+1); // if(!bj)for(int j=1;j<=n;j++)printf("%d ",a[j]); // if(!bj)printf("-->\n"); for(int j=1;j<=(i>>1);j++)jh(a[j],a[i-j+1]); // if(!bj)for(int j=1;j<=n;j++)printf("%d ",a[j]); // if(!bj)printf("\n\n"); dfs(now,y+1,z); if(ans!=0x3f3f3f3f)return; for(int j=1;j<=(i>>1);j++)jh(a[j],a[i-j+1]); } } LL rin() { LL s=0; char c=getchar(); bool bj=0; for(;(c>'9'||c<'0')&&c!='-';c=getchar()); if(c=='-')c=getchar(),bj=true; for(;c>='0'&&c<='9';c=getchar())s=(s<<1)+(s<<3)+(c^'0'); if(bj)return -s; return s; } int main() { // freopen("3.out","w",stdout); int i,j; for(int t=rin();t>0;t--) { n=rin(); int x=n-1; for(i=1;i<=n;i++)a[i]=rin(); a[n+1]=0x3f3f3f3f; for(i=1;i<n;i++)if(abs(a[i]-a[i+1])==1)x--; ans=0x3f3f3f3f; for(i=1;true;i++) { dfs(x,0,i); if(ans!=0x3f3f3f3f)break; } printf("%d\n",ans); } return 0; }