状压 dp

就是把一连串的状态压缩成一个长的二进制数,可以起到减省空间、简便计算等作用。

这个二进制数的每一位都代表这一位的状态。

#P313. 特殊方格棋盘

标准的模板。

我们把每一列是否防止一辆车的状态化为 0 和 1,那么整体的状态就是一个 \(n\) 位的二进制数。

比如说,\(n=4\) 时,\(01101\) 就表示第一列、第三列和第四列放。

由于一行只能放一个,我们可以得出:

\[dp(01101)=dp(01100)+dp(01001)+dp(00101) \]

抽象化地讲,对于状态 \(S\)

\[dp(S)+=dp(S-2^i) \]

其中 \(i\) 表示满足 \(S\text{ and }1<<(i+1)=1\) 的所有 \(i\)

题目上说,有的格子不能放,那我们也可以把这个状态压缩一下:

对于一个点 \((x,y)\),我们开一个数组 \(a\) 来记录

\[a[x]=a[x] \text{ or }1<<(y-1) \]

如果说点 \((2,3)\) 不能放,那么 \(a[2]=100\)

这样我们在循环每个状态的时候就可以排除掉不能放的点,对于状态 \(S\):

\[S=S \text{ and neg(x)} \]

就好了。

找一个数的最低一个一,使用 \(\text{lowbit}\) 就好。

记得空间要开到 \(2^n\)

#include<bits/stdc++.h>
using namespace std;
int n,m;
long long a[(1<<20)+1];
long long dp[(1<<20)+1];
inline int lowbit(int x)
{
	return x&(-x);
}
int main()
{
	cin>>n>>m;
	int mx=(1<<n)-1;
	for(int i=1;i<=m;i++)
	{
		int x,y;cin>>x>>y;
		a[x]|=(1<<(y-1));
	}
	dp[0]=1;
	for(int i=1;i<=mx;i++)
	{
		int num=i,cnt=0;
		for(;num;num-=lowbit(num))
			++cnt;
		num=i&(~a[cnt]);
		while(num)
		{
			dp[i]+=dp[i^lowbit(num)];
			num-=lowbit(num);
		}
	}
	cout<<dp[mx];
 }

P1896 [SCOI2005] 互不侵犯

标准的状压 dp。

在设计状态时,要考虑到 \(n,k\) 两个变量,同时也有所有的状态。

定义 \(dp[i][s][j]\) 表示第 \(i\) 行,行状态为 \(s\),有 \(j\) 个国王时的方案和。

考虑一个状态是合法的,如果这一位放了国王,那么上一行这里和这里的左右都不能放国王,这一行这个位置的左右也不能放国王。

我们设当前状态是 \(s1\),上一行的状态是 \(s2\),那么通过位运算可以得到合法条件:

\[((s1<<1)\text{ or }(s1>>1)) \text{ and }s1=0 \]

\[(s2\text{ or }(s2<<1)\text{ or }(s2>>1)) \text{ and }s1=0 \]

如果这一行放了 \(k\) 个国王,那么上一行一定是 \(k-sum[s]\) 个。

其中 \(sum[s]\) 表示 \(s\) 状态时这一行有多少个国王,即二进制下 \(s\) 有多少个 \(1\)

\(sum[s]\) 和合法 \(s\) 我们都可以预处理求出,课件上给的求 \(sum[k]\) 的方法是每次右移一位,判有多少个 \(1\),我的做法是每次 \(s-=\text{lowbit}(s)\),这样更快吧。

预处理:

for(int i=0;i<=(1<<n)-1;i++)
{
	int s=i;
	int tot=0;
	while(s)
	{
		tot++;
		s-=lowbit(s);
	}s=i;
	//	cout<<s<<" "<<tot<<endl; 
	cnt[s]=tot;
	if((((s<<1)|(s>>1))&s)==0)
		legal[++num]=s;
}

核心 dp:

dp[0][0][0]=1;
for(int i=1;i<=n;i++)\\循环每一行
{
	for(int j=1;j<=num;j++)\\循环当前所有合法状态
	{
		int s1=legal[j];
		for(int o=1;o<=num;o++)\\循环可转移的上一行合法状态
		{
			int s2=legal[o];
			if(((s2|(s2<<1)|(s2>>1))&s1)==0)
			{
				for(int k=0;k<=mx;k++)
				{
					if(k-cnt[s1]>=0)
						dp[i][s1][k]+=dp[i-1][s2][k-cnt[s1]];
				}
			}
		}
	}
}

P2704 [NOI2001] 炮兵阵地

和上面两个题大同小异,都是棋盘式的。

设计状态:\(dp[i][s1][s2]\) 表示当前行数为第 \(i\) 行,本行状态为 \(s1\),上一行状态为 \(s2\)

判断一个状态 \(s\) 合不合法,首先要看本行内满足 \([(s<<1)\text{ or }(s<<2)\text{ or }(s>>1)\text{ or }(s>>2)]\text{ and }s=0\),这个可以预处理出来,这样我们可以简化后面 \(dp\) 多重循环嵌套时的判断次数。

其次,要判断山地是否为 \(0\),我们通过读入的数据计算。

如果循环到三个状态 \(s1,s2,s3\) 都合法,那么可以转移:

\(dp[i][s1][s2]=\max(dp[i][s1][s2],dp[i-1][s2][s3]+cnt[s1])\)

其中 \(cnt[s]\) 代表状态 \(s\)\(1\) 的个数,可以用 \(\text{lowbit}\) 求得。

这样就基本得解了。

因为第一行和第二行很特殊,所以需要单独处理,并且在循环第三行到第 \(n\) 行的时候要判断 \(n\) 是否等于 \(1\)

观察到每一行状态只与上一行有关,我们可以放心地滚掉一维 ~

#include<bits/stdc++.h>
#define int short
using namespace std;
int n,m;
int a[101];
int dp[2][(1<<10)+1][(1<<10)+1];
int r=1;
int legal[(1<<10)+1];
int num;
int cnt[(1<<10)+1];
inline int max(int x,int y)
{
	return x>y?x:y;
}
inline int lowbit(int x)
{
	return x&-x;
}
signed main()
{
	cin>>n>>m;
	getchar();
	for(int i=1;i<=n;i++)
	{
		for(int j=1;j<=m;j++)
		{
			char c;cin>>c;
		//	cout<<c<<" ";
			if(c=='H')
			{
				a[i]|=(1<<(j-1));
			}
		}//cout<<endl;
	}
//	dp[0][0]=1;
	for(int i=0;i<(1<<m);i++)
	{
		int s=i;
		if((((s<<2)|(s<<1)|(s>>1)|(s>>2))&s)==0)
		{
			legal[++num]=s;
		//	cout<<i<<" "<<cnt[i]<<endl;
		//	for(int j=1;j<=n;j++)dp[j][i]=1;
		}
		for(int tmp=s;tmp;tmp-=lowbit(tmp))cnt[s]++;
	}
//	for(int i=1;i<=num;i++) cout<<legal[i]<<" "<<cnt[legal[i]]<<endl;
	int ans=0;
	for(int i=1;i<=num;i++)
	{
		int s=legal[i];
		if((s&a[1])==0)
		dp[r][s][0]=cnt[s],ans=max(ans,cnt[s]);
	}
	r^=1;
	if(n>1)
	{
	for(int i=1;i<=num;i++)
	{
		int s1=legal[i];
		if((s1&a[2])==0)
		for(int j=1;j<=num;j++)
		{
			int s2=legal[j];
			if((s2&s1)==0&&(s2&a[1])==0)
			{
				dp[r][s1][s2]=max(dp[r][s1][s2],dp[r^1][s2][0]+cnt[s1]);
			}
		}
	}
	for(int i=3;i<=n;i++)
	{
		r^=1;
		for(int j=1;j<=num;j++)
		{
			int s1=legal[j];
			if((s1&a[i])==0)
			for(int k=1;k<=num;k++)
			{
				int s2=legal[k];
				if((s2&a[i-1])==0)
				for(int o=1;o<=num;o++)
				{
					int s3=legal[o];
					if((s3&a[i-2])==0)
					{
						if(((s3|s2)&s1)==0)
						dp[r][s1][s2]=max(dp[r][s1][s2],dp[r^1][s2][s3]+cnt[s1]);
					}
				}
			}
		}
	}
	}
	for(int i=1;i<=num;i++)
	{
		int s1=legal[i];
		if((s1&a[n])==0)
		for(int j=1;j<=num;j++)
		{
			int s2=legal[j];
			if((s2&a[n-1])==0&&(s2&s1)==0)
			{
	//			cout<<dp[r][s1][s2]<<" ";
				ans=max(ans,dp[r][s1][s2]);
			}
		}
	}
	cout<<ans;
	return 0;
}

P8756 [蓝桥杯 2021 省 AB2] 国际象棋

2024-04-03 18:17:54 星期三

这个就很有意思了。

我们观察到每行的每个状态都与上一行和上上一行有关,并且题目要求计算总共摆 \(K\) 个马的方案数,因此本题就像是互不侵犯炮兵阵列的结合体。

定义状态 \(dp[i][s1][s2][j]\) 表示当前第 \(i\) 行,本行状态为 \(s1\),上行状态为 \(s2\),已经摆了 \(j\) 个马,可以得到状态转移方程:

\[dp[i][s1][s2][j]+=dp[i-1][s2][s3][j-cnt[s1]] \]

其中 \(cnt[s]\) 表示状态 \(s\) 含有的 \(1\) 的个数。(都是老套路)

炮兵阵列我们可以知道第一行和第二行需要预处理,因为特殊。

for(int i=0;i<(1<<n);i++) dp[1][i][0][cnt[i]]=1;
for(int s1=0;s1<(1<<n);s1++)
{
	for(int s2=0;s2<(1<<n);s2++)
	{
		if((s1&(s2<<2))|(s1&(s2>>2))) continue;
		for(int j=0;j<=k;j++)
		{
			if(j-cnt[s1]>=0)
				dp[2][s1][s2][j]+=(dp[1][s2][0][j-cnt[s1]]); 
		}
	}
}

在转移时我们要注意判断是否合法,按照题目中给出的图去做就好了。

这里给出一个思考题:

判合法时需要看上一行状态 \(s2\) 和上上一行状态 \(s3\),并且和 \(s1\) 取与(具体请看代码),那么,为什么不能,也不需要 \(s2\)\(s3\) 比较?

答案是这样的:如果 \(s2\)\(s3\) 比较了,就会使得一部分你要用到的 \(dp[i-1][...][...][...]\) 被干掉,即是会把原本合法的状态判非法。

我就是这个思考了半个小时

在代码的最后,统计第 \(m\) 行的所有方案之和即可。

以及,别别别忘了取模!

完整代码:

#include<bits/stdc++.h>
using namespace std;
int n,m,k;
const int mod=1e9+7;
int cnt[(1<<6)+1];
int dp[101][(1<<6)+1][(1<<6)+1][21];
inline int lowbit(int x)
{
	return x&-x;
}
int main()
{
	cin>>n>>m>>k;
	for(int s=0;s<(1<<n);s++)
	{
		for(int tmp=s;tmp;tmp-=lowbit(tmp))//计算 cnt 数组,lowbit 每次会找到这个数最低一个 1。
		++cnt[s];
	}
	for(int i=0;i<(1<<n);i++) dp[1][i][0][cnt[i]]=1;//第一行处理
	if(m>1){//防止被卡
	for(int s1=0;s1<(1<<n);s1++)
	{
		for(int s2=0;s2<(1<<n);s2++)
		{
			if((s1&(s2<<2))|(s1&(s2>>2))) continue;
			for(int j=0;j<=k;j++)
			{
				if(j-cnt[s1]>=0)
				dp[2][s1][s2][j]+=(dp[1][s2][0][j-cnt[s1]]);//第二行处理
			}
		}
	}
	if(m>2){//防止被卡
	for(int i=3;i<=m;i++)
	{
		for(int s1=0;s1<(1<<n);s1++)
		{
			for(int s2=0;s2<(1<<n);s2++)
			{
				if((s1&(s2<<2))|(s1&(s2>>2))) continue;//判断上一行是否合法。
				for(int s3=0;s3<(1<<n);s3++)
				{
				//	if((s2&(s3<<1))|(s2&(s3>>1)))continue; 这里就是上面说的那个思考题,删掉注释只有 5 分。
					if((s1&(s3<<1))|(s1&(s3>>1))) continue;//判断上上一行是否合法。
					for(int j=0;j<=k;j++)
					{
						if(j-cnt[s1]>=0)//防止 RE。
						dp[i][s1][s2][j]+=(dp[i-1][s2][s3][j-cnt[s1]]);
						dp[i][s1][s2][j]%=mod;
					}
				}
			}
		}
	}
	}}
	long long ans=0;
	for(int s1=0;s1<(1<<n);s1++)
	{
		for(int s2=0;s2<(1<<n);s2++)
		{
			if((s1&(s2<<2))|(s1&(s2>>2))) continue;
			ans+=dp[m][s1][s2][k];
			ans%=mod;
		}
	}
	cout<<ans;
}

也可以加一个滚动数组优化时空,能减少 30MB 空间和 200ms 时间。

P5005 中国象棋 - 摆上马

2024-04-05 11:00:58 星期五

此题和上一道题几乎一样,去掉第四维、修改判断非法的语句即可。

但是难点在判断非法上。

这个题会有蹩马腿的事情,这就会导致一个问题:

  • 如果一匹马的左边有马,那么它攻击不到上一行左边的马;
  • 如果一匹马的右边有马,那么它攻击不到上一行右边的马;
  • 如果一匹马的上面有马,那么它攻击不到上上一行的所有马。

因此,答案会比不考虑蹩马腿的答案要多。

加上判断就行。

for(int i=3;i<=m;i++)
{
	r^=1;
	memset(dp[r],0,sizeof(dp[r]));
	for(int s1=0;s1<(1<<n);s1++)
	{
		for(int s2=0;s2<(1<<n);s2++)
		{
			if((((s1&(~(s1>>1)))&(s2>>2))||((s1&(~(s1<<1)))&(s2<<2))||(((s2&(~(s2>>1)))&(s1>>2)))||((s2&(~(s2<<1)))&(s1<<2)))) continue;
			//奇妙的判断
			for(int s3=0;s3<(1<<n);s3++)
			{
				if((((s1&(~s2))&(s3>>1))||((s1&(~s2))&(s3<<1))||((s3&(~s2))&(s1>>1))||((s3&(~s2))&(s1<<1)))) continue;
				dp[r][s1][s2]+=(dp[r^1][s2][s3]);
				dp[r][s1][s2]%=mod;
			}
		}
	}
}

P8733 [蓝桥杯 2020 国 C] 补给

2024-03-31 16:01:36 星期日

这是状压的非传统棋盘的表现形式。

注意到末态必须是经过所有的村庄,并且最大的 \(N\) 只有 \(20\),那我们可以直接状压。

\(dp[s][i]\) 表示现在的状态是 \(s\),并且现在在第 \(i\) 个村庄,要到第 \(j\) 个村庄,我们需要满足的条件是:

  • \(s\) 中不包含 \(j\)
  • \(i\)\(j\) 可以走。

第一个很好判断,第二个的话可以先预处理出 \(dis[i][j]\) 表示从 \(i\)\(j\) 的直线距离,再跑个 \(Floyd\) 全源最短路找到能走的最短路径,然后就能直接跑状压 \(dp\) 了。

for(int k=1;k<=n;k++)//Floyd 全源最短路
	for(int i=1;i<=n;i++)
		for(int j=1;j<=n;j++)
			dis[i][j]=min(dis[i][j],dis[i][k]+dis[k][j]);

memset(dp,0x7f,sizeof(dp));
dp[1][1]=0;
for(int s=0;s<(1<<n);s++)
{
	for(int i=1;i<=n;i++)
	{
		if(s&(1<<(i-1))==0) continue;//如果 i 不在在 s 里面就跳过
		for(int j=1;j<=n;j++)
		{
			if((s^(1<<(i-1))>>(j-1))&1)//
				dp[s][i]=min(dp[s][i],dp[s^(1<<(i-1))][j]+dis[j][i]);
		}
	}
}

考虑走完全程之后仍需要回到出发点,再最后计算答案时还要加上 \(dis[i][1]\)

P1433 吃奶酪

和上面一个题一模一样,把起点 \((0,0)\) 当成虚拟原点就好。

P1171 售货员的难题

和上面两道题一样,不需要跑 Floyd。

P2831 [NOIP2016 提高组] 愤怒的小鸟

这个就和上面的所有题很不一样了。

观察题目:我们先要找到所有能攻击的抛物线,并且最大化抛物线打到的猪的个数。

所以,需要设一个 init 函数预处理,遍历所有的猪的坐标对。

为什么是两个?我们观察题目给出的抛物线模板 \(y=ax^2+bx\),因为其必过原点,所以只需要两点即可确定。

设找到的两点分别是 \((x_1,y_1)\)\((x_2,y_2)\),可以列出:

\[\begin{cases} y_1=ax_1^2+bx_1 \\ y_2=ax_2^2+bx_2 \end{cases} \]

两个未知数两个方程,可解,最终得出来的式子是这样的:

\[\Large{ \begin{cases} a=\frac{y_2x_1-y_1x_2}{x_2^2x1-x_1^2x_2} \\ b=\frac{y_1}{x_1}-\frac{y_2x_1-y_1x_2}{x_2x_2-x_1x_2} \end{cases}} \]

处理每条抛物线时,需要判断分母是否为零,要不然不能除。并且要判断 \(a\) 的正负。

然后对于每个抛物线,遍历全部的猪,看有没有在这条线上,然后定义一个状态 \(s\),如果一个点在抛物线上,就把 \(s\) 中这个位置标位 \(1\),以此初始化 \(dp\) 数组。

因为或运算的性质,不需要考虑判重的情况(无法用言语表述了……)。

然后定义 \(dp[s]\) 表示状态状态 \(s\) 下最小需要的鸟的个,对于每个状态,循环一每组猪对转移即可。

\(dp[s|b[i][j]]=\min(dp[s|b[i][j]],dp[s]+1)\)

#include<bits/stdc++.h>
using namespace std;
int t;
int n,m;
const int N=19;
struct node{
	double x,y;
}a[N];
int dp[(1<<N)];
const double minn=1e-8;
int b[N][N];
void calc(int i,int j,double &x,double &y)
{
	double x1=a[j].x*a[j].x*a[i].x-a[i].x*a[i].x*a[j].x;
	if(fabs(x1)<minn)return ;
	x=(a[j].y*a[i].x-a[i].y*a[j].x)/(x1);
	x1=a[j].x*a[j].x-a[i].x*a[j].x;
	if(fabs(x1)<minn)return ;
	y=(a[i].y/a[i].x)-(a[j].y*a[i].x-a[i].y*a[j].x)/(x1);
}
int cnt;
void init()
{
	for(int i=1;i<=n;i++)
	{
		for(int j=1;j<=n;j++)
		{
		//	cout<<i<<" "<<j<<endl;
			if(fabs(a[i].x-a[j].x)<minn) continue;
			double x=0,y=0;
			calc(i,j,x,y);
			bool flag=0;
			if(x>0) continue;
		//	cout<<x<<" "<<y<<endl;
			int s=0;
			for(int k=1;k<=n;k++)
			{
				double x1=a[k].x,y2=a[k].y;
				if(fabs(x1*x1*x+x1*y-y2)<minn)
				{
					s|=(1<<(k-1));
					dp[s]=dp[1<<(k-1)]=1;
				}
			}
			b[i][j]=b[j][i]=s;
		}
	}
}
int main()
{
	cin>>t;
	while(t--)
	{
		memset(b,0,sizeof(b));
		cnt=0;
		cin>>n>>m;
		memset(dp,0x7f,sizeof(dp));
		dp[0]=0;
		for(int i=1;i<=n;i++)
		{
			cin>>a[i].x>>a[i].y;
		}
		init();
		for(int s=0;s<(1<<n);s++)
		{
			for(int i=1;i<=n;i++)
			{
				if((s&(1<<(i-1)))!=0) continue;\\如果这只猪在此状态里似了,直接跳过,避免不必要的循环
				for(int j=1;j<=i;j++)
				{
					if((s&(1<<(j-1)))!=0) continue;
					dp[s|b[i][j]]=min(dp[s|b[i][j]],dp[s]+1);
				}
				dp[s|(1<<(i-1))]=min(dp[s|(1<<(i-1))],dp[s]+1);
			}
		}
		cout<<dp[(1<<n)-1]<<endl;
	}
}

P4163 [SCOI2007] 排列

2024-04-03 17:19:25 星期三

这个题就很玄学了。

\(n\) 个数组成排列,问能被 \(d\) 整除的数的个数。

当选数的状态是 \(s\),组成的数字是 \(a\) 时,考虑其被 \(d\) 整除的余数是 \(k\),如果再加一个数 \(num[i]\)\(a\) 会变成 \(a\times 10+num[i]\),这个数模 \(d\) 就是 \(k\times 10+num[i]\)\(d\) 的结果,写成式子就是:

\[{a\times 10+num[i]}\equiv{k\times 10+num[i]}\pmod{d} \]

应该是这么写的吧,同余方程已经忘了

定义 \(dp[s][k]\) 表示状态为 \(s\),当前数模 \(d=k\) 的个数。

因此我们可以得出转移方程:

对于状态 \(s\),一个数 \(num[i]\),并且 \(s\text{ and }(1<<(i-1))=0\),有:

\[dp[s|(1<<(i-1))][(k\times 10+num[i])\% d]+=dp[s][k] \]

还有细节!

比如:\(001122\) 这个组合,显然在循环的时候会把 \(0,1,2\) 分别多计算 \(2\) 次,因此我们在答案最后要除以每个数字出现个数的阶乘。

for(int i=0;i<=12;i++)
{
	if(f[cnt[i]])
	dp[(1<<len)-1][0]/=f[cnt[i]];
}

#P333. [Usaco2005 Open]Disease Manangement 疾病管理

2024-04-06 22:06:03 星期六

好像不用 \(dp\) 就可以。

我们先处理出每只牛对于的病的状态 \(a[i]\),然后预处理每个状态的 \(1\) 的个数,都是老套路。

然后对于一个状态 \(s\),如果一头牛的病是 \(s\) 的子集,那么这个状态对应的牛的个数就要加一,统计即可。

注意状态是以 \(d\) 为准,而不是 \(n\)

#P335. [Sdoi2009]Bill的挑战

神奇的小题。

玄学。。

#include<bits/stdc++.h>
using namespace std;
int t,n,d;
int a[51][51];
int dp[51][(1<<15)+1];
const int mod=1000003;
char s[16][51];
int main()
{
	cin>>t;
	while(t--)
	{
		cin>>n>>d;
		int len;
		memset(dp,0,sizeof(dp));
		memset(a,0,sizeof(a));
		for(int i=0;i<n;i++)scanf("%s",s[i]);
		len=strlen(s[0]);
		for(int i=0;i<len;i++)
		{
			for(int j=0;j<26;j++)
			{
				for(int k=0;k<n;k++)
				{
					if(s[k][i]==j+'a'||s[k][i]=='?')
					a[i][j]|=(1<<k);
				}
			}
		}
		dp[0][(1<<n)-1]=1;
		for(int i=0;i<len;i++)
		{
			for(int j=0;j<(1<<n);j++)
			{
				if(dp[i][j])
				{
					for(char k='a';k<='z';k++)
					{	
						dp[i+1][j&a[i][k-'a']]=(dp[i][j]+dp[i+1][j&a[i][k-'a']])%mod;
					}
				}
			}
		}
		long long ans=0;
		for(int i=0;i<(1<<n);i++)
		{
			int tot=0;
			for(int j=0;j<n;j++)
			{
				if(i&(1<<j)) ++tot;
			}
			if(tot==d) ans=(ans+dp[len][i])%mod;
		}
		cout<<ans<<endl;
	}
}

P3226 [HNOI2012] 集合选数

最离谱的状压了(神秘构造)。

在看到怎么状压之前甚至不知道怎么状压。。

观察到:要求每个集合如果存在 \(i\) 则不存在 \(2i\)\(3i\),怎么搞呢?

我们把它想象成这样:

\[\begin{bmatrix} &i&2i&4i&8i \\ &3i&6i&12i&24i \\ &9i&18i&36i&72i \end{bmatrix} \]

或者把它写成数字:

\[\begin{bmatrix} &1&2&4&8 \\ &3&6&12&24 \\ &9&18&36&72 \end{bmatrix} \]

观察到如果矩阵中有一个数字,那么一定没有它下面和右面的数字。

这是什么??!

芝士 棋盘

但是不可能一个棋盘就包含所有 \(\in[1,n]\) 的数字,我们可以开多个这样的棋盘和一个 \(vis\) 数组实现全覆盖。

最终答案是每个棋盘格的答案乘积(别忘了取模)。

核心代码:

inline void init(int x)
{
	for(int i=1;i<=11;i++)
	{
		if(i==1) a[i][1]=x;
		else a[i][1]=a[i-1][1]*3;
		if(a[i][1]>n) break;
		vis[a[i][1]]=1;lin[i]=1,ed=i;
		for(int j=2;j<=18;j++)
		{
			a[i][j]=a[i][j-1]*2;
			if(a[i][j]>n) break;
			lin[i]=j;
			vis[a[i][j]]=1;
		}
		lim[i]=(1<<lin[i])-1;
	}
}
int dp[51][(1<<18)];
long long res;
inline void solve(int x)
{
	res=0;
	for(int i=0;i<=lim[1];i++) dp[1][i]=g[i];
	for(int i=2;i<=ed;i++)
	{
		for(int j=0;j<=lim[i];j++)
		{
			if(!g[j])continue;
			dp[i][j]=0;
			for(int k=0;k<=lim[i-1];k++)
			{
				if(k&j||!g[k])continue;
				dp[i][j]+=dp[i-1][k],dp[i][j]%=mod;
			}
		}
	}
	for(int i=0;i<=lim[ed];i++)
	{
		res+=dp[ed][i];res%=mod;
	}
}



事实上,状压到这里要告一段落了。还有 \(3\) 道题单里的题没写,先溜了 ~~

2024.04.10

刚好是表哥生日

END


posted @ 2024-03-24 16:19  ccjjxx  阅读(26)  评论(0编辑  收藏  举报