ccf-csp算法学习(2-状态压缩dp)

互不侵犯

基本思想:是用f[i][j][k]表示当前i行的状态,j表示当前这一行的棋子状态,k表现棋盘目前全部的棋子。因此我们可以建立状态关系f[i][j][k]=f[i-1][x][k-num[j]]。并且我们要注意,一开始对一列状态的初始化也十分重要,dfs过去,对能成立的状态进行存储。并且这题要注意用long long存储(在这个地方死了好几次了)。代码如下

#include <iostream>
using namespace std;
const int N = 9;
const int M = 512;
long long state[M], number[M];
long long f[15][M][90];
int n, m;
int cnt;
void dfs(int x, int num, int
         cur) { //x是当前状态,num是当前状态的棋子个数,cur是指针,但是是从0-n-1
    if (cur >= n) {
        state[cnt] = x;
        number[cnt++] = num;
        return;
    }

    dfs(x, num, cur + 1);//当前位置不放国王,所以状态和数字都不会改变
    dfs(x + (1 << cur), num + 1, cur + 2);//当前位置放国王
}
bool compatible(int x, int y) {
    if (state[x] & state[y])
        return false;

    if ((state[x] >> 1) & state[y])
        return false;

    if (state[x] & (state[y] >> 1))
        return false;

    return true;
}
int main() {
    ios::sync_with_stdio(false);
    cin.tie(0);
    cout.tie(0);
    cin >> n >> m;
    dfs(0, 0, 0);

    for (int i = 0; i < cnt; i++)
        f[1][state[i]][number[i]] = 1;

    for (int i = 2; i <= n; i++)
        for (int j = 0; j < cnt; j++)
            for (int t = 0; t < cnt; t++) {
                if (compatible(j, t)) {
                    for (int k = number[j]; k <= m; k++)
                        f[i][state[j]][k] += f[i - 1][state[t]][k - number[j]];
                }
            }

    long long ans = 0;

    for (int i = 0; i < cnt; i++)
        ans += f[n][state[i]][m];

    cout << ans << endl;
    return 0;
}

PRZ(雪崩过桥)

基本思想:因为是不同时间过桥的,所以其实这里的dp数组只需要一维就可以了。然后学习到了一些技巧和问题。首先是这里的const int N=1<<n,必须要在输入n之后,再初始化,不然会出现问题。其次这题也需要对一开始的状态初始化,对每个状态进行求解时间和重量。在进入最后的判断阶段的时候。先初始化一个dp数组。这里的dp数组包括ts,ws数组都是vector动态创建的,然后遍历每一个状态,如果当前状态重量小于桥重量,就可以初始化dp为此时的时间。接着这里用了一个很方便的循环,令j=i;j;j=i&(j-1).这样就能遍历i的所有子集,牛逼。i^j其实就相当于集合i-集合j。

if (ws[i ^ j] <= W)dp[i] = min(dp[i], dp[j] + ts[i ^ j]);//i^j is i-j
#include<iostream>
#include<vector>
using namespace std;
int t,w;
int W, n;
//const int N = 1 << n;
int main()
{
	ios::sync_with_stdio(false);
	cin.tie(0);
	cout.tie(0);
	cin >> W >> n;
	const int N = 1 << n;
	vector<int> ts(N + 1), ws(N + 1);
	for (int i = 0; i < n; i++)
	{
		cin >> t >> w;
		for (int j = 0; j < N; j++)
		{
			if (j & (1 << i))
			{
				ts[j] = max(ts[j], t);
				ws[j] += w;
			}
		}
	}
	
	vector<int> dp(N + 1, 0x3f3f3f3f);
	for (int i = 0; i < N; i++)
	{
		if (ws[i] <= W)dp[i] = ts[i];
		for (int j = i; j; j = i & (j - 1))//list all condition
			if (ws[i ^ j] <= W)dp[i] = min(dp[i], dp[j] + ts[i ^ j]);//i^j is i-j
	}
	cout << dp[N - 1] << endl;
	return 0;

}

 复习的时候,也可以参考acwing基础课中 蒙德里安的梦想最短hamilton路径

玉米田 也是自己做出来了。但卡了半小时bug,原因是dp[i][j],这里的j表示状态,但我直接用索引加上去了。代码如下

#include<iostream>
#include<vector>
#include<cstring>
using namespace std;
const int M = 13;
const int mod = 1e8;
int soil[M][M];
int n, m;
int dp[M][1 << M];
vector<int>state[M];
bool compatible(int x, int y)
{
	if (x & y)return false;
	return true;
}
void dfs(int hang,int x,int point)
{
	if (point >= m)
	{
		state[hang].emplace_back(x);
		return;
	}
	if(soil[hang][point]==1) dfs(hang, x + (1 << point), point + 2);
	dfs(hang, x, point + 1);
}
void work()
{
	memset(soil, 0, sizeof soil);
	cin >> n >> m;
	for (int i = 0; i < n; i++)
		for (int j = 0; j < m; j++)cin >> soil[i][j];
	for (int i = 0; i < n; i++)dfs(i, 0, 0);
	for (int i = 0; i < state[0].size(); i++)dp[0][state[0][i]] = 1;
	for (int i = 1; i < n; i++)
		for (int j = 0; j < state[i].size(); j++)
		{
			int cnt = 0;
			for (int k = 0; k < state[i - 1].size(); k++)
				if (compatible(state[i][j], state[i - 1][k]))
				{
					dp[i][state[i][j]] += dp[i - 1][state[i-1][k]];
					//cout << cnt++ << " ";
				}
		}
	//cout << dp[1][0] <<" "<<dp[1][1]<< endl;
	// for (int i = 0; i < state[0].size(); i++)cout << dp[0][state[0][i]] << " ";
	int ans = 0;
	for (int i = 0; i < state[n - 1].size(); i++)ans =(ans+ dp[n - 1][state[n - 1][i]])%mod;
	cout << ans<<endl;
	//cout << state[0].size() << " ";
	//cout << state[1].size() << " " << state[1][1];
	return;
}
int main()
{
	ios::sync_with_stdio(0);
	cin.tie(0);
	cout.tie(0);
	work();
	return 0;
}

炮兵阵地 这道题不能用之前那种想法做了。之前的互不侵犯和玉米田,都可以基于上一层的状态来转移。但这个必须基于两层状态来转移。同时因为三层数组空间开太大,所以要使用转移数组。

代码如下

#include<iostream>
#include<vector>
using namespace std;
const int N = 101;
const int M = 10;
int n, m;
int f[2][1 << M][1 << M];
int g[N], cnt[1<<M];
vector<int> state;
vector<int> head[1<<M];
bool check(int x)
{
	return !(x & (x >> 1) || x & (x >> 2));
}
int count(int x)
{
	int ans = 0;
	while (x)
	{
		ans += x & 1;
		x >>= 1;
	}
	return ans;
}
void work()
{
	cin >> n >> m;
	vector<vector<char>> place(n, vector<char>(m));
	for (int i = 1; i <= n; i++)
		for (int j = 0; j < m; j++)
		{
			char c;
			cin >> c;
			if (c == 'H')g[i] += 1 << j;
		}
	for (int i = 0; i < (1 << m); i++)
	{
		if (check(i))state.emplace_back(i);
		cnt[i] = count(i);
	}
	for (int cur_st : state)
		for (int pre_st : state)
			if (!(cur_st & pre_st))head[cur_st].emplace_back(pre_st);
	for (int i = 1; i <= n; i++)
		for (int st : state)
			if (!(g[i] & st))
				for (int p1 : head[st])
					for (int p2 : head[p1])
						if (!(st & p2))
							f[i & 1][st][p1] = max(f[i & 1][st][p1], f[(i - 1) & 1][p1][p2]+cnt[st]);
	int res = 0;
	for (int st : state)
		for (int pre : head[st])
			res = max(res, f[n & 1][st][pre]);
	cout << res;


}
int main()
{
	ios::sync_with_stdio(0);
	cin.tie(0);
	cout.tie(0);
	work();
	return 0;
}

愤怒的小鸟 看题解说可以用dancinglink来做,上一次接触这个还是大一哎。但是因为小鸟的个数比较少<=18,所以可以用状压dp来做。用一维dp来存储。但是要先预处理抛物线,并且记录当前抛物线的状态。随后一层层遍历即可。对于每一个当前状态,更新的办法就是,找到当前状态中不存在的点,然后遍历这个点所有的抛物线来加到当前状态之上,然后判断。代码如下

小知识:浮点数无法准确表示,所以判断相等要用1e-8.绝对值要用fabs而不是abs

#include<iostream>
#include<cstring>
#include<cmath>
#include<algorithm>
using namespace std;
typedef pair<double, double> PDD;
const int N = 18, M = 1 << 18;
const double eps = 1e-8;
int n, m;
PDD q[N];
int path[N][N];
int f[M];
int cmp(double x, double y)
{
	if (fabs(x - y) < eps)return 0;
	if (x < y)return -1;
	return 1;
}
void work()
{
	int T;
	cin >> T;
	while (T--)
	{
		cin >> n >> m;
		for (int i = 0; i < n; i++)cin >> q[i].first >> q[i].second;
		memset(path, 0, sizeof path);
		for (int i = 0; i < n; i++)
		{
			path[i][i] = 1 << i;
			for (int j = 0; j < n; j++)
			{
				double x1 = q[i].first, y1 = q[i].second;
				double x2 = q[j].first, y2 = q[j].second;
				if (!cmp(x1, x2))continue;
				double a = (y1 / x1 - y2 / x2) / (x1 - x2);
				double b = y1 / x1 - a * x1;
				if (cmp(a, 0) >= 0)continue;
				int state = 0;
				for (int k = 0; k < n; k++)
				{
					double x = q[k].first, y = q[k].second;
					if (!cmp(a * x * x + b * x, y))state += 1 << k;
				}
				path[i][j] = state;
			}
		}
		memset(f, 0x3f, sizeof f);
		f[0] = 0;
		for (int i = 0; i < (1 << n) - 1; i++)
		{
			int x = 0;
			for(int j=0;j<n;j++)
				if (!(i >> j & 1))
				{
					x = j;
					break;
				}
			for (int j = 0; j < n; j++)
				f[i | path[x][j]] = min(f[i | path[x][j]], f[i] + 1);
		}
		cout << f[(1 << n) - 1] << endl;
	}
}
int main()
{
	ios::sync_with_stdio(0);
	cin.tie(0);
	cout.tie(0);
	work();
	return 0;
}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值