ICode9

精准搜索请尝试: 精确搜索
首页 > 其他分享> 文章详细

状态压缩DP-经典&变形

2022-01-26 14:34:46  阅读:217  来源: 互联网

标签:return 变形 cdot 压缩 状压 j1 int DP dp


前言

状压 DP 就是一类使用根据几进制来进行状态转移方程。

一般来说,数据范围 2 n 2^n 2n 都在情理之中,可是 n ! n! n! 就直接爆炸。

状压一般来说都可以用搜索,并且搜索都可以拿分,只是拿不全。

所以说,一般看见有一维是 11 11 11~ 19 19 19 的话,那可以考虑状压。

题目选讲

Solution

经典的状压 DP,算是入门题目吧。

我们发现 n n n 和 m m m 都特别小,但是搜索肯定过不了,那么我们思考一下状压。

我们发现:每一行的状态可以用一个二进制来表示,每一块草地种或者不种。

而且当前这块草地仅仅受到前一行草地的影响,也就是无后效性,肯定就是打 DP。

我们设 d p i , j dp_{i,j} dpi,j​ 表示第 i i i 行状态为 j j j 的情况下,有多少种方案数。

这里, j j j 表示出来是十进制数,实际上它表示了一种二进制下的一种状态。

首先排除这一行本身不合法的情况,即存在相邻或者不能种的草地上种了草。

对于这种判断,像我这种蒟蒻只会先用个数组存下来,然后在暴力判断,带一点常数。

大佬们可以尝试用位运算试试,只是很容易出锅。

接着我们枚举上一行,然后再看两行是否有相邻,如果合法了,那么累加就行了。

Code

#include<bits/stdc++.h>
using namespace std;
const int N=13;
const int mod=1e8;
int n,m,ans;
int a[N][N],Log[N],Tag;
int dp[N][1<<N];
bool check(int i,int j){
	if(j&(j<<1)) return 1;
	int w=m;
	while(j){
		if(j&1 && !a[i][w]) return 1;
		j>>=1;
		w--;
	}
	return 0;
}
int main(){
	scanf("%d %d",&n,&m);
	Tag=1<<m;
	for(int i=1;i<=n;i++)
		for(int j=1;j<=m;j++) scanf("%d",&a[i][j]);
	for(int i=0;i<=Tag;i++) !check(0,i)?dp[0][i]=1:1;
	for(int i=1;i<=n;i++)
		for(int j=0;j<=Tag;j++){
			if(check(i,j)) continue;
			for(int k=0;k<=Tag;k++){
				if(j&k || check(i-1,k)) continue;
				dp[i][j]+=dp[i-1][k];
				dp[i][j]%=mod;
			}
		}
	for(int i=0;i<=Tag;i++) ans+=dp[n][i],ans%=mod;
	printf("%d",ans);
	return 0;
}

Solution

经典的棋盘状压,挺好玩。

观察数据范围,发现 m m m 很小,考虑状压。

可以看到一个炮兵只被上一排和上上一排影响。

那么我们我们的状态定义就很显然:

设 d p i , j , k dp_{i,j,k} dpi,j,k​ 表示第 i i i 行状态为 j j j,第 i − 1 i-1 i−1 行状态为 k k k 的情况。

然后直接枚举三行的情况,判断是否相邻。

看到这里相信大家都有了一点疑惑: 2 10 × 2 10 × 2 10 × 100 = 107374182400 2^{10}\times 2^{10}\times 2^{10}\times 100=107374182400 210×210×210×100=107374182400

岂不爆炸,实际上,这是一个状压很精髓的地方。

对于一行来讲,本事就有许多状态不合法,就如本身就相邻。

我算了一下,只有: 144 144 144 种情况

那么带进去就有: 298598400 298598400 298598400 的复杂度。

再加上枚举两个删掉一些,和图中本来有一些不能放,就可以排除大部分。

反正一句话:跑不满。

Code

#include<bits/stdc++.h>
using namespace std;
const int N=1e2+5;
const int M=1e1+1;
int n,m,Fi,ans;
int dp[N][1<<10][1<<10],Bit[1<<10];
bool Check[N][1<<M],f[11],k[N][M];
bool check(int i,int j){
	memset(f,0,sizeof(f));
	int x=1;
	while(j){
		f[x]=(j&1);
		j>>=1;
		x++;
	}
	for(int l=1;l<=m;l++) if(f[l] && !k[i][l]) return 0;
	return 1; 
}
int Count(int x){
	int sum=0;
	while(x) sum+=x&1,x>>=1;
	return sum;
}
int main(){
	scanf("%d %d",&n,&m);
	Fi=(1<<m)-1;
	for(register int i=1;i<=m;i++) k[0][i]=1;
	for(register int i=1;i<=n;i++)
		for(register int j=1;j<=m;j++){
			char opt;
			cin>>opt;
			if(opt=='P') k[i][j]=1;
		}
	for(register int i=0;i<=Fi;i++)
		for(register int j=0;j<=n;j++) (!check(j,i) || i&(i<<1) || i&(i<<2))?1:Check[j][i]=1;
	for(register int j1=0;j1<=Fi;j1++){
		Bit[j1]=Count(j1);
		if(Check[1][j1]) dp[1][j1][0]=Bit[j1]; 
	}	
	for(register int i=2;i<=n;i++)
		for(register int j1=0;j1<=Fi;j1++)
			if(Check[i][j1])
				for(register int j2=0;j2<=Fi;j2++)
					if(Check[i-1][j2] && !(j1&j2))
						for(register int j3=0;j3<=Fi;j3++)
							if(Check[i-2][j3])
								if(!(j1&j2) && !(j2&j3) && !(j1&j3)) dp[i][j1][j2]=max(dp[i-1][j2][j3]+Bit[j1],dp[i][j1][j2]);
	for(register int i=0;i<=Fi;i++)
		for(register int j=0;j<=Fi;j++) ans=max(dp[n][i][j],ans);
	printf("%d",ans);
	return 0;
}

Solution

感觉也很好玩。

我们知道,三点确定一个抛物线,这里已经给出了原点,即两点确定一条抛物线。

我们先把所有的抛物线整出来。

先推一下式子。

{ a ⋅ A x 2 + b ⋅ A x = A y a ⋅ B x 2 + b ⋅ B x = B y \begin{cases}a\cdot A_x^2+b\cdot A_x=A_y\\a\cdot B_x^2+b\cdot B_x=B_y\end{cases} {a⋅Ax2​+b⋅Ax​=Ay​a⋅Bx2​+b⋅Bx​=By​​

设参数 k = A x 2 B x 2 k=\frac{A_x^2}{B_x^2} k=Bx2​Ax2​​

第二个式子乘上参数:

a ⋅ A x 2 + k ⋅ b ⋅ B x = k ⋅ B y \begin{aligned} a\cdot A_x^2+k\cdot b\cdot B_x&=k\cdot B_y\end{aligned} a⋅Ax2​+k⋅b⋅Bx​​=k⋅By​​

让一式减去二式:

b ⋅ A x − k ⋅ b ⋅ B x = A y − k ⋅ B y b ⋅ ( A x − k ⋅ B x ) = A y − k ⋅ B y b = A y − k ⋅ B y A x − k ⋅ B x \begin{aligned}b\cdot A_x-k\cdot b\cdot B_x&=A_y-k\cdot B_y \\ b\cdot(A_x-k\cdot B_x) &=A_y-k\cdot B_y \\ b &=\frac{A_y-k\cdot B_y}{A_x-k\cdot B_x} \end{aligned} b⋅Ax​−k⋅b⋅Bx​b⋅(Ax​−k⋅Bx​)b​=Ay​−k⋅By​=Ay​−k⋅By​=Ax​−k⋅Bx​Ay​−k⋅By​​​

通过这样我们就把 b b b 给解出来了。

随便带进去,带到一式吧。

a ⋅ A x 2 = A y − b ⋅ A x a = A y − b ⋅ A x A x 2 \begin{aligned}a\cdot A_x^2 &= A_y-b\cdot A_x \\ a &= \frac{A_y-b\cdot A_x}{A_x^2} \end{aligned} a⋅Ax2​a​=Ay​−b⋅Ax​=Ax2​Ay​−b⋅Ax​​​

就这么解完了。

由于一条抛物线可能经过多个点,所以我们把每一条抛物线跑一遍,用二进制表示出其能经过哪些点。

然后问题就转化成了一个背包问题。

在这里,就把每个抛物线的价值进行了状压。

Code

#include<bits/stdc++.h>
#define mem(a,x) memset(a,x,sizeof(a))
using namespace std;
int T,n,m,cnt;
int Fc[400],dp[1<<19];
struct node{
	double x,y;
}a[19];
struct Par{
	double a,b;
}f[400];
bool Check(int i,int j){
	return fabs(a[j].y-(f[i].a*a[j].x*a[j].x+f[i].b*a[j].x))<0.000001;
}
int main(){
	scanf("%d",&T);
	while(T--){
		cnt=0;
		scanf("%d %d",&n,&m);
		for(int i=1;i<=n;i++) scanf("%lf %lf",&a[i].x,&a[i].y);
		for(int i=1;i<=n;i++)
			for(int j=i+1;j<=n;j++){
				double k=(a[i].x*a[i].x)/(a[j].x*a[j].x),A,B;
				B=(a[i].y-k*a[j].y)/(a[i].x-k*a[j].x);
				A=(a[i].y-a[i].x*B)/(a[i].x*a[i].x);
				if(A<0) f[++cnt]=((Par){A,B});
			}
		mem(Fc,0);
		mem(dp,0x3f);
		dp[0]=0;
		int ans=0;
		for(int i=1;i<=cnt;i++){
			for(int j=1;j<=n;j++) Fc[i]=Fc[i]*2+Check(i,j);
		}
		for(int i=1;i<=n;i++) Fc[++cnt]=1<<i-1;
		for(int i=0;i<=(1<<n)-1;i++)
			for(int j=1;j<=cnt;j++) dp[i|Fc[j]]=min(dp[i|Fc[j]],dp[i]+1);
		printf("%d\n",dp[(1<<n)-1]);
	}
	return 0;
} 

Solution

感觉全部题最难的一道,最好玩的。

{ a 3 ⋅ a 9 ⋅ a . . . 2 ⋅ a 6 ⋅   a 18 ⋅ a . . . 4 ⋅ a 12 ⋅ a 36 ⋅ a . . . . . . . . . . . . . . . } \begin{Bmatrix}a & 3\cdot a & 9\cdot a & ...\\ 2\cdot a & 6\cdot\ a & 18\cdot a & ... \\ 4\cdot a & 12\cdot a & 36\cdot a & ...\\... &... &...&... \end{Bmatrix} ⎩⎪⎪⎨⎪⎪⎧​a2⋅a4⋅a...​3⋅a6⋅ a12⋅a...​9⋅a18⋅a36⋅a...​............​⎭⎪⎪⎬⎪⎪⎫​

通过观察这个矩阵我们发现,不能取这个矩阵相邻的元素作为集合。

那么这不禁让我们想起了第一题,而且对于矩阵的大小,是 log ⁡ \log log 级别的。

那么我们的方法就显而易见了。

对于这个矩阵,我们把所有没有出现过的元素标记上,这样就避免了重复的计算。

当我们算完了一个矩阵,需要进行相乘,因为肯定任意两个矩阵互相还是可以取,就是乘法原理。

万一矩阵的数量太多,跑飞了怎么办?

别慌,我们来想一想。

我跑了一下 1 0 5 10^5 105,跑了 33333 33333 33333 个矩阵。

那接下来我们算一下一个矩阵的复杂度。

约是 10000 10000 10000 级别的样子,但是跑不满,因此还是可以过的,况且随着矩阵越来越多,状态就越来越少,因为越来越多的数都会被标记。

Code

#include<bits/stdc++.h>
#define mem(a,x) memset(a,x,sizeof(a))
using namespace std;
const int N=5e6+5;
const int mod=1e9+1;
int Fi,a[19][12],dp[18][5096];
int n,ans=1,Len[18],dq[18];
bool f[N],flag[18][12];
void Work(int x){
	mem(a,0);
	a[1][1]=x;
	int m=1,sum=0,len=0;
	for(int i=2;a[1][i-1]*3<=n;i++){
		a[1][i]=a[1][i-1]*3;
		m++;
	}
	for(int j=1;j<=m;j++)
		for(int i=2;a[i-1][j]*2<=n;i++) a[i][j]=a[i-1][j]*2;
	mem(Len,0);
	for(int i=1;a[i][1];i++){
		for(int j=1;j<=m;j++){
			Len[i]=Len[i]*2+(!a[i][j]);
			if(!a[i][j]) flag[i][j]=1;
			f[a[i][j]]=1;
		}
		len++;
	}
	mem(dp,0);
	dp[0][0]=1;
	for(int i=1;i<=len;i++){
		for(int j1=0;j1<=(1<<m)-1;j1++){
			if(Len[i]&j1 || j1&(j1<<1)) continue;
			for(int j2=0;j2<=(1<<m)-1;j2++){
				if(Len[i-1]&j2 || j2&(j2<<1) || j1&j2) continue;
				dp[i][j1]+=dp[i-1][j2];
				dp[i][j1]%=mod;
			}
		}
	}
	for(int i=0;i<=(1<<m)-1;i++) sum+=dp[len][i],sum%=mod;
	ans=(1ll*ans*sum)%mod;
}
int main(){
	scanf("%d",&n);
	for(int i=1;i<=n;i++){
		if(!f[i]){
			f[i]=1;
			Work(i);
		}
	}
	printf("%d",ans);
	return 0;
}

Solution

这题在洛谷上使用是标算二十倍的算法卡得过去。

Link

做得最爽的一道题。

首先还是思考如何将这个问题转化为状压。

其实这个题的连线和状压的另一种版题有点像,就是说传球,但是中间的直线需要另行判断。

什么直线呢?

就是说因为两点的直线必须是中间经过的点是使用过的。

可以很自然的想到直接枚举一层 n n n,然后暴力判断斜率。

O ( 2 n ⋅ n 3 ) O(2^n\cdot n^3) O(2n⋅n3)

8388608000 8388608000 8388608000,这么大,但是考虑到状压本身跑不满,因此复杂度虽然比这个小,但还是跑不过。

考虑位运算优化,我们先把任意两点间连线有的点给预处理下来。

然后进行状压取反后再和实际状态做与运算。

Code

#include<bits/stdc++.h>
#define mem(a,x) memset(a,x,sizeof(a))
using namespace std;
const int mod=1e8+7;
int n,Fi;
int dp[20][1<<19];
int sum[1<<19],check[20][20];
bool f[20];
struct node{
	int x,y;
}a[20];
inline int qr(){
	int s=0,f=1;
	char ch;
	ch=getchar();
	while(!isdigit(ch)){if(ch=='-') f=-1;ch=getchar();}
	while(isdigit(ch)){s=s*10+ch-'0';ch=getchar();}
	return s*f;
}
inline void qw(int x){
	if(x<0) putchar('-'),x=-x;
	if(x>9) qw(x/10);
	putchar(x%10+'0');
}
inline bool cmp(node x,node y){
	if(x.x==y.x) return x.y<y.y;
	return x.x<y.x;
}
inline void opt(int x){
	mem(f,0);
	int w=1;
	while(x) f[w]=(x&1),x>>=1,w++;
}
inline int Count(int x){
	int sum=0;
	while(x) sum+=x&1,x>>=1;
	return sum;
}
inline int Checker(int x,int y){
	mem(f,0);
	if(x>y) swap(x,y);
	int FF=0;
	if(x+1==y || x==y) return 0;
	for(int i=x+1;i<y;i++) if((a[x].x-a[i].x)*(a[i].y-a[y].y)==(a[i].x-a[y].x)*(a[x].y-a[i].y)) f[i]=1;
	for(int i=1;i<=n;i++) FF+=f[i]?1<<i-1:0;
	return FF;
}
int main(){
	n=qr();
	Fi=(1<<n)-1; 
	for(register int i=1;i<=n;i++) a[i]=((node){qr(),qr()});
	sort(a+1,a+1+n,cmp);
	for(register int i=1;i<=n;i++)
		for(register int j=1;j<=n;j++) check[i][j]=Checker(i,j);
	for(register int i=1;i<=n;i++) dp[i][1<<i-1]=1;
	for(register int j=0;j<=Fi;j++){
		opt(j);
		for(register int i=1;i<=n;i++){
			if(!f[i]) continue;
			for(register int k=1;k<=n;k++)
				if(f[k] && !(check[i][k]&(~j)) && k!=i) dp[i][j]+=dp[k][j-(1<<i-1)],dp[i][j]%=mod;
			sum[j]+=dp[i][j];
			sum[j]%=mod;
		}
	}
	int ans=0;
	for(register int j=0;j<=Fi;j++){
		if(Count(j)>=4) ans+=sum[j],ans%=mod;
	}
	printf("%d",ans);
	return 0;
} 
```# 前言

状压 DP 就是一类使用根据几进制来进行状态转移方程。

一般来说,数据范围 $2^n$ 都在情理之中,可是 $n!$ 就直接爆炸。

状压一般来说都可以用搜索,并且搜索都可以拿分,只是拿不全。

所以说,一般看见有一维是 $11$~$19$ 的话,那可以考虑状压。

# 题目选讲

* [P1879 ](https://www.luogu.com.cn/problem/P1879)

**Solution**

经典的状压 DP,算是入门题目吧。

我们发现 $n$ 和 $m$ 都特别小,但是搜索肯定过不了,那么我们思考一下状压。

我们发现:每一行的状态可以用一个二进制来表示,每一块草地种或者不种。

而且当前这块草地仅仅受到前一行草地的影响,也就是无后效性,肯定就是打 DP。

我们设 $dp_{i,j}$ 表示第 $i$ 行状态为 $j$ 的情况下,有多少种方案数。

这里,$j$ 表示出来是十进制数,实际上它表示了一种二进制下的一种状态。

首先排除这一行本身不合法的情况,即存在相邻或者不能种的草地上种了草。

对于这种判断,像我这种蒟蒻只会先用个数组存下来,然后在暴力判断,带一点常数。

大佬们可以尝试用位运算试试,~~只是很容易出锅。~~

接着我们枚举上一行,然后再看两行是否有相邻,如果合法了,那么累加就行了。

**Code**

```cpp
#include<bits/stdc++.h>
using namespace std;
const int N=13;
const int mod=1e8;
int n,m,ans;
int a[N][N],Log[N],Tag;
int dp[N][1<<N];
bool check(int i,int j){
	if(j&(j<<1)) return 1;
	int w=m;
	while(j){
		if(j&1 && !a[i][w]) return 1;
		j>>=1;
		w--;
	}
	return 0;
}
int main(){
	scanf("%d %d",&n,&m);
	Tag=1<<m;
	for(int i=1;i<=n;i++)
		for(int j=1;j<=m;j++) scanf("%d",&a[i][j]);
	for(int i=0;i<=Tag;i++) !check(0,i)?dp[0][i]=1:1;
	for(int i=1;i<=n;i++)
		for(int j=0;j<=Tag;j++){
			if(check(i,j)) continue;
			for(int k=0;k<=Tag;k++){
				if(j&k || check(i-1,k)) continue;
				dp[i][j]+=dp[i-1][k];
				dp[i][j]%=mod;
			}
		}
	for(int i=0;i<=Tag;i++) ans+=dp[n][i],ans%=mod;
	printf("%d",ans);
	return 0;
}

Solution

经典的棋盘状压,挺好玩。

观察数据范围,发现 m m m 很小,考虑状压。

可以看到一个炮兵只被上一排和上上一排影响。

那么我们我们的状态定义就很显然:

设 d p i , j , k dp_{i,j,k} dpi,j,k​ 表示第 i i i 行状态为 j j j,第 i − 1 i-1 i−1 行状态为 k k k 的情况。

然后直接枚举三行的情况,判断是否相邻。

看到这里相信大家都有了一点疑惑: 2 10 × 2 10 × 2 10 × 100 = 107374182400 2^{10}\times 2^{10}\times 2^{10}\times 100=107374182400 210×210×210×100=107374182400

岂不爆炸,实际上,这是一个状压很精髓的地方。

对于一行来讲,本事就有许多状态不合法,就如本身就相邻。

我算了一下,只有: 144 144 144 种情况

那么带进去就有: 298598400 298598400 298598400 的复杂度。

再加上枚举两个删掉一些,和图中本来有一些不能放,就可以排除大部分。

反正一句话:跑不满。

Code

#include<bits/stdc++.h>
using namespace std;
const int N=1e2+5;
const int M=1e1+1;
int n,m,Fi,ans;
int dp[N][1<<10][1<<10],Bit[1<<10];
bool Check[N][1<<M],f[11],k[N][M];
bool check(int i,int j){
	memset(f,0,sizeof(f));
	int x=1;
	while(j){
		f[x]=(j&1);
		j>>=1;
		x++;
	}
	for(int l=1;l<=m;l++) if(f[l] && !k[i][l]) return 0;
	return 1; 
}
int Count(int x){
	int sum=0;
	while(x) sum+=x&1,x>>=1;
	return sum;
}
int main(){
	scanf("%d %d",&n,&m);
	Fi=(1<<m)-1;
	for(register int i=1;i<=m;i++) k[0][i]=1;
	for(register int i=1;i<=n;i++)
		for(register int j=1;j<=m;j++){
			char opt;
			cin>>opt;
			if(opt=='P') k[i][j]=1;
		}
	for(register int i=0;i<=Fi;i++)
		for(register int j=0;j<=n;j++) (!check(j,i) || i&(i<<1) || i&(i<<2))?1:Check[j][i]=1;
	for(register int j1=0;j1<=Fi;j1++){
		Bit[j1]=Count(j1);
		if(Check[1][j1]) dp[1][j1][0]=Bit[j1]; 
	}	
	for(register int i=2;i<=n;i++)
		for(register int j1=0;j1<=Fi;j1++)
			if(Check[i][j1])
				for(register int j2=0;j2<=Fi;j2++)
					if(Check[i-1][j2] && !(j1&j2))
						for(register int j3=0;j3<=Fi;j3++)
							if(Check[i-2][j3])
								if(!(j1&j2) && !(j2&j3) && !(j1&j3)) dp[i][j1][j2]=max(dp[i-1][j2][j3]+Bit[j1],dp[i][j1][j2]);
	for(register int i=0;i<=Fi;i++)
		for(register int j=0;j<=Fi;j++) ans=max(dp[n][i][j],ans);
	printf("%d",ans);
	return 0;
}

Solution

感觉也很好玩。

我们知道,三点确定一个抛物线,这里已经给出了原点,即两点确定一条抛物线。

我们先把所有的抛物线整出来。

先推一下式子。

{ a ⋅ A x 2 + b ⋅ A x = A y a ⋅ B x 2 + b ⋅ B x = B y \begin{cases}a\cdot A_x^2+b\cdot A_x=A_y\\a\cdot B_x^2+b\cdot B_x=B_y\end{cases} {a⋅Ax2​+b⋅Ax​=Ay​a⋅Bx2​+b⋅Bx​=By​​

设参数 k = A x 2 B x 2 k=\frac{A_x^2}{B_x^2} k=Bx2​Ax2​​

第二个式子乘上参数:

a ⋅ A x 2 + k ⋅ b ⋅ B x = k ⋅ B y \begin{aligned} a\cdot A_x^2+k\cdot b\cdot B_x&=k\cdot B_y\end{aligned} a⋅Ax2​+k⋅b⋅Bx​​=k⋅By​​

让一式减去二式:

b ⋅ A x − k ⋅ b ⋅ B x = A y − k ⋅ B y b ⋅ ( A x − k ⋅ B x ) = A y − k ⋅ B y b = A y − k ⋅ B y A x − k ⋅ B x \begin{aligned}b\cdot A_x-k\cdot b\cdot B_x&=A_y-k\cdot B_y \\ b\cdot(A_x-k\cdot B_x) &=A_y-k\cdot B_y \\ b &=\frac{A_y-k\cdot B_y}{A_x-k\cdot B_x} \end{aligned} b⋅Ax​−k⋅b⋅Bx​b⋅(Ax​−k⋅Bx​)b​=Ay​−k⋅By​=Ay​−k⋅By​=Ax​−k⋅Bx​Ay​−k⋅By​​​

通过这样我们就把 b b b 给解出来了。

随便带进去,带到一式吧。

a ⋅ A x 2 = A y − b ⋅ A x a = A y − b ⋅ A x A x 2 \begin{aligned}a\cdot A_x^2 &= A_y-b\cdot A_x \\ a &= \frac{A_y-b\cdot A_x}{A_x^2} \end{aligned} a⋅Ax2​a​=Ay​−b⋅Ax​=Ax2​Ay​−b⋅Ax​​​

就这么解完了。

由于一条抛物线可能经过多个点,所以我们把每一条抛物线跑一遍,用二进制表示出其能经过哪些点。

然后问题就转化成了一个背包问题。

在这里,就把每个抛物线的价值进行了状压。

Code

#include<bits/stdc++.h>
#define mem(a,x) memset(a,x,sizeof(a))
using namespace std;
int T,n,m,cnt;
int Fc[400],dp[1<<19];
struct node{
	double x,y;
}a[19];
struct Par{
	double a,b;
}f[400];
bool Check(int i,int j){
	return fabs(a[j].y-(f[i].a*a[j].x*a[j].x+f[i].b*a[j].x))<0.000001;
}
int main(){
	scanf("%d",&T);
	while(T--){
		cnt=0;
		scanf("%d %d",&n,&m);
		for(int i=1;i<=n;i++) scanf("%lf %lf",&a[i].x,&a[i].y);
		for(int i=1;i<=n;i++)
			for(int j=i+1;j<=n;j++){
				double k=(a[i].x*a[i].x)/(a[j].x*a[j].x),A,B;
				B=(a[i].y-k*a[j].y)/(a[i].x-k*a[j].x);
				A=(a[i].y-a[i].x*B)/(a[i].x*a[i].x);
				if(A<0) f[++cnt]=((Par){A,B});
			}
		mem(Fc,0);
		mem(dp,0x3f);
		dp[0]=0;
		int ans=0;
		for(int i=1;i<=cnt;i++){
			for(int j=1;j<=n;j++) Fc[i]=Fc[i]*2+Check(i,j);
		}
		for(int i=1;i<=n;i++) Fc[++cnt]=1<<i-1;
		for(int i=0;i<=(1<<n)-1;i++)
			for(int j=1;j<=cnt;j++) dp[i|Fc[j]]=min(dp[i|Fc[j]],dp[i]+1);
		printf("%d\n",dp[(1<<n)-1]);
	}
	return 0;
} 

Solution

感觉全部题最难的一道,最好玩的。

{ a 3 ⋅ a 9 ⋅ a . . . 2 ⋅ a 6 ⋅   a 18 ⋅ a . . . 4 ⋅ a 12 ⋅ a 36 ⋅ a . . . . . . . . . . . . . . . } \begin{Bmatrix}a & 3\cdot a & 9\cdot a & ...\\ 2\cdot a & 6\cdot\ a & 18\cdot a & ... \\ 4\cdot a & 12\cdot a & 36\cdot a & ...\\... &... &...&... \end{Bmatrix} ⎩⎪⎪⎨⎪⎪⎧​a2⋅a4⋅a...​3⋅a6⋅ a12⋅a...​9⋅a18⋅a36⋅a...​............​⎭⎪⎪⎬⎪⎪⎫​

通过观察这个矩阵我们发现,不能取这个矩阵相邻的元素作为集合。

那么这不禁让我们想起了第一题,而且对于矩阵的大小,是 log ⁡ \log log 级别的。

那么我们的方法就显而易见了。

对于这个矩阵,我们把所有没有出现过的元素标记上,这样就避免了重复的计算。

当我们算完了一个矩阵,需要进行相乘,因为肯定任意两个矩阵互相还是可以取,就是乘法原理。

万一矩阵的数量太多,跑飞了怎么办?

别慌,我们来想一想。

我跑了一下 1 0 5 10^5 105,跑了 33333 33333 33333 个矩阵。

那接下来我们算一下一个矩阵的复杂度。

约是 10000 10000 10000 级别的样子,但是跑不满,因此还是可以过的,况且随着矩阵越来越多,状态就越来越少,因为越来越多的数都会被标记。

Code

#include<bits/stdc++.h>
#define mem(a,x) memset(a,x,sizeof(a))
using namespace std;
const int N=5e6+5;
const int mod=1e9+1;
int Fi,a[19][12],dp[18][5096];
int n,ans=1,Len[18],dq[18];
bool f[N],flag[18][12];
void Work(int x){
	mem(a,0);
	a[1][1]=x;
	int m=1,sum=0,len=0;
	for(int i=2;a[1][i-1]*3<=n;i++){
		a[1][i]=a[1][i-1]*3;
		m++;
	}
	for(int j=1;j<=m;j++)
		for(int i=2;a[i-1][j]*2<=n;i++) a[i][j]=a[i-1][j]*2;
	mem(Len,0);
	for(int i=1;a[i][1];i++){
		for(int j=1;j<=m;j++){
			Len[i]=Len[i]*2+(!a[i][j]);
			if(!a[i][j]) flag[i][j]=1;
			f[a[i][j]]=1;
		}
		len++;
	}
	mem(dp,0);
	dp[0][0]=1;
	for(int i=1;i<=len;i++){
		for(int j1=0;j1<=(1<<m)-1;j1++){
			if(Len[i]&j1 || j1&(j1<<1)) continue;
			for(int j2=0;j2<=(1<<m)-1;j2++){
				if(Len[i-1]&j2 || j2&(j2<<1) || j1&j2) continue;
				dp[i][j1]+=dp[i-1][j2];
				dp[i][j1]%=mod;
			}
		}
	}
	for(int i=0;i<=(1<<m)-1;i++) sum+=dp[len][i],sum%=mod;
	ans=(1ll*ans*sum)%mod;
}
int main(){
	scanf("%d",&n);
	for(int i=1;i<=n;i++){
		if(!f[i]){
			f[i]=1;
			Work(i);
		}
	}
	printf("%d",ans);
	return 0;
}

Solution

这题在洛谷上使用是标算二十倍的算法卡得过去。

Link

做得最爽的一道题。

首先还是思考如何将这个问题转化为状压。

其实这个题的连线和状压的另一种版题有点像,就是说传球,但是中间的直线需要另行判断。

什么直线呢?

就是说因为两点的直线必须是中间经过的点是使用过的。

可以很自然的想到直接枚举一层 n n n,然后暴力判断斜率。

O ( 2 n ⋅ n 3 ) O(2^n\cdot n^3) O(2n⋅n3)

8388608000 8388608000 8388608000,这么大,但是考虑到状压本身跑不满,因此复杂度虽然比这个小,但还是跑不过。

考虑位运算优化,我们先把任意两点间连线有的点给预处理下来。

然后进行状压取反后再和实际状态做与运算。

Code

#include<bits/stdc++.h>
#define mem(a,x) memset(a,x,sizeof(a))
using namespace std;
const int mod=1e8+7;
int n,Fi;
int dp[20][1<<19];
int sum[1<<19],check[20][20];
bool f[20];
struct node{
	int x,y;
}a[20];
inline int qr(){
	int s=0,f=1;
	char ch;
	ch=getchar();
	while(!isdigit(ch)){if(ch=='-') f=-1;ch=getchar();}
	while(isdigit(ch)){s=s*10+ch-'0';ch=getchar();}
	return s*f;
}
inline void qw(int x){
	if(x<0) putchar('-'),x=-x;
	if(x>9) qw(x/10);
	putchar(x%10+'0');
}
inline bool cmp(node x,node y){
	if(x.x==y.x) return x.y<y.y;
	return x.x<y.x;
}
inline void opt(int x){
	mem(f,0);
	int w=1;
	while(x) f[w]=(x&1),x>>=1,w++;
}
inline int Count(int x){
	int sum=0;
	while(x) sum+=x&1,x>>=1;
	return sum;
}
inline int Checker(int x,int y){
	mem(f,0);
	if(x>y) swap(x,y);
	int FF=0;
	if(x+1==y || x==y) return 0;
	for(int i=x+1;i<y;i++) if((a[x].x-a[i].x)*(a[i].y-a[y].y)==(a[i].x-a[y].x)*(a[x].y-a[i].y)) f[i]=1;
	for(int i=1;i<=n;i++) FF+=f[i]?1<<i-1:0;
	return FF;
}
int main(){
	n=qr();
	Fi=(1<<n)-1; 
	for(register int i=1;i<=n;i++) a[i]=((node){qr(),qr()});
	sort(a+1,a+1+n,cmp);
	for(register int i=1;i<=n;i++)
		for(register int j=1;j<=n;j++) check[i][j]=Checker(i,j);
	for(register int i=1;i<=n;i++) dp[i][1<<i-1]=1;
	for(register int j=0;j<=Fi;j++){
		opt(j);
		for(register int i=1;i<=n;i++){
			if(!f[i]) continue;
			for(register int k=1;k<=n;k++)
				if(f[k] && !(check[i][k]&(~j)) && k!=i) dp[i][j]+=dp[k][j-(1<<i-1)],dp[i][j]%=mod;
			sum[j]+=dp[i][j];
			sum[j]%=mod;
		}
	}
	int ans=0;
	for(register int j=0;j<=Fi;j++){
		if(Count(j)>=4) ans+=sum[j],ans%=mod;
	}
	printf("%d",ans);
	return 0;
} 

标签:return,变形,cdot,压缩,状压,j1,int,DP,dp
来源: https://blog.csdn.net/cqbzfsk/article/details/122700899

本站声明: 1. iCode9 技术分享网(下文简称本站)提供的所有内容,仅供技术学习、探讨和分享;
2. 关于本站的所有留言、评论、转载及引用,纯属内容发起人的个人观点,与本站观点和立场无关;
3. 关于本站的所有言论和文字,纯属内容发起人的个人观点,与本站观点和立场无关;
4. 本站文章均是网友提供,不完全保证技术分享内容的完整性、准确性、时效性、风险性和版权归属;如您发现该文章侵犯了您的权益,可联系我们第一时间进行删除;
5. 本站为非盈利性的个人网站,所有内容不会用来进行牟利,也不会利用任何形式的广告来间接获益,纯粹是为了广大技术爱好者提供技术内容和技术思想的分享性交流网站。

专注分享技术,共同学习,共同进步。侵权联系[81616952@qq.com]

Copyright (C)ICode9.com, All Rights Reserved.

ICode9版权所有