合理安排(狀壓dp,包括技巧)

cn是大帅哥886發表於2024-08-20
https://www.luogu.com.cn/problem/P1879
第4題 合理安排 檢視測評資料資訊

花園主John新買了一塊長方形的花園,這塊花園被精心地劃分成M行N列(1≤M≤12,1≤N≤12),每一格都是一塊正方形的花壇。John計劃在花園的某些花壇裡種上鮮豔的花朵,讓他的蜜蜂們能夠採集花蜜。然而,有些花壇的土壤質量不佳,不適合種植花朵。另外,為了保持花園的美觀和蜜蜂的舒適度,John不會選擇相鄰的花壇種植花朵,也就是說,沒有哪兩個花壇是相鄰的(即它們沒有公共邊)。John想知道,如果不考慮花朵的總數,那麼,他有多少種不同的種植方案可以選擇?(當然,將花園完全保持原樣不種植花朵也是一種方案)

輸入格式

第一行:兩個整數M和N,用空格隔開,分別代表花園的行數和列數。

第2到第M+1行:每行包含N個用空格隔開的整數,描述了每個花壇的土壤狀態。第i+1行描述了第i行的花壇狀態,所有整數均為0或1,其中1表示花壇土壤肥沃適合種植花朵,0則表示土壤不適合種植。

1<=M<=12,1<=N<=12

輸出格式

一個整數,即牧場分配總方案數除以100000000的餘數。

輸入/輸出例子1

輸入:

2 3

1 1 1

0 1 0

輸出:

9

樣例解釋

注意到範圍很小,且和dp可能扯上關係

考慮狀壓。

但是題目給了一些限制條件

1.對於同一行,相鄰列不同時為1,開個迴圈判斷即可,假設本行選了狀態是s, (s<<i) & (s<<(i+1)) ==0,s的第 i 位和第 i+1 位不同時為1即可。

2.對於不同行,相同列,相鄰行不同時為1,這個更簡單了,假設第一行選了狀態是x,第二行選了狀態是y,x & y == 0

然後我們可以預處理每一行的合法狀態,裝入vec,這樣可以減少列舉量,也方便一些

狀態和轉移就顯而易見了。

f(i, s) 當前考慮前i行,並且第i行的狀態是s的方案數

假設s2是第i行選的狀態,s1是第i-1行選的狀態。滿足限制條件時(只有限制2,因為這是對於不同行時的轉移),f(i, s2) += f(i-1, s1)

如何預處理合法狀態?這裡給一個小知識點

判斷一個集合是否是另外一個集合的子集

s2 & s == s2
s2是s的子集

所以我們先處理每一行合法狀態的最大值(就是每一行,可以種植的地,對應的二進位制和)

如何列舉二進位制狀態,前提是每個二進位制狀態是合法狀態最大值的子集才行,如何因為這是同一行,就只有限制1了。

#include <bits/stdc++.h>
#define int long long
using namespace std;
const int N=15, M=8500, Mod=100000000;

int n, m, a[N][N], f[N][M], ans=0;
vector<int> v[N];
bool check(int x)
{
	for (int i=0; i<=12; i++)
		if (((x<<i) & (x<<(i+1)))!=0) return 0;
	
	return 1;
}
signed main()
{
	scanf("%lld%lld", &n, &m);
	for (int i=1; i<=n; i++)
		for (int j=1; j<=m; j++)
			scanf("%lld", &a[i][j]);
			
	for (int i=1; i<=n; i++) 
	{
		int s=0;
		for (int j=1; j<=m; j++)
			if (a[i][j]) s+=(1<<(j-1));
		
		for (int j=0; j<(1<<m); j++)
			if ((j&s)==j && check(j)) v[i].push_back(j);
	}
	
	for (int i=0; i<v[1].size(); i++) f[1][v[1][i]]=1;
	
	for (int i=2; i<=n; i++)
		for (int j=0; j<v[i].size(); j++)
			for (int k=0; k<v[i-1].size(); k++)
				if ((v[i][j]&v[i-1][k])==0) f[i][v[i][j]]=(f[i][v[i][j]]+f[i-1][v[i-1][k]])%Mod;
	
	for (int i=0; i<v[n].size(); i++) ans=(ans+f[n][v[n][i]])%Mod;	
	
	printf("%lld", ans);
	return 0;
}

  

相關文章