CF388D Fox and Perfect Sets

一、题目

点此看题

二、解法

我们考虑用线性基表示 S S S,集合 S S S还需要一些线性基组合出来的,但是不在线性基内的数,我们就保证线性基的最大值在 n n n以内。但是还要保证映射的唯一性,我们把线性基消除成上三角形式,就是如果一个基的最高位有值,那么其他基就必须 0 0 0,举个例子( makedown \text{makedown} makedown小技巧:二级对齐\begin{alignedat}{2}\end{alignedat}):
1 x x x 0 x x x 0 1 x x x 0 1 \begin{alignedat}{2} 1xxx&0xxx&0\\ &1xxx&0\\ &&1 \end{alignedat} 1xxx0xxx1xxx001其中 x x x可以随便填,这样就可以保证一一对应,从两方面说明:如果基位置不同或者基个数不同,那么集合肯定不同,如果 x x x不同那么集合也不同。这样就说明了是唯一映射。

那么问题变成了求满足上述条件的线性基个数,可以考虑二进制位 d p dp dp,设 d p [ i ] [ j ] [ k ] dp[i][j][k] dp[i][j][k]表示从高到低位,现在到了 i i i这一位, j j j为基的个数, k k k表示 m a x max max是否顶到上界,满足条件的线性基个数,来讨论一波转移。

Case one:k=0
  • 不新加入基,而是修改 x x x(含义见上图),然后我们随便怎么填都能满足条件,因为当前的 k k k已经为 0 0 0了,转移: d p [ i − 1 ] [ j ] [ 0 ] = d p [ i ] [ j ] [ 0 ] × 2 j dp[i-1][j][0]=dp[i][j][0]\times 2^j dp[i1][j][0]=dp[i][j][0]×2j
  • 新加入基,转移: d p [ i − 1 ] [ j + 1 ] [ 0 ] = d p [ i ] [ j ] [ 0 ] dp[i-1][j+1][0]=dp[i][j][0] dp[i1][j+1][0]=dp[i][j][0]
Case two:k=1

如果 n n n i − 1 i-1 i1上有值的话:

  • 使最大值 i − 1 i-1 i1位为 0 0 0,那么: d p [ i − 1 ] [ j ] [ 0 ] = x × d p [ i ] [ j ] [ 1 ] dp[i-1][j][0]=x\times dp[i][j][1] dp[i1][j][0]=x×dp[i][j][1],其中 x x x i − 1 i-1 i1位为 0 0 0的方案数(如果 j = 0 j=0 j=0那么 x = 1 x=1 x=1,否则 x = 2 j − 1 x=2^{j-1} x=2j1
  • 使最大值 i − 1 i-1 i1位为 1 1 1,那么: d p [ i − 1 ] [ j ] [ 1 ] = y × d p [ i ] [ j ] [ 1 ] dp[i-1][j][1]=y\times dp[i][j][1] dp[i1][j][1]=y×dp[i][j][1],其中 y y y i − 1 i-1 i1位为 1 1 1的方案数(如果 j = 0 j=0 j=0那么 y = 0 y=0 y=0,否则 y = 2 j − 1 y=2^{j-1} y=2j1
  • 加入一个基: d p [ i − 1 ] [ j + 1 ] [ 1 ] = d p [ i ] [ j ] [ 1 ] dp[i-1][j+1][1]=dp[i][j][1] dp[i1][j+1][1]=dp[i][j][1]

o t h e r w i s e otherwise otherwise,只能让当前位为 0 0 0 d p [ i − 1 ] [ j ] [ 1 ] = x × d p [ i ] [ j ] [ 1 ] dp[i-1][j][1]=x\times dp[i][j][1] dp[i1][j][1]=x×dp[i][j][1]

#include <cstdio>
#define int long long
const int jzm = 1e9+7;
int read()
{	
	int x=0,flag=1;char c;
	while((c=getchar())<'0' || c>'9') if(c=='-') flag=-1;
	while(c>='0' && c<='9') x=(x<<3)+(x<<1)+(c^48),c=getchar();
	return x*flag;
}
int n,ans,dp[35][35][2];
int add(int &a,int b)
{
	a=(a+b)%jzm;
}
signed main()
{
	n=read();
	dp[30][0][1]=1;
	for(int i=30;i>0;i--)
		for(int j=0;j<=30;j++)
		{
			add(dp[i-1][j][0],(1<<j)*dp[i][j][0]%jzm);
			add(dp[i-1][j+1][0],dp[i][j][0]);
			int x=j?(1<<j-1):1,y=j?(1<<j-1):0;
			if(n>>(i-1)&1)
			{
				add(dp[i-1][j][0],x*dp[i][j][1]%jzm);
				add(dp[i-1][j][1],y*dp[i][j][1]%jzm);
				add(dp[i-1][j+1][1],dp[i][j][1]);
			}
			else add(dp[i-1][j][1],x*dp[i][j][1]%jzm);
		}
	for(int i=0;i<=30;i++)
		add(ans,dp[0][i][0]),add(ans,dp[0][i][1]);
	printf("%lld\n",ans);
}
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值