【题意】
dzy 定义一个n^2 位的数的生成矩阵A 为一个大小为n*n 且Aij 为这个数的第i*n+j-n位的矩阵。
现在dzy 有一个数n^2 位的数k,他想知道所有小于等于k 的数的n*n 生成矩阵有多少种。(如果不足n^2 位则补前缀零)
如果两个生成矩阵在其中一个旋转180 度后可以重叠,则称这两个矩阵是相同的。
n为小于等于1000的正偶整数。
【题解】
以前的模拟题今天被虐了。。。。好久没写博了更一下吧。。
实际上直接可以当作一个n*n位的整数处理。
设F(I)为i翻转后的数。
答案可以分为3部分求:
一是小于等于k的数的个数(设为S1)。
二是小于等于k且翻转后的数也小于等于k的数个数(设为S2)。
三是小于等于k且等于自身翻转的数的个数(设为S3)。
于是$Ans=k-\frac{\sum_{i=1}^{k}[f[i]\in (1,k)]-\sum_{i=1}^{k}[f[i]==i]}{2}=S1-(S2-S3)/2$
这三部分可以用数位dp求出:
设x为
dp[i][j][p]表示枚举的整数在
1.当前处理到第i位(这一维可以滚动的);
2.j为一个01变量,标记创造的整数前一段长度为i的部分是小于还是等于k的前一部分;
3.p也是一个01变量,表示这一段数翻转后小于等于还是大于原数的后一部分;
状态下的合法个数。
状态转移方程比较麻烦,细节比较多吧,注意一下各个状态之间的关系就行了,所以具体不多说了(好吧我比较懒。。)
于是 S1=k
S2=dp[n][0][0]+dp[n][1][0]
S3=dp[n/2][0][0]+dp[n/2][0][1]+dp[n/2][1][0]
最后代入原式即可求得答案。
【代码】
1 #include <cstdio> 2 #include <algorithm> 3 #include <iostream> 4 #define ll long long 5 #define mo 1000000007 6 using namespace std; 7 char str[1001000]; 8 int n,s1,s2,s3,s,dp[1000100][2][2],a[1000100]; 9 int main() 10 { 11 cin>>n; 12 scanf("%s",str+1); 13 n=n*n; 14 for (int i=1;i<=n;++i) 15 a[i]=str[i]-48; 16 dp[0][1][0]=1; 17 for (int i=0;i<n;++i) 18 for (int j=0;j<2;++j) 19 for (int k=0;k<2;++k) 20 { 21 if (j==0) s1=10; else s1=a[i+1]; 22 if (k==0) s2=a[n-i]+1; else s2=a[n-i]; 23 dp[i+1][0][0]=(dp[i+1][0][0]+(ll)dp[i][j][k]*min(s1,s2)%mo)%mo; 24 if (j==1 && s2>a[i+1]) dp[i+1][1][0]=(dp[i][j][k]+dp[i+1][1][0])%mo; 25 dp[i+1][0][1]=(dp[i+1][0][1]+(ll)dp[i][j][k]*max(0,s1-s2)%mo)%mo; 26 if (j==1 && s2<=a[i+1]) dp[i+1][1][1]=(dp[i+1][1][1]+dp[i][j][k])%mo; 27 } 28 s1=((dp[n][0][0]+dp[n][0][1])%mo+(dp[n][1][0]+dp[n][1][1])%mo)%mo; 29 s2=(dp[n][0][0]+dp[n][1][0])%mo; 30 s3=((dp[n/2][0][0]+dp[n/2][1][0])%mo+dp[n/2][0][1])%mo; 31 s=s1-(ll)(s2-s3)*((mo+1)/2)%mo-1; 32 cout<<(s%mo+mo)%mo<<endl; 33 return 0; 34 }