2020小米网络赛第二场 F-Modulo Nine

博客介绍了F-Modulo Nine问题的解决思路,通过逐位填数的动态规划方法,分析了如何确定状态转移方程,并利用前缀和优化将时间复杂度降低到O(n^2)。文章详细阐述了如何处理3、6的因子,以及0、9的情况,最后给出了实现代码。
摘要由CSDN通过智能技术生成

题意

F-Modulo Nine
给定m个区间[li,ri],问带前导零的n位数,有多少个数满足: ∀ i , ∏ k = l i r i a k % 9 = = 0 \forall i,\prod_{k=l_i}^{r_i}a_k \%9==0 i,k=liriak%9==0
其中a[k]表示这个数的第k位,n,m<=1000.

分析

思维方向有很多,比较靠谱的是逐位填数dp。
一个区间要合法,只需要有2个3的因子即可,一个0、9算两个3的因子,3、6算一个。我们先不考虑填0、9,考虑只填3、6。那么一个位置的状态只有两种,3的因子或者非3的因子。
我们只用记录最后两个3的因子填在哪里。因为假如一个区间有超过两个因子,前面的可以忽略掉。
设f[i][j]表示最后两个因子出现在i,j上(i<j),其中右端点在j之前的区间已经全部合法,的方案数。
这样的记录的好处是,当f[j][k]要从f[i][j]转移过来的时候,我们知道右端点在j之前的区间已经全部合法了,而j~k-1的区间还未判断,而且他们要合法的最后机会就是i足够大,使得区间包含i,而j一定包含。
那么 f [ j ] [ k ] = ∑ i f [ i ] [ j ] ∗ 6 k − j − 1 ∗ 2 ∗ [ 右 端 点 在 j 至 k − 1 的 区 间 的 左 端 点 都 小 于 等 于 i ] f[j][k]=\sum_i f[i][j]*6^{k-j-1}*2*[右端点在j至k-1的区间的左端点都小于等于i] f[j][k]=if[i][j]6kj12[jk1i]
系数中,6的幂指j+1~k-1的位置可以填非3因子的数,2指的是k可以填3、6。
注意到限制条件中,左端点都<=i,就是最大值<=i。那么对于每个右端点相同的区间,我们保留左端点最大的那个即可,记Le[x]表示右段点在x的最大的左端点下标。
现在的还有0\9没有考虑。事实上,当我们得到了f[j][k],我们可以直接用相同的方法转移到f[k][k],只是系数变成了1,因为之前填3\6时,这个位置算了两种方案,0\9刚好也是两个。
初始状态就是f[0][0]=1,最后的答案可以用所有f[j][k]中符合要求的状态求得。
这样暴力转移是O(n^3)的,注意到转移中合法的i一定是连续的,拿个前缀和优化一下转移就行了。

代码

#include<cmath>
#include<cstring>
#include<cstdio>
#include<algorithm>
#include<map>
using namespace std;
#define fo(i,j,k) for(i=j;i<=k;i++)
#define fd(i,j,k) for(i=j;i>=k;i--)
#define cmax(a,b) (a=(a>b)?a:b)
#define cmin(a,b) (a=(a<b)?a:b)
typedef long long ll;
typedef unsigned long long ull;
typedef double db;
const int N=1e3+5,mo=1e9+7,M=1e7+5;
ll f[N][N],six[N],coef,ans,g[N][N];
int Le[N],Lmx,n,m,l,r,i,j,k;
ll get(int l,int r,int j)
{
	if (!l) return g[r][j];
	else return g[r][j]-g[l-1][j];
}
int main()
{
	freopen("F.in","r",stdin);
	//freopen("I.out","w",stdout);
	n=1e3;
	six[0]=1;
	fo(i,1,n) six[i]=six[i-1]*6ll%mo;
	while (scanf("%d %d",&n,&m)!=EOF)
	{
		ans=0;
		fo(i,1,n) Le[i]=0;
		fo(i,0,n) fo(j,0,n) g[i][j]=f[i][j]=0;
		fo(i,1,m) 
		{
			scanf("%d %d",&l,&r);
			Le[r]=max(Le[r],l);
		}
		f[0][0]=1;
		g[0][0]=1;
		fo(k,1,n)
		{
			Lmx=0;
			fd(j,k-1,0)
			{
				// f[j][k]: f[Lmx[j~k-1]~j][j]
				Lmx=max(Lmx,Le[j]);
				if (Lmx>j) break;
				f[j][k]=get(Lmx,j,j)*six[k-j-1]*2%mo;
			}
			g[0][k]=f[0][k];
			fo(j,1,k-1) g[j][k]=(g[j-1][k]+f[j][k])%mo;
			f[k][k]=g[k-1][k];
			g[k][k]=(g[k-1][k]+f[k][k])%mo;
		}
		Lmx=0;
		fo(i,1,n) Lmx=max(Lmx,Le[i]);
		fo(i,0,n)
			if (Lmx<=i)
				fo(j,i,n)
				{
					ans=(ans+f[i][j]*six[n-j])%mo;
				}
		printf("%lld\n",(ans+mo)%mo);
	}
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值