1. 程式人生 > >Codeforces 388D Fox and Perfect Sets

Codeforces 388D Fox and Perfect Sets

題面

題意

一個集合如果是完美的,則集合如果包含a,b,那麼a^b也一定在這個集合內,求所有最大數小於等於n的集合數量。

做法

對於完美集合,我們可以發現一個線性基一定對應一個合法的完美集合,因此我們可以對線性基進行數位dp,因為一個完美集合可以對應多種線性基,所以可以通過高斯消元使其唯一,也就是說,對於一個經過高斯消元的線性基的每一位,如果它是某個基的最高位,則只有那一個基的這一位為1,反之任何最高位大於這一位的基的這一位可以為任意數(滿足最大值小於等於n時),且所有基的異或值即為這個線性基所能表示的最大值。
因此可以進行數位dp,dp[i][j][k]表示當前考慮的位為第i位,已經選了j個基,k表示其是否受k的性質。

當不受n的限制時,即無論後面幾位怎麼樣,其最大值都不會大於n:
若新增以i為最高位的基時,dp[i][j][0]可以轉移到dp[i-1][j+1][0],且只有一種轉移方式,因為前j個基的這一位都是0.
若不新增以i為最高位的基時,dp[i][j][0]可以轉移到dp[i-1][j][0],且有(1 << j)種轉移方式,因為前j個基的這一位可以為任意數.

當受到n的限制時:
如果n的第i位為0:
顯然不能新增以i位最高位的基,否則異或值必將大於n,而且前j個基的這一位不能隨便選,要保證前j位的異或值的第i位為0,即前j個基種一共有偶數個基的這一位是1,這可以用組合數去算,也就是說dp[i][j][1]可以轉化到dp[i-1][j][1],有F(j,0)種轉移方式。
F(j,0)=C(j,0)+C(j,2)+C(j,4)…

如果n的第i位為1:
若新增以i這一位為最高位的基,則同理,僅一種轉移:dp[i][j][1]轉移到dp[i-1][j+1][1]
若不新增,則有以下兩種情況:
1.前j位中,第i位為1的數一共有偶數個時,則最大值的第i位一定為0,此線性基的最大值一定小於n,因此dp[i][j][1]可以轉移到dp[i-1][j][0],有F(j,0)中轉移方式。
2.前j位中,第i位為1的數一共有奇數個時,則最大值的第i位為1,此線性基的前j+1位仍然與n相同,其最大值不一定小於n,故dp[i][j][1]可以轉移到dp[i-1][j][1],有F(j,1)中轉移方式。
F(j,1)=C(j,1)+C(j,3)+C(j,5)…

程式碼

#include<iostream>
#include<cstdio>
#define ll long long
#define N 40
#define M 1000000007
using namespace std;

ll n,m,C[N][N],cnt[N][2],dp[N][N][2],ans;

inline void add(ll &u,ll v)
{
	u+=v;
	u%=M;
}

int main()
{
	ll i,j;
	cin>>n;
	for(i=1;i<=n;i<<=1,m++);
	for(i=0; i<=m; i++)
	{
		C[i][0]=C[i][i]=1;
		for(j=1; j<i; j++)
		{
			C[i][j]=(C[i-1][j]+C[i-1][j-1])%M;
		}
		for(j=0; j<=i; j++)
		{
			add(cnt[i][j&1],C[i][j]);
		}
	}
	
	dp[m][0][1]=1;
	for(i=m; i>=1; i--)
	{
		for(j=0; j<=m; j++)
		{
			add(dp[i-1][j][0],dp[i][j][0]*(1 << j)%M);
			add(dp[i-1][j+1][0],dp[i][j][0]);
			if((1 << (i-1))&n)
			{
				add(dp[i-1][j][0],dp[i][j][1]*cnt[j][0]%M);
				add(dp[i-1][j][1],dp[i][j][1]*cnt[j][1]%M);
				add(dp[i-1][j+1][1],dp[i][j][1]);
			}
			else add(dp[i-1][j][1],dp[i][j][1]*cnt[j][0]%M);
		}
	}
	
	for(i=0; i<=m; i++)
	{
		add(ans,dp[0][i][0]+dp[0][i][1]);
	}
	cout<<ans;
}