1 条题解

  • 0
    @ 2025-8-24 22:13:14

    自动搬运

    查看原文

    来自洛谷,原作者为

    avatar Alex_Wei
    **

    搬运于2025-08-24 22:13:14,当前版本为作者最后更新于2019-11-10 10:33:34,作者可能在搬运后再次修改,您可在原文处查看最新版

    自动搬运只会搬运当前题目点赞数最高的题解,您可前往洛谷题解查看更多

    以下是正文


    T4. Magical Gates\color{#9400d3}T4.\ Magical\ Gates

    题面\color{#9400d3}\text{题面}

    官方题解\color{#9400d3}\text{官方题解}

    欢迎爆踩 std\mathrm{std}


    你感受过明天就要比赛却在比赛前一天发现有相似类型的题目的绝望吗?

    双倍经验,如果你过了这道题目就一定能 AC\mathrm{AC} 本题:P4317P4317

    可以将本题看成该题的超级加强版(但是我们出题的时候并不知道有该题的存在)


    本题主要考察数位 DP\mathrm{DP}

    暴力分 :10%:10\%

    • 下文中所有的 nn 都代表该测试点的数据范围(可以理解为 n=maxrn=\max r

    • 下文中所有的 log\log22 为底

    • 下文中,(ij)\binom{i}{j} 表示从 jj 个不同元素中选出 ii 个元素的方案数,即我们通常说的组合数,也可表示为 CjiC_{j}^{i},计算公式为 j!i!(ji)!\frac{j!}{i!(j-i)!}


    Sol 1:10%Sol\ 1:10\%

    将题目所给代码直接复制 ++ 暴力即可,可拿到前 22 个测试点的分数,共 10%10\%


    Sol 2:20%Sol\ 2:20\%

    发现题目中所给 get 函数就是求:一个数在二进制下有多少个 11

    11051-10^5 中的每个数 Θ(logn)\Theta(\log n) 预处理出有多少个 11

    然后暴力回答询问即可

    时间复杂度:Θ(nlogn)\Theta(n\log n)


    Sol 3:35%Sol\ 3:35\%

    前置知识:乘法逆元和有理数取余,如果不会请先移步 P3811P3811P2613P2613

    Θ(1)\Theta(1) 预处理出 11071-10^7 之间每个数有多少个 11,记录一下每个数的前缀和与前缀积,然后就可以 Θ(logp)\Theta(\log p) 回答每个询问

    因为模数 pp 为质数,所以可以费马小定理求逆元

    您可能会因为空间/时间被卡而拿不到应得的分数

    时间复杂度:Θ(n+Tlogp)=Θ(n)\Theta(n+T\log p)=\Theta(n)

    代码:

    #include<bits/stdc++.h>
    using namespace std;
    #define ll long long
    #pragma GCC optimize(3)
    int t,d[10000005],q[10000005];
    ll k[10000005],p,n;
    ll ksm(ll a,ll b,ll p){ll ans=1;while(b){if(b&1)ans=(ans*a)%p;b>>=1,a=(a*a)%p;}return ans;}
    int lowbit(int x){return x&-x;}
    int main()
    {
    	cin>>t>>p>>n;
    	for(int i=1;i<=1e7;i++)
    		d[i]=d[i-lowbit(i)]+1;
    	k[0]=1;
    	for(int i=1;i<=1e7;i++)
    		q[i]=q[i-1]+d[i],k[i]=k[i-1]*d[i]%p;
    	while(t--){
    		int l,r;
    		cin>>l>>r;
    		cout<<q[r]-q[l-1]<<" "<<k[r]*ksm(k[l-1],p-2,p)%p<<endl;//费马小定理求逆元 
    	}
    	return 0;
    }
    

    Sol 4:31%Sol\ 4:31\%

    前置知识:高精,找规律,欧拉定理,如果不会请先移步 P5091P5091

    找规律(或者推出结论),当 l=1,r=2kl=1,r=2^k

    i=lrdi=k×2k1+1\sum_{i=l}^{r}d_i=k\times 2^{k-1}+1 $$\prod_{i=l}^{r}d_i=\prod_{i=1}^{k}i^{\binom{i}{k}} $$

    可以 Θ(log2n)\Theta(\log^2 n) 预处理出 (ij)\binom{i}{j} ,再用快速幂 Θ(lognloglogn)\Theta(\log n\log\log n) 计算上式

    • 根据欧拉定理,因为 (ij)\binom{i}{j} 在指数的位置上,且 pp 为素数,所以应对 φ(p)=p1\varphi(p)=p-1 取模

    时间复杂度 $\Theta(T(\log^2 n+\log n\log\log n))=\Theta(\log^2 n)$

    代码:

    #include<bits/stdc++.h>
    using namespace std;
    #define ll long long
    ll t,p,p2,n,C[3333][3333];
    string l,r;
    int x[1005];
    ll ksm(ll a,ll b){ll s=1,m=a;while(b){if(b&1)s=s*m%p;m=m*m%p;b>>=1;}return s;}
    int main()
    {
    	cin>>t>>p>>n,p2=p-1;
    	int N=n<15?70:3332;
    	for(int i=1;i<N;i++)
    		for(int j=0;j<=i;j++)
    			if(j==0||i==j)C[i][j]=1;
    			else C[i][j]=(C[i-1][j]+C[i-1][j-1])%p2;
    	for(int i=0;i<t;i++){
    		cin>>l>>r;
    		int top=r.size(),bit=0;
    		for(int j=0;j<r.size();j++)
    			x[j]=r[r.size()-j-1]-'0';
    		while(top){
    			bit++;
    			int tmp=0;
    			for(int j=top-1;j>=0;j--)
    				tmp=tmp*10+x[j],x[j]=tmp/2,tmp%=2;
    			if(!x[top-1])top--;
    		}
    		bit--;
    		int mul=1;
    		for(int j=1;j<=bit;j++)
    			mul=mul*ksm(j,C[bit][j])%p;
    		cout<<(bit*ksm(2,bit-1)+1)%p<<" "<<mul<<endl;
    	}
    	return 0;
    }
    

    Sol 5:54%Sol\ 5:54\%

    结合 Sol 3Sol\ 3Sol 4Sol\ 4 可拿到 54%54\% 的高分

    代码就不贴了


    Sol 6:100%Sol\ 6:100\%

    前置知识:高精,欧拉定理,数位 DP\mathrm{DP}

    • 在下文中,x(2)x_{(2)} 表示 xx 在二进制下表示的数

    我们考虑数位 DP\mathrm{DP}

    dpidp_i 表示在 [1,x][1,x] 中,二进制下 11 的个数为 ii 个的数有多少个,然后将 [1,r][1,r] 的结果减去 [1,l)[1,l) 的结果就是 [l,r][l,r] 的结果,最后的答案就是 i=1ki×dpi\sum_{i=1}^{k}i\times dp_ii=1kidpi\prod_{i=1}^{k}i^{dp_i}

    Q1:\large Q1:

    如何计算区间 [1,x][1,x]dpidp_i

    A1:\large A1:

    • x(2)x_{(2)} 一共有 kk 位,从左往右遍历 x(2)x_{(2)} 的每一位(只有可能为 0011

    • 如果该位为 00:跳过,不考虑

    • 如果该位为 11 :将该位为 00右边的位为任意值 的情况讨论到答案中,这样保证了不会 少计算/多计算/重复计算,且在接下来的讨论中,将该位的值看作为 11

    即设当前考虑到第 j(j[1,k])j(j\in[1,k]) 位(从右往左数),设该位左边共有 cntcnt11

    对于所有 dpi+cnt(i[0,j))dp_{i+cnt}(i\in[0,j)),加上 (ij1)\binom{i}{j-1},最后再将 cntcnt11

    • 遍历过所有位之后,还要将 x(2)x_{(2)} 本身的答案算进去,即 dpcntdp_{cnt}11

    Q2:Q2:

    为什么我按照 A1A1 的方法做,却 WA\color{red}\mathrm{WA} 掉了?

    A2:\large A2:

    因为求积的 dpidp_i 在指数的位置上,且 pp 为质数,所以该部分的 dpidp_i 应对 φ(p)=p1\varphi(p)=p-1 取模,而不是 pp

    同样的,上式 dpi+cnt(i[0,j))+(ij1)dp_{i+cnt}(i\in[0,j))+\binom{i}{j-1} 中的 (ij1)\binom{i}{j-1} 也应对 p1p-1 取模

    Q3:\large Q3:

    为什么我按照 A1,A2A1,A2 的方法做,却 TLE\color{#0000AA}\mathrm{TLE} 了?

    A3:\large A3:

    可以先 Θ(log2n)\Theta(\log^2 n) 预处理出 (ij)\binom{i}{j}ppp1p-1 取模的值,计算的时候就不需要再 Θ(logn)\Theta(\log n) 处理了(虽然先预处理出阶乘和阶乘逆元也不错,但是常数较大)

    千万别小看这个 Θ(logn)\Theta(\log n),因为 nn 有可能到 10100010^{1000},所以 maxlogn1000×log(10)3322\max\log n\approx 1000\times \log(10)\approx3322

    Q4:\large Q4:

    这个算法的时间复杂度是多少?

    A4:\large A4:

    一次 A1A1 操作时间复杂度 Θ(log2n)\Theta(\log^2n),共 2T2T 次操作且 TT 为常数,预处理 Θ(log2n)\Theta(\log^2n),总时间复杂度 Θ(log2n)\Theta(\log^2n)

    Q5:\large Q5:

    说了这么多废话,赶紧把代码拿出来

    A5:\large A5:

    代码:

    #include<bits/stdc++.h>
    using namespace std;
    #define ll long long
    const int N=3333; 
    ll ksm(ll a,ll b,ll p){ll ans=1;while(b){if(b&1)ans=(ans*a)%p;b>>=1,a=(a*a)%p;}return ans;}
    ll t,p,p2,n,k,add[N][N],mult[N][N],prod[N],sum[N];
    bool bit[N];//二进制
    string l,r;
    void change(string s,bool minus)//将字符串转成二进制数,minus 为是否减 1(计算l时需要)
    {
    	k=0;
    	int l=s.size()-1,p[N],high=l;
    	for(int i=l;i>=0;i--)p[i]=s[l-i]-'0';//高精度
    	if(minus){
    		p[0]--;
    		int pos=0;
    		while(p[pos]<0)p[pos+1]--,p[pos]+=10,pos++;
    		if(!p[high])high--;
    	}
    	while(high>=0){
    		k++;
    		int res=0;
    		for(int i=high;i>=0;i--){
    			res=res*10+p[i];
    			p[i]=res/2;
    			res%=2;
    		}
    		bit[k]=res;
    		if(!p[high])high--;
    	}
    }
    void solve()//一组数据
    {
    	memset(sum,0,sizeof(sum));
    	memset(prod,0,sizeof(prod));
    	cin>>l>>r;
    	change(r,0);
    	int cnt=0;
    	for(int j=k;j>0;j--)
    		if(bit[j]){
    			for(int i=0;i<j;i++)
    				sum[i+cnt]=(sum[i+cnt]+add[j-1][i])%p,
    				prod[i+cnt]=(prod[i+cnt]+mult[j-1][i])%p2;
    			cnt++;
    		}
    	sum[cnt]++,prod[cnt]++;//计算[1,r]
    	change(l,1),cnt=0;
    	for(int j=k;j>0;j--)
    		if(bit[j]){
    			for(int i=0;i<j;i++)
    				sum[i+cnt]=(sum[i+cnt]-add[j-1][i]+p)%p,
    				prod[i+cnt]=(prod[i+cnt]-mult[j-1][i]+p2)%p2;
    			cnt++;
    		}
    	sum[cnt]=(sum[cnt]-1+p)%p,prod[cnt]=(prod[cnt]-1+p2)%p2;//减去[1,l)
    }
    void init()
    {
    	cin>>t>>p>>n,p2=p-1;
    	int lim;
    	if(n<18)lim=64;
    	else if(n<21)lim=333;
    	else lim=3333;//避免浪费时间和空间
    	add[0][0]=mult[0][0]=1;
    	for(int i=1;i<lim;i++){
    		add[i][0]=mult[i][0]=1;
    		for(int j=1;j<=i;j++){
    			add[i][j]=(add[i-1][j]+add[i-1][j-1])%p;
    			mult[i][j]=(mult[i-1][j]+mult[i-1][j-1])%p2;
    		}
    	}//预处理出组合数
    }
    int main()
    {
    	init();
    	for(ll i=1;i<=t;++i){
    		solve();
    		ll ans=0;
    		for(ll j=1;j<N;j++)
    			ans=(ans+sum[j]*j)%p;
    		cout<<ans<<" ";
    		ans=1;
    		for(ll j=1;j<N;j++)
    			ans=(ans*ksm(j,prod[j],p))%p;//这里是对 p 取模,因为已经在算最终的答案了
    		cout<<ans<<endl;
    	}
        return 0;
    }
    

    当然,解法并不唯一,如果有更好的思路也欢迎大家在题解区发布!

    如果题解有误请私信我,或在右侧评论区指出

    • 1

    信息

    ID
    4560
    时间
    300~2000ms
    内存
    256MiB
    难度
    6
    标签
    递交数
    0
    已通过
    0
    上传者