【BZOJ 2734】集合取数

1.题目链接。这个题其实还是一个不错的题目,思路比较新颖。首先构造一个矩阵:

                                

这个矩阵的构造规则是这样的:从左上角开始,a[1][1]=st,a[i][j+1]=3*a[i][j],a[i+1][j]=2*a[i][j].也就是右边的数都是它的三倍,做别的数都是他的两倍,然后得到了这样的一个矩阵。可以发现,这个矩阵里面出现了很多的1-n中的的数。首先考虑第一个矩阵,就是用1为左上角顶点构造的。那么可以发现,我们选取的方案就是:不选取相邻的数即可。可以发现,这个矩阵其实不会太大,20*20都不会超过。所以,可以把每一行的状压一下,然后考虑dp[i[j]表示:前i行,第i行的状态是j的方案,这个其实就很好转移了:

                                                     dp[i][j]=\sum dp[i-1][k]

但是这个转移显然是需要条件的,很明显第一个条件就是j必须是合法的状态,也就是j不能是选取两个相邻的数的转态,这个把j>>1&j 就可以判断,这是行上的约束,接下来就是列上的约束,这个也很好判断,遍历所有的i-1的状态,k和j不能有同一行一样,也就是j&k需要==0,这样只是完成了第一步,因为这个矩阵并不能把全部的数据包含进去。比如5就不在里面,所以遍历一下那些数据不在,优先用这些小的数据当做左上角继续构造矩阵,还是会得到答案。这些得到的答案乘起来就是最后的结果。下面做一些细节的证明:

(1)为什么答案是乘起来,首先可以明确一点的就是不同矩阵的集合,得到的是不一样的,所以考虑最后的答案的时候就是乘法原理的应用。

(2)(1)中有一个假设,就是不同矩阵得到的集合是不同的,这一点其实需要证明。现在这个命题可以等价转化为一个问题:不同矩阵的数值都是不一样的,互不相交,并起来是全集{1,2,3...n}.对于后一点,并起来是全集这个毋庸置疑,是一定的。但是互不相交需要证明:

考虑构造矩阵的方式:一每个较小的值的二倍,三倍填充整个矩阵。当然,填的时候需要都是小于等于n的数。假设有一个数在两个矩阵同时出现了:这两个矩阵的初始值是x和y,那么重复出现的值m:

在x矩阵里一定会被表示为:

                                                     m=x*2^{p_1}*3^{q_1}

在y矩阵里,会被表示为:

                                                     m=y*2^{p_2}*3^{q_2}

这里可以简单地讨论一下,对于x,y分别做质因数分解之后,一定会变成质因数乘积的形式,如果要想等,那么他们的差异一定只会出现在2和3这两个质因子的幂次方上,其他部分一定是一样的,但是如果这两个出现了差异,那么在x矩阵里,一定会有另外一个位置把这个差异覆盖掉,这一点我不好解释,其实就是类似于自由组合的思想。所以他俩不可能相等,那么就说明了,各个矩阵里面的数都是不相等的。其实看了几份题解,都没有提到过这一点,不知道是我多虑了还是大家都默认就是乘起来。

代码:

#include<bits/stdc++.h>
using namespace std;
#define ll long long
const int mod=1e9+1;
bool vis[(int)1e6+10];
int a[20][20];
ll f[20][1<<16];
int top[20];
int n;
ll calc(int x)
{
    a[1][1]=x;
    int i;
    for(i=1;;i++)
    {
        int j;
        for(j=1;;j++)
        {
            vis[a[i][j]]=true;
            a[i][j+1]=a[i][j]*3;
            if(a[i][j+1]>n) break;
        }
        top[i]=j;
        a[i+1][1]=a[i][1]*2;
        if(a[i+1][1]>n) break;
    }
    top[i+1]=0;
    int temp=i;
    for(int i=1;i<=temp;i++)
        for(int j=0;j<(1<<top[i]);j++)
            f[i][j]=0;
    f[temp+1][0]=0;
    f[0][0]=1;
    top[0]=0;
    for(i=0;i<=temp;i++)
    {
        for(int j=0;j<(1<<(top[i]));j++)
        {
            if(f[i][j]==0) continue;
            if((j&(j<<1))) continue;
            for(int k=0;k<(1<<(top[i+1]));k++)
            {
                if(j&k) continue;
                (f[i+1][k]+=f[i][j])%=mod;
            }
        }
    }
    return f[temp+1][0];
}
int main()
{
    scanf("%d",&n);
    ll ans=1;
    for(int i=1;i<=n;i++)
        if(!vis[i])
            ans=ans*calc(i)%mod;
    printf("%lld\n",ans);
    return 0;
}
发布了383 篇原创文章 · 获赞 58 · 访问量 6万+

猜你喜欢

转载自blog.csdn.net/weixin_41863129/article/details/104603457