集合选数
问题:
《集合论与图论》这门课程有一道作业题,要求同学们求出{\(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; }