状态dp学习、

练习衔接:传送门

POJ 3254

题意:给出n*m的矩阵,矩阵中只有0/1两种数字,你能在1数字的位置建立农场,要求农场之间没有公共边,现在问你有多少种建法

思路:入门题,dp[i][j]代表第i行状态j时有多少种建法

#include <cstdio>
#include <cstring>
#include <cmath>
#include <iostream>
#include <algorithm>
#include <sstream>
#include <map>
#include <set>
#include <vector>
#include <utility>
#include <queue>
#include <stack>
#include <string>

using namespace std;
#define LL long long
#define pb push_back
#define mk make_pair
#define pill pair<int, int>
#define mst(a, b)	memset(a, b, sizeof a)
#define REP(i, x, n)	for(int i = x; i <= n; ++i)
const int MOD = 1e9;
const int qq = 30 + 10;
int n, m;
int gra[qq][qq], dp[qq][1 << 13];
vector<int> vt[qq];
int State(int x) {
	int t = 1;
	int ans = 0;
	for(int i = m; i >= 1; --i) {
		if(gra[x][i] == 0)	ans += t;
		t <<= 1;
	}
	return ans;
}

int main(){
	scanf("%d%d", &n, &m);
	for(int i = 1; i <= n; ++i) {
		for(int j = 1; j <= m; ++j) {
			scanf("%d", &gra[i][j]);
		}
	}
	vt[0].pb(0);
	int k = (1 << m);
	for(int i = 0; i < k; ++i) {
		dp[0][i] = 1;
	}
	for(int i = 1; i <= n; ++i) {
		int now = State(i);        //注意now实际上是把0的位置给标记出来了
		for(int j = 0; j < k; ++j) {
			if(j & (j >> 1))	continue;
			if(now & j)	continue;
			vt[i].pb(j);
		}
		for(int j = 0; j < (int)vt[i].size(); ++j) {
			int a = vt[i][j];
			for(int l = 0; l < (int)vt[i - 1].size(); ++l) {
				int b = vt[i - 1][l];
				if(a & b)	continue;
				dp[i][a] = (dp[i][a] + dp[i - 1][b]) % MOD;
			}
		}
	}
	int ans = 0;
	for(int i = 0; i < k; ++i) {
		ans = (ans + dp[n][i]) % MOD;
	}
	printf("%d\n", ans);
	return 0;
}

POJ 1185

思路:dp[i][j][k]代表第i行为状态j, i-1行为状态k时能摆放的最大炮兵数量,其实类似于上面那一题

#include <cstdio>
#include <cstring>
#include <cmath>
#include <iostream>
#include <algorithm>
#include <sstream>
#include <map>
#include <set>
#include <vector>
#include <utility>
#include <queue>
#include <stack>
#include <string>

using namespace std;
#define LL long long
#define pb push_back
#define mk make_pair
#define pill pair<int, int>
#define mst(a, b)	memset(a, b, sizeof a)
#define REP(i, x, n)	for(int i = x; i <= n; ++i)
const int MOD = 1e9 + 7;
const int qq = 100 + 5;
int dp[qq][qq][qq];
char gra[qq][15];
vector<int> vt[qq];
int n, m;
int State(int x) {
	int t = 1, ans = 0;
	for(int i = m; i >= 1; --i) {
		if(gra[x][i] == 'H')	ans += t;
		t <<= 1;
	}
	return ans;
}
int Cal(int S) {
	int ans = 0;
	for(int i = m - 1; i >= 0; --i) {
		if(S & (1 << i))	++ans;
	}
	return ans;
}

int main(){
	scanf("%d%d", &n, &m);
	n += 2;
	for(int i = 3; i <= n; ++i) {
		scanf("%s", gra[i] + 1);
	}
	int ms = (1 << m);
	for(int i = 3; i <= n; ++i) {
		int now = State(i);
		for(int j = 0; j < ms; ++j) {
			if(j & (j >> 1) || j & (j >> 2))	continue;
			if(j & now)	continue;
			vt[i].pb(j);
		}
	}
	vt[1].pb(0), vt[2].pb(0);
	for(int l = 3; l <= n; ++l) {
		int a, b, c;
		for(int i = 0; i < (int)vt[l].size(); ++i) {
			a = vt[l][i];
			for(int j = 0; j < (int)vt[l - 1].size(); ++j) {
				b = vt[l - 1][j];
				for(int k = 0; k < (int)vt[l - 2].size(); ++k) {
					c = vt[l - 2][k];
					if(a & b || a & c || b & c)	continue;
					dp[l][i][j] = max(dp[l][i][j], dp[l - 1][j][k] + Cal(a));
				}
			}
		}
	}
	int ans = 0;
	for(int j = 0; j < (int)vt[n].size(); ++j) {
		for(int k = 0; k < (int)vt[n - 1].size(); ++k) {
			ans = max(ans, dp[n][j][k]);
		}
	}
	printf("%d\n", ans);
	return 0;
}


POJ 3311

题意:给出n*n的邻接矩阵,问你从0出发访问所有点再回到0点最少花费是多少,任意点可以访问任意次数

思路:由于任意点可以访问任意次,所以我们求一下floyd计算出任意两点之间的最小距离,然后我们要明确假设最后访问的点是i(i != 0),无论怎么样他都要回到0点去,那么我们可以这样dp[i][j]代表状态i时,最后到达点j,那么我们在最后的状态下枚举一下谁是最后一个访问点即可

#include <cstdio>
#include <cstring>
#include <cmath>
#include <iostream>
#include <algorithm>
#include <sstream>
#include <map>
#include <set>
#include <vector>
#include <utility>
#include <queue>
#include <stack>
#include <string>

using namespace std;
#define LL long long
#define pb push_back
#define mk make_pair
#define pill pair<int, int>
#define mst(a, b)	memset(a, b, sizeof a)
#define REP(i, x, n)	for(int i = x; i <= n; ++i)
const int MOD = 1e9 + 7;
const int INF = 0x3f3f3f3f;
const int qq = 1 + 10;
int n, m, minx;
int gra[12][12];
int bit[11];
int dp[60000][12];
int num[60000][12];
/*void MakeTrb() {
	bit[0] = 1;
	for(int i = 1; i < 11; ++i) {
		bit[i] = bit[i - 1] * 3;
	}
	for(int i = 0; i < bit[10]; ++i) {
		int x = i;
		for(int j = 0; j < 10; ++j) {
			num[i][j] = x % 3;
			x /= 3;
		}
	}
}
*/
int main(){
//	MakeTrb();
	while(scanf("%d", &n) != EOF) {
		if(!n)	break;
		n++;
		for(int i = 0; i < n; ++i) {
			for(int j = 0; j < n; ++j) {
				scanf("%d", &gra[i][j]);
			}
		}
		for(int k = 0; k < n; ++k) {		//floyd
			for(int i = 0; i < n; ++i) {
				for(int j = 0; j < n; ++j) {
					gra[i][j] = min(gra[i][j], gra[i][k] + gra[k][j]);
				}
			}
		}
		mst(dp, INF);
		dp[1][0] = 0;			// begin state
		int next;
		for(int i = 0; i < (1 << n); ++i) {
			for(int j = 0; j < n; ++j) {
				if(dp[i][j] == INF)	continue;
				for(int k = 0; k < n; ++k) {
					if(j == k || (i & (1 << k)))	continue;
					next = i + (1 << k);
					dp[next][k] = min(dp[next][k], dp[i][j] + gra[j][k]);
				}
			}
		}
		minx = INF;
		for(int i = 1; i < n; ++i) {
			minx = min(minx, gra[i][0] + dp[(1 << n) - 1][i]);
		}
		printf("%d\n", minx);
	}
	return 0;
}

HDU 3001

题意:你可以从任意点出发,要求访问所有点,并且任何一个点最多只能访问两次,问你最小花费

思路:实在没想到三进制表示,参考了某位聚聚的思路才知道可以这样搞。

另外感叹一下状压的美妙,之前一直向不通,为什么要从0开始枚举状态,后面的状态不能更新前面的状态玛?

代码中有这么一块

next = i + bit[k];
dp[next][k] = min(dp[next][k], dp[i][j] + gra[j][k]);

i就是当前的状态, next就是下一步的状态,很显然每次从i开始更新的状态只能向上加。

也就是说你遍历到状态i时,所有能到达状态i的情况都已经考虑了。

#include <cstdio>
#include <cstring>
#include <cmath>
#include <iostream>
#include <algorithm>
#include <sstream>
#include <map>
#include <set>
#include <vector>
#include <utility>
#include <queue>
#include <stack>
#include <string>

using namespace std;
#define LL long long
#define pb push_back
#define mk make_pair
#define pill pair<int, int>
#define mst(a, b)	memset(a, b, sizeof a)
#define REP(i, x, n)	for(int i = x; i <= n; ++i)
const int MOD = 1e9 + 7;
const int INF = 0x3f3f3f3f;
const int qq = 1 + 10;
int n, m, minx;
int gra[12][12];
int bit[11];
int dp[60000][12];
int num[60000][12];
void MakeTrb() {
	bit[0] = 1;
	for(int i = 1; i < 11; ++i) {
		bit[i] = bit[i - 1] * 3;
	}
	for(int i = 0; i < bit[10]; ++i) {
		int x = i;
		for(int j = 0; j < 10; ++j) {
			num[i][j] = x % 3;
			x /= 3;
		}
	}
}

int main(){
	MakeTrb();
	while(scanf("%d%d", &n, &m) != EOF) {
		mst(gra, -1);
		for(int i = 0; i < m; ++i) {
			int a, b, c;
			scanf("%d%d%d", &a, &b, &c);
			if(gra[a - 1][b - 1] == -1) {
				gra[a - 1][b - 1] = gra[b - 1][a - 1] = c;
			} else {
				gra[a - 1][b - 1] = gra[b - 1][a - 1] = min(c, gra[a - 1][b - 1]);
			}
		}
		mst(dp, INF);
		for(int i = 0; i < n; ++i) {
			dp[bit[i]][i] = 0;
		}
		int flag, next;
		minx = INF;
		for(int i = 0; i < bit[n]; ++i) {
			flag = 1;
			for(int j = 0; j < n; ++j) {
				if(num[i][j] == 0)	flag = 0;
				if(dp[i][j] == INF)	continue;
				for(int k = 0; k < n; ++k) {
					if(j == k || num[i][k] >= 2 || gra[j][k] == -1)	continue;
					next = i + bit[k];
					dp[next][k] = min(dp[next][k], dp[i][j] + gra[j][k]);
				}
			}
			if(flag == 1) {
				for(int j = 0; j < n; ++j) {
					minx = min(minx, dp[i][j]);
				}
			} 	
		}
		if(minx == INF)	minx = -1;
		printf("%d\n", minx);
	}
	return 0;
}



POJ 2288

题意:给出n个点和m条边,然后给出每一个点的val[i],现在问你取走一条Hamilton path,要求访问每一点恰好一次,要求获得最大价值,价值计算除了获得走过那点的val[i]之外还有两条规则,对于一条路径上的相邻两个点a,b获得val[a] * val[b],对于一条路径上的连续三个点a,b,c,如果他们之间存在环,那么获得val[a] * val[b] *val[c]

思路:dp[i][j][k]代表i状态时走到j点,并且上一个点是k点的最大价值,然后注意一下初状态就好了

PS:这题我之前一直WA、看discuss才知道我没有处理只有一个点的情况

#include <cstdio>
#include <cstring>
#include <cmath>
#include <iostream>
#include <algorithm>
#include <sstream>
#include <map>
#include <set>
#include <vector>
#include <utility>
#include <queue>
#include <stack>
#include <string>

using namespace std;
#define LL long long
#define pb push_back
#define mk make_pair
#define pill pair<int, int>
#define mst(a, b)	memset(a, b, sizeof a)
#define REP(i, x, n)	for(int i = x; i <= n; ++i)
const int MOD = 1e9 + 7;
const int qq = 5 + 10;
int gra[qq][qq];
LL dp[(1 << 13) + 10][qq][qq], val[qq], path[(1 << 13) + 10][qq][qq];
int n, m;
void Init() {
	mst(gra, 0);
	mst(dp, -1);
	mst(path, 0);
}

int main(){
	int t;	scanf("%d", &t);
	while(t--) {
		Init();
		scanf("%d%d", &n, &m);
		for(int i = 0; i < n; ++i) {
			scanf("%lld", val + i);
		}
		if(n == 1) {
			printf("%lld 1\n", val[0]);
			continue;
		}
		for(int i = 0; i < m; ++i) {
			int a, b;	scanf("%d%d", &a, &b);
			a--, b--;
			gra[a][b] = gra[b][a] = 1;
			int s = (1 << a) + (1 << b);
			dp[s][a][b] = dp[s][b][a] = val[a] * val[b] + val[a] + val[b];
			path[s][a][b] = path[s][b][a] = 1;
		}
		for(int i = 0; i < (1 << n); ++i) {
			for(int j = 0; j < n; ++j) {
				if(!(i & (1 << j))) continue;
				for(int k = 0; k < n; ++k) {
					if(j == k || (!(i & (1 << k))))	continue;
					if(dp[i][k][j] == -1)	continue;
					for(int l = 0; l < n; ++l) {
						if(l == k || l == j || i & (1 << l) || !gra[k][l])	continue;
						int next = i + (1 << l);
						LL x = val[k] * val[l];
						LL y = val[j] * val[k] * val[l];
						if(!gra[j][k] || !gra[k][l] || !gra[l][j])	y = 0;
						if(dp[next][l][k] < dp[i][k][j] + x + y + val[l]) {
							dp[next][l][k] = dp[i][k][j] + x + y + val[l];
							path[next][l][k] = path[i][k][j];
						} else if(dp[next][l][k] == dp[i][k][j] + x + y + val[l]) {
							path[next][l][k] += path[i][k][j];
						}
					}
				}
			}
		}
		LL maxn = -1;
		for(int i = 0; i < n; ++i) {
			for(int j = 0; j < n; ++j) {
				if(i == j || !gra[i][j])	continue;
				maxn = max(maxn, dp[(1 << n) - 1][i][j]);
			}
		}
		LL num = 0;
		if(maxn != -1){
			for(int i = 0; i < n; ++i) {
				for(int j = 0; j < n; ++j) {
					if(i == j || !gra[i][j])	continue;
					if(dp[(1 << n) - 1][i][j] == maxn) {
						num += path[(1 << n) - 1][i][j];
					}
				}
			}
		}
		if(maxn == -1)	maxn = num = 0;
		printf("%lld %lld\n", maxn, num / 2);
	}
	return 0;
}


POJ 2411

题意:给出n*m的矩阵,用1*2的小矩阵填满n*m的矩阵,问有多少种方案

这题看了很久阿很久阿、

算法竞赛入门经典P384

这里讲解的其实比较详细了、只是我比较蠢看了半天

参考:传送门

轮廓线

这种思想真的好厉害阿、

每次只考虑一个点(i, j),枚举它前m个点的状态k就是轮廓线,就这一个点的状态只和轮廓线的状态有关。

点(i, j)只有三种选择不放、向上放、向左放,然后依次考虑三种放法即可、

#include <cstdio>
#include <cstring>
#include <cmath>
#include <iostream>
#include <algorithm>
#include <sstream>
#include <map>
#include <set>
#include <vector>
#include <utility>
#include <queue>
#include <stack>
#include <string>

using namespace std;
#define LL long long
#define pb push_back
#define mk make_pair
#define pill pair<int, int>
#define mst(a, b)	memset(a, b, sizeof a)
#define REP(i, x, n)	for(int i = x; i <= n; ++i)
const int MOD = 1e9 + 7;
const int qq = 2e5 + 10;
const double eps = 1e-9;
int n, m;
int cur;
LL dp[2][1 << 15], tmp;
void Update(int a, int b) {
	if(b & (1 << m))	dp[cur][b ^ (1 << m)] += dp[1 - cur][a];
}

int main(){
	while(scanf("%d%d", &n, &m) != EOF) {
		if(!n && !m)	break;
		mst(dp, 0);
		cur = 0;
		dp[0][(1 << m) - 1] = 1;
		for(int i = 0; i < n; ++i) {
			for(int j = 0; j < m; ++j) {
				cur ^= 1;
				mst(dp[cur], 0);
				for(int k = 0; k < (1 << m); ++k) {
					Update(k, k << 1);
					if(i && !(k & (1 << (m - 1))))	Update(k, (k << 1) ^ (1 << m) ^ 1);
					if(j && !(k & 1))	Update(k, (k << 1) ^ 3);
				}	
			}
		}
		printf("%lld\n", dp[cur][(1 << m) - 1]);
	}
	return 0;
}


HDU 3681

题意:给出n*m的矩阵

S代表空地可以走

F代表出发点,只有一个

D代表障碍不能走这点

G代表能量充满点,这一点可以将自身能量充满,这一点只能使用一次,使用之后变成S

Y代表能量转换器,走到这一点后开关会关闭

你从F点出发,要求关闭所有的Y,你的能量容量最低是多少,最初能量是满的

思路:Y + G小于15个,那么我们是不是只需要考虑再F G Y这些点之间走就行了? 用BFS求出FGY这些点到达另外的FGY这些点的最短路径,然后进行状压类似于前面的哈密顿回路的状压。

#include <cstdio>
#include <cstring>
#include <cmath>
#include <iostream>
#include <algorithm>
#include <sstream>
#include <map>
#include <set>
#include <vector>
#include <utility>
#include <queue>
#include <stack>
#include <string>

using namespace std;
#define LL long long
#define pb push_back
#define mk make_pair
#define pill pair<int, int>
#define ft first
#define sd second
#define mst(a, b)	memset(a, b, sizeof a)
#define REP(i, x, n)	for(int i = x; i <= n; ++i)
const int MOD = 1e9 + 7;
const int qq = 18;
const double eps = 1e-9;
int n, m, start, FinalState, cnt;
int dis[qq][qq][qq][qq];
char gra[qq][qq];
pill node[qq];
int dp[1 << qq][qq];
int dx[] = {0, 0, -1, 1};
int dy[] = {1, -1, 0, 0};
void Bfs(int st, int ed) {
	if(gra[st][ed] == 'D' || gra[st][ed] == 'S')	return;
	/*for(int i = 0; i < n; ++i) {
		for(int j = 0; j < m; ++j) {
			dis[st][ed][i][j] = -1;
		}
	}*/
	dis[st][ed][st][ed] = 0;
	queue<pill> Q;
	Q.push(mk(st, ed));
	while(!Q.empty()) {
		pill tmp = Q.front();
		Q.pop();
		for(int i = 0; i < 4; ++i) {
			int x = tmp.ft + dx[i];
			int y = tmp.sd + dy[i];
			if(x < 0 || y < 0 || x >= n || y >= m)	continue;
			if(gra[x][y] == 'D')	continue;
			if(dis[st][ed][x][y] != -1)	continue;
			dis[st][ed][x][y] = dis[st][ed][tmp.ft][tmp.sd] + 1;
			Q.push(mk(x, y));
		}
	}
}
void Init() {
	mst(dis, -1);
	FinalState = cnt = 0;
}
bool Check(int v) {
	mst(dp, -1);
	dp[1 << start][start] = v;
	int s = (1 << cnt);
	for(int i = 0; i < s; ++i) {
		for(int j = 0; j < cnt; ++j) {
			if(!(i & (1 << j)))	continue;
			if(dp[i][j] == -1)	continue;
			if((i & (FinalState)) == FinalState)	return true;
			for(int k = 0; k < cnt; ++k) {
				if((i & (1 << k)) || k == j)	continue;
				int tmp = dis[node[j].ft][node[j].sd][node[k].ft][node[k].sd];
				if(tmp == -1 || dp[i][j] < tmp)	continue;
				int next = i | (1 << k);
				dp[next][k] = max(dp[next][k], dp[i][j] - tmp);
				if(gra[node[k].ft][node[k].sd] == 'G') {
					dp[next][k] = v;
				}
			}
		}
	}
	return false;
}

int main(){
	while(scanf("%d%d", &n, &m) != EOF) {
		if(!n && !m)	break;
		Init();
		for(int i = 0; i < n; ++i) {
			scanf("%s", gra[i]);	
		}
		for(int i = 0; i < n; ++i) {
			for(int j = 0; j < m; ++j) {
				char tmp = gra[i][j];
				if(tmp == 'F') {
					start = cnt;
					FinalState |= (1 << cnt);
				} else if(tmp == 'Y') {
					FinalState |= (1 << cnt);
				}
				if(tmp == 'F' || tmp == 'G' || tmp == 'Y') {
					node[cnt].ft = i, node[cnt].sd = j;
					cnt++;
				}
			}	
		}
		for(int i = 0; i < n; ++i) {
			for(int j = 0; j < m; ++j){
				Bfs(i, j);
			}
		}
		int l = 0, r = n * m;
		int ans = -1;
		while(l <= r) {
			int mid = (l + r) / 2;
			if(Check(mid)) {
				ans = mid;
				r = mid - 1;
			} else {
				l = mid + 1;
			}
//			printf("%d %d\n", l, r);
		}
		printf("%d\n", ans);
	}
	return 0;
}



评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值