状态dp学习、

来源:互联网 发布:条形码数据采集器用法 编辑:程序博客网 时间:2024/06/07 08:26

练习衔接:传送门

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) {//floydfor(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 stateint 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;}