zoukankan      html  css  js  c++  java
  • 【状压】ARC058E 和風いろはちゃん / Iroha and Haiku

    题目链接

    是我想不到的的状压方式。

    首先,条件中的“存在”比较麻烦,由于一个相同的数列可能包含多个满足条件的三元组,用乘法原理直接正面刚会出现重复计数的情况。

    正难则反,所以我们反向考虑用总的(10^n)的方案数减去不合法的数列。


    注意到(X+Y+Zleq 17),所以可以考虑状压。

    状压的方式非常不容易想到,具体是:

    我们用二进制位数来表示数字,例如:(5->10000 , 3->100)

    如果(5)后面是加上一个(3),即序列的一个后缀是(53),那么这个状态就用(10000100)表示(把两个数的二进制表示拼在一起)。同时,这个新的二进制状态的第(8)位是(1),表示(5+3=8);第(3)位为(1),表示最后一个数是(3),那么倒数第二个数就可以用(8-3=5)算出来。

    举个更复杂的例子,假如后缀是(536)(五三打钱),我们的二进制状态就是(10000100100000),那么我们可以通过这个二进制状态得到所有(536)这个后缀的所有情况:

    (14)(第(14)位上为(1,14=5+3+6)

    (9)(第(9)位上为(1,9=3+6)

    (6)(第(6)位上为(1,6=6)

    (5)(14-9=5=5)

    (8)(14-6=8=5+3)

    (3)(9-6=3=3)

    (这实际上就是一种变相的后缀和)

    那么什么样的状态表示俳句呢?

    俳句是连续三段数,和分别为(X,Y,Z),所以俳句对应的二进制第(X+Y+Z,Y+Z,Z)位上都是(1)

    用这种方式表示状态,状态的最大位数就是最大和,所以状态数是(2^{X+Y+Z})种,在时空承受范围之内。


    关于(dp)过程,设(dp[i][s])表示转移到第(i)位,数列结尾的状态为(s)的方案数。

    转移时枚举后继数字(第(i+1)位)为(j),那么新的二进制状态就是(s'=(s<<j)|(1<<(j-1)))

    则有(dp[i+1][s']+=dp[i][s])

    边界情况(dp[0][0]=1)

    另外,这里有一点需要提一下,就是(n)的范围比较大,而每个数字的范围在([1,10]),那么最大的数字和应该是(40),但是我们只需要(dp)和是(x+y+z)的部分,因为这个是后缀和,前面的数不管是啥并不重要,我们的(i)指针在不断移动的时候,就计算了不同位置的方案。


    ►Code View

    #include<cstdio>
    #include<algorithm>
    #include<queue>
    #include<cstring>
    #include<string>
    #include<map>
    #include<cmath>
    using namespace std;
    #define N 45
    #define INF 0x3f3f3f3f
    #define MOD 1000000007
    #define LL long long
    int rd()
    {
    	int x=0,f=1;char c=getchar();
    	while(c<'0'||c>'9'){if(c=='-')f=-1; c=getchar();}
    	while(c>='0'&&c<='9'){x=(x<<3)+(x<<1)+(c^48); c=getchar();}
    	return f*x;
    }
    int n,x,y,z;
    LL dp[N][1<<18];
    LL ksm(LL a,LL b)
    {
    	LL res=1;
    	while(b)
    	{
    		if(b&1) res=res*a%MOD;
    		a=a*a%MOD;
    		b>>=1;
    	}
    	return res;
    }
    int main()
    {
    	n=rd(),x=rd(),y=rd(),z=rd();
    	int jdg=((1<<(x+y+z-1))|(1<<(y+z-1))|(1<<(z-1)));
    	dp[0][0]=1;
    	int sum=(1<<(x+y+z))-1;//最高位是x+y+z-1 所以最大和就是所有位数都是1 也就是2^(x+y+z)-1 
    	for(int i=0;i<=n-1;i++)
    		for(int s=0;s<=sum;s++)
    		{
    			if(dp[i][s]==0) continue;//不会产生贡献
    			for(int j=1;j<=10;j++)//枚举第i+1位是什么数字
    			{
    				int t=(s<<j)|(1<<(j-1));
    				t&=sum;//防止溢出
    				if((t&jdg)==jdg) continue;//存在俳句
    				else dp[i+1][t]=(dp[i+1][t]+dp[i][s])%MOD;
    			}
    		}
    	LL ans=0;
    	for(int s=0;s<=sum;s++)
    		ans=(ans+dp[n][s])%MOD;
    	ans=(ksm(10,n)-ans+MOD)%MOD;
    	printf("%lld
    ",ans);
        return 0;
    }
    
  • 相关阅读:
    多线程编程学习笔记——任务并行库(三)
    多线程编程学习笔记——任务并行库(二)
    多线程编程学习笔记——任务并行库(一)
    多线程编程学习笔记——线程池(三)
    多线程编程学习笔记——线程池(二)
    多线程编程学习笔记——线程池(一)
    多线程编程学习笔记——线程同步(三)
    多线程编程学习笔记——线程同步(二)
    多线程编程学习笔记——线程同步(一)
    多线程编程学习笔记-基础(三)
  • 原文地址:https://www.cnblogs.com/lyttt/p/13859086.html
Copyright © 2011-2022 走看看