题解:考虑题意,所修改的位置实际上是若干段,每段有一段或者两端可以修改。
处于最左边和最右边的两端由于只能从一边开始点灯,因此方案数为1,而其余每一段内的方案数为2^(l-1)。
其中只考虑每个区间都是从一端一直点燃到另一端。则方案数为(n-m!)/(p1!*p2!*p3!*.....*pk1),其中pi!表示第i端全排列,(n-m)!表示总区间的全排列。这只是一种情况,然后再乘以每个区间的方案数即为最终答案。
ps:由于阶乘会导致最后的数非常大,因此可将其分解为质因子相乘的形式。除以的时候只需进行质因子的幂运算即可。最后将所有的质因子相乘。
#include<cstdio> #include<iostream> #include<algorithm> #include<cstring> #define N 100100 #define ll long long const int M=1e9+7; using namespace std; int n,m,sum(0),num(0); int dr[N],zs[N],ff[N]; ll ans=1; bool f[N]={0}; struct node { ll l,s;//l每一段区间的长度,s每一段区间的方案数 }la[N]; void xx()//欧拉筛法求素数。背过模板就好了。 { for (int i=2;i<=n;i++) { if (!f[i]) zs[++num]=i; for (int j=1;j<=num,zs[j]*i<=n;j++) { f[zs[j]*i]=1; if (!(i%zs[j])) break; } } } ll jc(ll p,ll k)//快速幂 { ll ans=1; while (k) { if (k&1) ans=ans*p%M; p=p*p%M; k>>=1; } return ans; } int main() { freopen("lantern.in","r",stdin); freopen("lantern.out","w",stdout); scanf("%d%d",&n,&m); xx(); for (int i=1;i<=m;i++) scanf("%d",&dr[i]); sort(dr+1,dr+m+1); if (dr[1]>1) { la[++sum].l=dr[1]-1; la[sum].s=1; } if (dr[m]<n) { la[++sum].l=n-dr[m]; la[sum].s=1; } for (int i=2;i<=m;i++) { if (dr[i]-dr[i-1]-1) { la[++sum].l=dr[i]-dr[i-1]-1; la[sum].s=jc(2,la[sum].l-1); } } for (int i=1;i<=num;i++)//(n-m)!表示成质因子相乘的形式 { ll t=n-m; while (t) { ff[i]+=t/zs[i]; t/=zs[i]; } } for (int i=1;i<=sum;i++)//求出每个区间的全排列pi for (int j=1;j<=num;j++) { ll t=la[i].l; while (t) { ff[j]-=t/zs[j]; t/=zs[j]; } } for (int i=1;i<=num;i++) ans=ans*jc(zs[i],ff[i])%M; for (int i=1;i<=sum;i++) ans=ans*la[i].s%M; cout<<ans<<endl; fclose(stdin); fclose(stdout); return 0; }