练习衔接:传送门
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;
}