某次模拟赛 交换
题目描述 Description |
给定一个{0, 1, 2, 3, … , n - 1}的排列 p。一个{0, 1, 2 , … , n - 2}的排列 q 被认为是优美的排列,当且仅当 q 满足下列条件: |
输入描述 Input Description |
第一行一个正整数 n. |
输出描述 Output Description |
仅一行表示答案。 |
样例输入 Sample Input |
3 |
样例输出 Sample Output |
1 |
数据范围及提示 Data Size & Hint |
30%: n <= 10
100%: n <= 50 |
之前的一些废话:《摔跤吧爸爸》这部电影真是太感动了
题解:还是觉得这种题挺难的,考试时候想不到正解的话花几分钟把30分拿了其实也挺爽的。
控制交换次数为n-1,所以我们可以这么考虑:考虑倒着处理,比如交换 (i, i + 1),那么前面的所有数不管怎么交换都无法到后面去(下标恒小于等于 i),后面的数也是一样到不了前面。说明这最后一次交换前,就要求对于所有的 x <= i, y > i,px<py。所以交换前左边的数是连续的,右边也是连续的。由于交换前,前面和后面的数是互相不干涉的,所以就归结成了两个子问题。(这段话是搬运题解而来的)
想到这里,就不太难了,很明显的区间DP:dp[i][j]表示将当前序列的第i个数到第j个数换成最终序列的方案数,只要区间长度确定了,那么交换次数也确定了,所以没有必要再单开一个状态来存交换次数。根据区间DP的套路,转移肯定是枚举中间点,但是还有一个限制:假如要在[L,R]中选取第k和第k+1个数进行交换,那么交换之后[L,K]中的数是再也不可能换到K以后了的,[K+1,R]中的数也不可能换到K+1以前了,所以需要保证交换第k和第k+1个数后,新序列中[L,K]的数都小于等于K,而[K+1,R]的数都大于K。只有满足以上限制的才能进行转移。转移方程是:dp[L,R]+=dp[L,K]*dp[K+1,R]*C(R-L,K-L-1),至于为什么要乘上一个组合数...自己想一想吧。
然后需要一个组合数预处理。
代码:
#include<iostream> #include<cmath> #include<cstring> #include<cstdio> #include<queue> #include<algorithm> using namespace std; typedef long long LL; typedef pair<int,int> PII; #define mem(a,b) memset(a,b,sizeof(a)) inline int read() { int x=0,f=1;char c=getchar(); while(!isdigit(c)){if(c=='-')f=-1;c=getchar();} while(isdigit(c)){x=x*10+c-'0';c=getchar();} return x*f; } const int maxn=55,MOD=1000000007; int n,p[maxn],seq[maxn],tmp[maxn],cnt,dp[maxn][maxn],c[maxn][maxn]; bool has[maxn]; void dfs(int step) { if(step==n) { for(int i=1;i<=n;i++)tmp[i]=p[i]; for(int i=1;i<n;i++)swap(tmp[seq[i]],tmp[seq[i]+1]); for(int i=1;i<=n;i++)if(tmp[i]!=i-1)return; cnt++; return; } for(int i=1;i<n;i++) if(!has[i]) { seq[step]=i;has[i]=1; dfs(step+1); seq[step]=has[i]=0; } } int C(int n,int m) { if(c[n][m])return c[n][m]; int &ret=c[n][m]; if(m==0)return ret=1; if(n<m)return ret=0; return ret=(C(n-1,m)+C(n-1,m-1))%MOD; } int DP(int L,int R) { if(dp[L][R]>0)return dp[L][R]; if(L==R)return dp[L][R]=1; if(R-L==1) { if(L<p[L] && R>p[R])dp[L][R]=1; else dp[L][R]=0; return dp[L][R]; } for(int i=L;i<R;i++) { bool ok=0; swap(p[i],p[i+1]); for(int j=L;j<=i;j++)if(p[j]>i){ok=1;break;} for(int j=i+1;j<=R;j++)if(p[j]<i+1){ok=1;break;} if(ok){swap(p[i],p[i+1]);continue;} dp[L][R]=(dp[L][R]+(LL)DP(L,i)*(LL)DP(i+1,R)%MOD*C(R-L-1,i-L))%MOD; // printf("%d %d %d %d %d\n",L,R,i,(LL)DP(L,i)*(LL)DP(i+1,R)%MOD*C(R-L-1,i-L),dp[L][R]); swap(p[i],p[i+1]); } if(dp[L][R]==-1)dp[L][R]=0; return dp[L][R]; } int main() { freopen("swap.in","r",stdin); freopen("swap.out","w",stdout); n=read(); for(int i=1;i<=n;i++)p[i]=read()+1; DP(1,n); // for(int i=1;i<=n;i++)for(int j=i;j<=n;j++)printf("dp%d %d %d\n",i,j,dp[i][j]); printf("%d\n",dp[1][n]%MOD); return 0; } /* 3 1 2 0 5 2 0 4 1 3 */
总结:突破口在于控制交换次数,算是一种经验吧。