YBTOJ 5.5状压DP

A.种植方案

image
image

范围看起来很状压 放不放奶牛看起来也很状压 那我们就状压
首先判左右不同时有 那就判它左/右移一位和原来与起来为 \(0\)
然后判上下不同时有 那就判上一行和当前行与起来为 \(0\)
然后要判当前放置方案和地形是否符合 那就判它和地形与起来不为 \(0\)
有个常数上的小优化 就是提前枚举预处理出合法方案
但是不预处理直接暴力枚举也能过

点击查看代码
#include <bits/stdc++.h>
using namespace std;

const int N = 1e4 + 0721;
const int M = 15;
const int mod = 1e8;
int loc[M];
int f[M][N];
int m, n;

int main() {
	scanf("%d%d", &m, &n);
	for (int i = 1; i <= m; ++i) {
		for (int j = 1; j <= n; ++j) {
			int x;
			scanf("%d", &x);
			loc[i] += ((1 - x) << j - 1);
		}
//		cout << i << " " << loc[i] << "\n";
	}
	
	
	for (int j = 0; j <= (1 << n) - 1; ++j) {
//		cout << j << endl; 
		if (((j & loc[1]) == 0) && ((j & (j >> 1)) == 0) && ((j & (j << 1)) == 0)) {
//			cout << j << endl;
			f[1][j] = 1;
		}
	}
	f[1][0] = 1;
	
	for (int i = 2; i <= m; ++i) {
		for (int j = 0; j <= (1 << n) - 1; ++j) {
			if ((j & (j << 1)) || (j & (j >> 1)))
				continue;
			if ((j & loc[i]) == 0 || j == 0) {
//				cout << j << endl;
				for (int k = 0; k <= (1 << n) - 1; ++k) {
					if ((j & k) == 0 && ((k & (k << 1)) == 0) && ((k & (k >> 1)) == 0))
						f[i][j] = (f[i][j] + f[i - 1][k]) % mod;
				}
				
			}
		}
	}
	
	int sum = 0;
	for (int i = 0; i <= (1 << n) - 1; ++i) {
		sum = (sum + f[m][i]) % mod;
	}
	
	printf("%d",sum);
	
	return 0;
}

B.最短路径

image
image

首先想到搜索 但是算一下也就只能过得去 \(n \le 10\) 的数据范围
那考虑一下这题与普通搜索题多了什么限制条件
发现如果是普通搜索 可以让每个点被经过多次
但是此题每个点只能被经过一次
所以说对于一个点来说 它只有两个状态:没被经过或者被经过一次
考虑状压 非常神奇的一个写法
\(f[i][j]\) 表示现在在第 \(i\) 个点 当前点的状态为 \(j\) 时的最短路
那我们枚举当前状态 \(j\) 作为外层循环 内层循环枚举当前的点 \(i\) 以及可以到达的点 \(k\)
然后判合法性 如果当前状态 \(j\) 中表示 \(i\)\(k\) 的那位为 \(0\) 显然就不合法

点击查看代码
#include <bits/stdc++.h>
using namespace std;

const int N = 22;
int dis[N][N], f[N][(1 << 21)];
int n;

int main() {
	scanf("%d", &n);
	for (int i = 1; i <= n; ++i) {
		for (int j = 1; j <= n; ++j) scanf("%d", &dis[i][j]);
	}
	
	memset(f, 0x3f, sizeof f );
	f[1][1] = 0; 
	
	for (int j = 0; j < (1 << n); ++j) {
		for (int i = 1; i <= n; ++i) {
			if (!(j & (1 << i - 1))) continue;
			for (int k = 1; k <= n; ++k) {
				if (k == i) continue;
				if (!(j & (1 << k - 1))) continue;
				f[i][j] = min(f[i][j], f[k][j ^ (1 << i - 1)] + dis[i][k]);
			}
		}
	}
	
	printf("%d",f[n][(1 << n) - 1]);
	
	return 0;
}

C.涂抹果酱

image
image

不难发现如果我们能写出三进制状压 这题就能秒
那我们研究一下三进制状压怎么写
首先位运算那一车东西肯定都要丢掉用不了
我们考虑将一个数化成三进制 显然是不断除 \(3\) 然后对 \(3\) 取模
那我们就可以记录范围内十进制数化成三进制的每一位
但是显然这个十进制很多 开数组空间不够用
进一步我们发现 我们只需要合法的三进制状态 即没有 \(0/1/2\) 左右相邻的状态
那我们就可以弄一个 \(tmp\) 数组把它拆掉然后判合法 合法之后再记录到要用的记录数组中
这样顺便也完成了离散化
这题同样可以预处理出合法状态来缩小常数

点击查看代码
#include <bits/stdc++.h>
using namespace std;

const int N = 1e4 + 10;
const int M = 10;
const int mod = 1e6;
bool valid[N][N];
int a[N][M], f[N][N];
int id[N];
int n, m, kk, sum, ans;
int tot;

void init_num() {
	int t = pow(3, m);
	for (int i = 0; i < t; ++i) {
		int tmp[M], num = i, len = 0;
		memset(tmp, 0, sizeof tmp );
		while (num) {
			tmp[++len] = num % 3;
			num /= 3;
//			cout << "1";
		}
		bool flag = 0;
		for (int j = 2; j <= m; ++j) {
			if (tmp[j] == tmp[j - 1]) {
				flag = 1;
				break;
			}
		}
		if (!flag) {
			id[i] = ++tot;
			for (int j = 1; j <= m; ++j) a[tot][j] = tmp[j];
		}
	}
}

void init_valid() {
	for (int i = 1; i <= tot; ++i) {
		valid[i][i] = 0;
		for (int j = i + 1; j <= tot; ++j) {
			bool flag = 0;
			for (int k = 1; k <= m; ++k) {
				if (a[i][k] == a[j][k]) {
					flag = 1;
					break;
				}
			}
			if (flag) valid[i][j] = valid[j][i] = 0;
			else valid[i][j] = valid[j][i] = 1;
		}
	}
}

void dp() {
	if (kk == 1) {
		f[1][id[sum]] = 1;
		for (int i = 2; i <= n; ++i) {
			for (int j = 1; j <= tot; ++j) {
				for (int k = 1; k <= tot; ++k) {
					if (valid[j][k]) f[i][j] = (f[i][j] + f[i - 1][k]) % mod;
				}
			}
		}
	} else {
		for (int i = 1; i <= tot; ++i) f[1][i] = 1;
		for (int i = 2; i <= n; ++i) {
			if (i == kk) {
				for (int j = 1; j <= tot; ++j) {
					if (valid[id[sum]][j]) f[i][id[sum]] = (f[i][id[sum]] + f[i - 1][j]) % mod;
				}
			} else {
				for (int j = 1; j <= tot; ++j) {
					for (int k = 1; k <= tot; ++k) {
						if (valid[j][k]) f[i][j] = (f[i][j] + f[i - 1][k]) % mod;
					}
				}
			}
		}
	}
}

int main() {
	scanf("%d%d", &n, &m);
	scanf("%d", &kk);
	for (int i = 0; i < m; ++i) {
		int x;
		scanf("%d", &x);
		sum = sum * 3 + x - 1;
	}
	init_num();
	init_valid();
	dp();
	for (int i = 1; i <= tot; ++i) ans = (ans + f[n][i]) % mod;
	printf("%d",ans);
	
	return 0;
}

D.炮兵阵地

image
image

首先左和左左 右和右右是非常好判的 直接判左/右移 \(1/2\) 位即可
主要是如何判上和上上
首先我们还是看原来只用判上的 我们用 \(f[i][j]\) 表示第 \(i\) 行 当前状态为 \(j\) 的方案数
那么如果是上上 我们不妨扩展到 \(f[i][j][k]\) 表示第 \(i\) 行 当前状态为 \(j\) 上一行状态为 \(k\) 的方案数
转移式就比较显然了 主要是要预处理第 \(1\) \(2\) 行的方案数
(有一说一 越写越感觉到预处理对常数优化的强大作用)

点击查看代码
#include <bits/stdc++.h>
using namespace std;

const int N = 105;
bool valid[N][N];
int f[N][N][N], val[N], s[N], mp[N], tot;
int n, m, ans;

inline int lowbit(int x) {
	return x & (-x);
}

void init() {
	for (int i = 0; i < (1 << m); ++i) {
		if ((i & (i << 1)) || (i & (i << 2)) || (i & (i >> 1)) || (i & (i >> 2))) continue;
		val[++tot] = i;
	}
	for (int i = 1; i <= tot; ++i) {
		int tmp = val[i];
		while (tmp) {
			++s[i];
			tmp -= lowbit(tmp);
		}
	}
	for (int i = 1; i <= tot; ++i) {
		valid[i][i] = 0;
		for (int j = i + 1; j <= tot; ++j) {
			if (val[i] & val[j]) valid[i][j] = valid[j][i] = 0;
			else valid[i][j] = valid[j][i] = 1;
		}
	}
}

void dp() {
	for (int i = 1; i <= tot; ++i) {
		if (val[i] & mp[1]) continue;
		for (int j = 1; j <= tot; ++j) {
			if (val[j] & mp[2]) continue;
			if (valid[i][j]) f[2][j][i] = s[i] + s[j];
		}
	}
	for (int i = 3; i <= n; ++i) {
		for (int j = 1; j <= tot; ++j) {
			if (val[j] & mp[i]) continue;
			for (int k = 1; k <= tot; ++k) {
				if (!valid[j][k])  continue;
				for (int l = 1; l <= tot; ++l) {
					if (!valid[j][l]) continue;
					if (!valid[k][l]) continue;
					f[i][j][k] = max(f[i][j][k], f[i - 1][k][l] + s[j]);
				}
			}
		}
	}
}

int main() {
	scanf("%d%d", &n, &m);
	for (int i = 1; i <= n; ++i) {
		for (int j = 1; j <= m; ++j) {
			char c;
			cin >> c;
			mp[i] <<= 1;
			if (c == 'H') ++mp[i];
		}
	}
	init();
	dp();
	
	for (int i = 1; i <= tot; ++i) {
		for (int j = 1; j <= tot; ++j) ans = max(ans, f[n][i][j]);
	}
	printf("%d",ans);
	
	return 0;
}

E.最优组队

image
image

枚举子集 复杂度 \(\text{O}(3^n)\)

点击查看代码
#include <bits/stdc++.h>
#define ll long long
using namespace std;

inline int read() {
	int xr = 0, F = 1;
	char cr;
	while (cr = getchar(), cr < '0' || cr > '9') if (cr == '-') F = -1;
	while (cr >= '0' && cr <= '9')
		xr = (xr << 3) + (xr << 1) + (cr ^ 48), cr = getchar();
	return xr * F;
}

void write(ll x) {
	char ws[51];
	int wt = 0;
	if (x < 0) putchar('-'), x = -x;
	do {
		ws[++wt] = x % 10 + '0';
		x /= 10;
	} while (x);
	for (int i = wt; i; --i) putchar(ws[i]);
}

namespace steven24 {

const int N = 1e6 + 0721;
int f[N];
int n;

void main() {
	n = read();
	for (int i = 1; i <= (1 << n) - 1; ++i) f[i] = read();
	for (int i = 1; i <= (1 << n) - 1; ++i) {
		for (int j = i; j; j = (j - 1) & i) f[i] = max(f[i], f[i ^ j] + f[j]);
	}
	write(f[(1 << n) - 1]), putchar('\n');
}

}

int main() {
	steven24::main();
	return 0;
}
/*
3 
41 
12 
57 
94 
89 
23 
12
*/

F.最短路径

image
image

这是我在 ybt 里面见到的第几道最短路径了。。。

直接把起点和终点都加入特殊点然后跑哈密顿路即可
写的时候把 (s << 1) 写成了 (k << 1) 调了我15分钟 /fn

点击查看代码
#include <bits/stdc++.h>
#pragma GCC optimize(2)
#define ll long long
using namespace std;

inline int read() {
	int xr = 0, F = 1;
	char cr;
	while (cr = getchar(), cr < '0' || cr > '9') if (cr == '-') F = -1;
	while (cr >= '0' && cr <= '9')
		xr = (xr << 3) + (xr << 1) + (cr ^ 48), cr = getchar();
	return xr * F;
}

void write(ll x) {
	char ws[51];
	int wt = 0;
	if (x < 0) putchar('-'), x = -x;
	do {
		ws[++wt] = x % 10 + '0';
		x /= 10;
	} while (x);
	for (int i = wt; i; --i) putchar(ws[i]);
}

namespace steven24 {

const int N = 5e4 + 0721;
const int M = 1e5 + 0721;
int head[N], nxt[M], to[M], len[M], cnt;
int sp[15];
ll dis[15][N], f[15][5210];
int n, m, k, S, T;

inline void add_edge(int x, int y, int z) {
	to[++cnt] = y;
	nxt[cnt] = head[x];
	head[x] = cnt;
	len[cnt] = z;
}

struct node {
	int id;
	ll dis;
	friend bool operator<(node x, node y) {
		return x.dis > y.dis;
	}
};
priority_queue<node> q;
bool vis[N];

void dijkstra(int s, int id) {
	memset(vis, 0, sizeof vis);
	dis[id][s] = 0;
	q.push((node){s, 0});
	while (!q.empty()) {
		int now = q.top().id;
		q.pop();
		if (vis[now]) continue;
		vis[now] = 1;
		for (int i = head[now]; i; i = nxt[i]) {
			int y = to[i]; 
			if (dis[id][y] > dis[id][now] + len[i]) {
				dis[id][y] = dis[id][now] + len[i];
				q.push((node){y, dis[id][y]});
			}
		}
	}
}

void main() {
	n = read(), m = read(), k = read(), S = read(), T = read();
	memset(f, 0x3f, sizeof f);
	for (int i = 1; i <= m; ++i) {
		int x = read(), y = read(), z = read();
		add_edge(x, y, z);
	}
	for (int i = 1; i <= k; ++i) {
		int x = read();
		sp[i] = x;
	}
	sp[0] = S;
	++k;
	sp[k] = T;
	memset(dis, 0x3f, sizeof dis);
	for (int i = 0; i <= k; ++i) dijkstra(sp[i], i);
	
	f[0][1] = 0;
	for (int i = 0; i <= (1 << k + 1) - 1; ++i) {
		for (int j = 0; j <= k; ++j) {
			for (int s = 0; s <= k; ++s) {
				if (s == j) continue;
				if ((i & (1 << j)) == 0) continue;
				if ((i & (1 << s)) == 0) continue;
				f[j][i] = min(f[j][i], f[s][i ^ (1 << j)] + dis[s][sp[j]]);
//				cout << "dis: " << s << " " << sp[j] << " " << dis[s][sp[j]] << "\n";
//				cout << j << " " << i << " " << s << " " << (i ^ (1 << j)) << " " << f[j][i] << "\n";
			} 
		}
	}
	
	if (f[k][(1 << k + 1) - 1] < 0x3f3f3f3f3f3f3f3f) write(f[k][(1 << k + 1) - 1]), putchar('\n');
	else puts("-1");
}

}

int main() {
	steven24::main();
	return 0;
}
/*
3 3 2 1 1
1 2 1
2 3 1
3 1 1
2
3
*/

G.小绿小蓝

image
image

发现如果经过的点集固定 只需要最大化时间即可
所以直接跑哈密顿路求当前所有点经过情况为 \(S\) 的最长时间 最后枚举经过的点集统计答案即可

  • 注意去重边
  • 邻接表存图注意没有边连接的时候不能转移
点击查看代码
#include <bits/stdc++.h>
#define ll long long
using namespace std;

inline int read() {
	int xr = 0, F = 1;
	char cr;
	while (cr = getchar(), cr < '0' || cr > '9') if (cr == '-') F = -1;
	while (cr >= '0' && cr <= '9')
		xr = (xr << 3) + (xr << 1) + (cr ^ 48), cr = getchar();
	return xr * F; 
}

void write(ll x) {
	char ws[51];
	int wt = 0;
	if (x < 0) putchar('-'), x = -x;
	do {
		ws[++wt] = x % 10 + '0';
		x /= 10;
	} while (x);
	for (int i = wt; i; --i) putchar(ws[i]);
}

namespace steven24 {
	
const int N = 2e5 + 0721;
const int M = 521;
const ll inf = 0x3f3f3f3f3f3f3f3f;
ll f[20][N];
int a[20];
int mp[20][20];
int n, m, S, T;

int calc(int s) {
	int ret = 0;
	for (int i = 1; i <= n; ++i) if (s & (1 << i - 1)) ret += a[i];
//	cout << s << " " << ret 
	return ret;
}

void main() {
	n = read(), m = read();
	for (int i = 1; i <= n; ++i) a[i] = read();
	for (int i = 1; i <= m; ++i) {
		int x = read(), y = read(), z = read();
		mp[x][y] = max(mp[x][y], z);
	}
	S = read(), T = read();
	memset(f, -0x3f, sizeof f);
	f[S][(1 << S - 1)] = 0;
	for (int i = 0; i < (1 << n); ++i) {
		for (int j = 1; j <= n; ++j) {
			if (!(i & (1 << j - 1))) continue;
			for (int k = 1; k <= n; ++k) {
				if (j == k || !(i & (1 << k - 1))) continue;
				if (!mp[k][j]) continue;
				f[j][i] = max(f[j][i], f[k][i ^ (1 << j - 1)] + mp[k][j]);
			}
		}
	}
	
	double ans = 1e17;
	for (int i = 0; i < (1 << n); ++i) {
		if (f[T][i] <= 0) continue;
//		cout << ans << "\n";
		ans = min(ans, 1.0 * calc(i) / f[T][i]);
//		cout << i << " " << f[T][i] << "\n";
	}
	printf("%.3lf", ans);
}
	
}

int main() {
	steven24::main();
	return 0;
}
/*
7 9
3 0 6 9 1 0 10
1 6 5
6 2 3
3 2 2
2 4 4
4 3 9
3 5 1
4 5 3
5 6 2
5 1 7
2 6
*/

H.擦除序列

image

不难相当设 \(f_S\) 表示剩余字符集为 \(S\) 的最小步数
转移枚举子集显然
发现一个状态可能被判多次回文 所以判回文的时候记录一下保证不会TLE

点击查看代码
#include <bits/stdc++.h>
#define ll long long
using namespace std;

inline int read() {
	int xr = 0, F = 1;
	char cr;
	while (cr = getchar(), cr < '0' || cr > '9') if (cr == '-') F = -1;
	while (cr >= '0' && cr <= '9')
		xr = (xr << 1) + (xr << 3) + (cr ^ 48), cr = getchar();
	return xr * F;
}

namespace steven24 {
	
const int N = 1e5 + 0721;
ll f[N];
int vis[N]; 
char tmp[21];
string s;
int n;

bool check(int S) {
	if (vis[S] != -1) return vis[S];
	int cnt = 0;
	for (int i = 0; i < n; ++i) {
		if (S & (1 << i)) tmp[++cnt] = s[i];
	}
//	for (int i = 1; i <= cnt; ++i) cerr << tmp[i];
//	cerr << "\n";
	for (int i = 1, j = cnt; i <= j; ++i, --j) {
		if (tmp[i] != tmp[j]) return vis[S] = 0;
	}
	return vis[S] = 1;
}

void main() {
	memset(vis, -1, sizeof vis);
	cin >> s;
	n = s.length();
	memset(f, 0x3f, sizeof f);
	f[(1 << n) - 1] = 0;
	for (int i = (1 << n) - 1; i; --i) {
		for (int j = i; j; j = (j - 1) & i) {
			if (check(j)) f[i ^ j] = min(f[i ^ j], f[i] + 1);
		}
	}
//	for (int i = 0; i < (1 << n); ++i) cerr << i << " " << vis[i] << "\n";
	printf("%lld\n", f[0]);
}	
	
}

int main() {
	steven24::main();
	return 0;
}
posted @ 2023-07-03 13:42  Steven24  阅读(38)  评论(0编辑  收藏  举报