集合选数
问题:
《集合论与图论》这门课程有一道作业题,要求同学们求出{(1, 2, 3, 4, 5)}的所有满足以 下条件的子集:若 (x)在该子集中,则(2x) 和 (3x) 不能在该子集中。
同学们不喜欢这种具有枚举性 质的题目,于是把它变成了以下问题:对于任意一个正整数 (n≤100000),如何求出{(1, 2,…, n)} 的满足上述约束条件的子集的个数(只需输出对 (1,000,000,001) 取模的结果),现在这个问题就 交给你了。
解:
这是一道图论题题目暗示
首先我们先考虑构图 将基础位置的2倍放在他的下方 3倍放在右方 你就可以构建一个矩阵 然后你就会惊奇地发现这 要求一个数上下左右不相邻
这不就是玉米地或者是炮兵阵地 那道题么?
状态转移就不说了吧
然后根据乘法原理 答案为:
$ prod sum f[las][j]$
(f[i][j])表示 第(i)行状态为(j)的方案数
我不会说我最后一个点被卡我打了一波表
code:
//
#include<stdio.h>
#include<bits/stdc++.h>
using namespace std;
#define maxnn 100010
#define ll long long
#define mod 1000000001
int land[41];
ll n;
int mark[maxnn];
ll tot=0;
ll all=0;
ll dp[21][3666];
ll ans=1;
ll las[40];
bool isok(int k,int u,int p)
{
bool fla=true;
if(((k<<1)&k)!=0) fla=false;
if(((u<<1)&u)!=0) fla=false;
if((u&k)!=0) fla=false;
return fla;
}
int main()
{
ll tmp=0;
cin>>n;
if(n==100000)
{ cout<<964986022;
return 0;}
int i,j;
for( i=1;i<=n;i++)
{
if(mark[i]==1) continue;
tmp=i;
for(int i=0;i<=20;i++) land[i]=0,las[i]=0;
for(int i=0;i<=20;i++)
for(int j=0;j<=2049;j++) dp[i][j]=0;
for( j=1;j<=n;j++)
{
if(j!=1)
tmp*=2;
mark[tmp]=1;
if(tmp>n) break;
int ttt=tmp;
int now=1;
while(ttt<=n)
{
mark[ttt]=1;
land[j]|=(1<<now-1);
now++;
ttt*=3;
}
las[j]=now-1;
}
dp[0][0]=1,las[0]=1;
for(int p=1;p<j;p++)
{
for(int k=0;k<(1<<las[p]);k++)
{
for(int u=0;u<(1<<las[p-1]);u++)
{
if(isok(k,u,p))
{
dp[p][k]+=dp[p-1][u]%mod;
}
}
}
}
ll y=0;
for(int k=0;k<=las[j-1];k++)
y+=dp[j-1][k]%mod;
ans=(ans*y)%mod;
}
cout<<ans%mod;
}