6915. 【2020.12.02提高组模拟】显示器(display)

64 篇文章 0 订阅
4 篇文章 0 订阅

Description

       你可能很熟悉的 7 段显示器,它被广泛用于各种设备上显示数字,如手表或计算器。由于其简洁、直观及美观,这种设计已被世界各国所接受。尽管如此,年轻的 Soaring 反对 7 段式设计,并声称仅使用 5 段显示器(下图中 a,b,c,d,e 这 5 段)就可以有效 地获得同样的功能。 

       Soaring 决定在其最强的运动项目足球比赛中进行推广使用。他的革命性设计将被用在下一轮奥运会预选赛中。他目前正在说服足协董事会。球员替换牌在足球比赛中用以显示被换下球员的球衣号码,Soaring 设计的替换牌有 M 个 5 段显示器,从左到右依次排开。Soaring 在游说时,先把替换牌设置成数 X,同时,然后接下来每秒进行一次操作。操作有两种:

       ①打开一个当前关闭的段
       ②关闭一个当前打开的段
       Soaring 一共进行 N 次操作,并且保证每进行 K 次操作后,替换牌上都会显示一个 有效的数,即牌上的每一位显示器上显示的数字都是正确的。此外,只要正确显示前导 0,少于 M 位的球衣号码也认为是有效的。     
       Soaring 感兴趣的是,对于每个最终状态(0 到 10M−1 之间的整数),他有多少种不 同的操作方法来实现。当然,他需要遵守前面提到的操作要求。我们认为只要某一刻 替换牌显示的状态不一样,我们就认为这两种操作序列不同。 由于答案可能非常大,所以只需要输出模(10^9 + 7)即可。

Input

输入文件名为 display.in。
第一行那包含整数 M,N,K(1≤K≤N)和 X(0≤X<10^ M )

Output

输出文件名为 display.out。
第 i 行输出替换牌经过 N 次操作后最终显示数 i-1 的方法数,答案模(10^ 9 +7)。

Sample Input

input1:

1 2 1 5

input2:

1 3 3 8

input3:

1 4 2 4

Sample Output

output1:

0
0
0
1
0
2
0
0
0
0
output2:
0
0
0
6
0
13
0
0
0
0
output3:
24
0
8
0
37
0
4
28
4
24

Data Constraint

        子任务                                                                                                                                                        限制                      分值
            1

                                                                                                                                         m=1,1<=n<=12

                   6
            2                                                                                                                                         m=1,1<=n<=10^15

                   15

            3                                                                                                                                       m=2,1<=n<=1500,k=n                     12
            4                                                                                                                                  m=2,1<=n<=10^15,1<=k<=15                     12
            5                                                                                                                                 m=2,1<=n<=10^15,1<=k<=1500                     15
            6                                                                                                                                       m=2,1<=n<=10^15                     4

       

Solution

先将数字转化一下,对于每一个数字可以用一个5位二进制数表示用不用那一段。

而一个二位数就可以表示为一个10位二进制数。

先考虑暴力dp转移。

设f[ i ][ S ]表示走了i步,当前的数的状态为S的方案数。

则初始f[ 0 ][ state[x] ]=1。

转移只需要枚举一个 j ,表示翻转状态的第j位,f[ i ][ S ]=\sum f[ i-1 ]][ S~\^ ~~(1<<j) ]

每当i为k的倍数时再将不合法的状态清空即可,f[ i ][ S ]=0 (S不能被表示为某一个数的状态 ) 。

可以用矩阵乘法优化。

设 f[ S ]表示走了n步状态为S的方案数,转移矩阵即为 g[ i ][ i ^ (1<<j ) ]=1。

答案就是 f*g^n 。

但是考虑到有k的限制,不妨拆开若干部分讨论。

先走k步,再k步k步走n/k次,最后再走n%k步。

但是我们发现转移矩阵是2^10 *2^10的,时间复杂度达到了(2^10)^3不能接受。

考虑将转移矩阵缩小。

注意到前k步的转移矩阵没有限制,可以随便走。

因此对于走的任意的从x走到y的状态,相同的x^y的矩阵g[ x ][ y ]的值一定是相同的,因为状态改变量相同即步数相同。

那么我们将矩阵含义变成 g[ x ] 表示 该变量为 x 时的方案数。

考虑之前的转移:

G[i][j]=\sum_{k} g[i][k]*g[k][j]

现在变成:

G[i~\^~j]= \sum_{k} g[i~\^~k]*g[k~\^~j]

变形,得:

G[(i~\^~k)~\^~(k~\^~j)]= \sum_{k} g[i~\^~k]*g[k~\^~j]

令x= i^k, y= k^j,有:

G[x~\^~y]= \sum_{k} g[x]*g[y] ~~~(x~\^~y=k)

也就是说我们只需要枚举一个x和y,就可以转移到 G[x^y]了

\sum_{x} \sum_{y} G[x~\^~y] +=g[x]*g[y]

至此,我们成功地将复杂度降低为(2^10)^2级别。

其实上式就是异或卷积的形式。

处理完这个之后我们发现因为k步之后必为一个有效的数字,因此我们可以只记录下数字之间的转移,这样就可以避免再将不合法状态清空了。

于是设a[ i ][ j ]表示刚好经过k步之后从数字 i 变成 数字 j 的方案数 (可以为一位数或者两位数)。

接下来我们就可以用标准的矩阵乘法转移a了,这里我们转移n/k次,表示k步k步走,同时也保证了每k步之后必为有效的状态。

A[i][j]=\sum_{k} a[i][k]*a[k][j]

再设f[ i ]表示 从初始数字x走到数字 i 走n步的方案数。

那么这里 f=f*A^{\left \lfloor \frac{n}{k} \right \rfloor}

最后就是处理n%k的余下部分,这一部分同样没有限制,因此和第一部分一样,最后再将处理出来的 f 和 A 乘起来就是答案了。

这题特别要注意的是:

在处理一位数的时候,数字只有一位,状态只有2^5。

但是在处理两位数时,所有数字都是两位,因此一位数的状态要加上前导0的状态!!!

这个地方调了我1h+。

Code 

#include<cstdio>
#include<cstring>
#include<algorithm>
#define I int
#define ll unsigned long long
#define F(i,a,b) for(I i=a;i<=b;i++)
#define Fd(i,a,b) for(I i=a;i>=b;i--)
#define mem(a,b) memset(a,b,sizeof a)
#define N 1050
#define M 1000000007
using namespace std;
ll n,K,m,st,now,s[102],g[N],t[N],h[N],f[102],a[102][102],c[102][102];
I num[10][5]={{0,1,0,1,0},{0,1,0,0,0},{1,0,0,1,0},{1,1,1,0,0},{0,1,0,0,1},
{1,0,1,0,1},{0,0,1,1,0},{1,1,0,0,0},{1,0,1,1,1},{1,1,1,0,1}};
I p[3]={0,4,9},q[3]={0,31,1023},l[3]={0,9,99};
I R(ll &x){
	x=0;char c=getchar();
	while(c<'0'||c>'9') c=getchar();
	while(c>='0'&&c<='9'){x=x*10+c-'0';c=getchar();}
}
void tim(ll a[N],ll b[N]){
	F(i,0,q[m]) F(j,0,q[m])	t[i^j]=(t[i^j]+a[i]*b[j]%M)%M;
	F(i,0,q[m]) a[i]=t[i],t[i]=0;
}
void work(ll now){
	mem(g,0),mem(h,0);
	F(i,0,p[m]) g[1<<i]=h[1<<i]=1;
	while(now){
		if(now&1) tim(g,h);
		tim(h,h);
		now>>=1;
	}
	mem(a,0);
	F(i,0,l[m]) F(j,0,l[m]) a[i][j]=g[s[i]^s[j]]%M;
}
void cg(){
	F(i,0,l[m]) F(j,0,l[m]) t[i]=(t[i]+f[j]*a[j][i]%M)%M;
	F(i,0,l[m]) f[i]=t[i],t[i]=0;
}
I main(){
	freopen("display.in","r",stdin);
	freopen("display.out","w",stdout);
	R(m),R(n),R(K),R(st);
	F(i,0,9){
		F(j,0,4) s[i]=s[i]*2+num[i][j];
	}
	if(m==2){
		F(i,10,l[m]){
			I x=i/10,y=i%10;
			s[i]=(s[x]<<5)+s[y];
		}
		F(i,1,9) s[i]=(s[0]<<5)+s[i];
		s[0]=(s[0]<<5)+s[0];
	}
	work(K-1);
	f[st]=1;
	now=n/K;
	while(now){
		if(now&1) cg();
		mem(c,0);
		F(i,0,l[m]) F(j,0,l[m]) F(k,0,l[m]) c[i][j]=(c[i][j]+a[i][k]*a[k][j]%M)%M;
		F(i,0,l[m]) F(j,0,l[m]) a[i][j]=c[i][j];
		now>>=1;
	}
	if(n%K){work(n%K-1),cg();}
	F(i,0,9+(m==2)*90) printf("%lld\n",f[i]);
	return 0;
}

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值