树形Dp+区间Dp+数位Dp+状压Dp

P2014 [CTSC1997] 选课

https://www.luogu.com.cn/problem/P2014

题意

给m个课 第i行 输入x w 代表选i个课一定要选第x个课 修第i个课可以得到w个学分
问选修m个课最多可以选修多少学分

思路

相当于树上背包
容量为m
先根据输入建一棵树 然后用dfs深搜从叶节点转移上去
dp首先初始化为0

dp[fa][j]=max(dp[fa][j],dp[to][j1]+score[to])

树的根节点为0 即空
因为树已经有先后关系了 前一个节点若要从后一个节点转移肯定是要空余一个给父节点 即 dp[fa][m]一定某个由子节点dp[to][m - 1]转移过来的 因为一开始为0 很小
所以树本身就已经保证了先后关系

#include<iomanip>
#include<bits/stdc++.h>
#include<iostream> 
#include<bitset>
#include<algorithm>
#include<cstring>
#include<cmath>
#include<map>
#include<vector>
#define ll long long
#include<unordered_map>
#define ull unsigned long long
#define IOS ios::sync_with_stdio(false);cin.tie(0);cout.tie(0);
using namespace std;
const ll inf = 0x3f3f3f3f;
const ll INF = 0x3f3f3f3f3f3f3f3f;
const double eps = 1e-10;
const ll N = 2e5 + 5;
const int M = 1e6 + 5;
const ll mod = 2147493647;
ll n, m, score[N], dp[305][305];
vector<ll>g[N];

void dfs(ll x) {
	for (auto to : g[x]) {
		dfs(to);
		for (int i = m; i >= 1; i--) {
                        //子节点已经占用空间 必须j <= i - 1 因为要留一位给父节点 这样保证选子节点 父节点一定选
			for (int j = 0; j <= i - 1; j++) {
				dp[x][i] = max(dp[x][i], dp[x][i - j - 1] + dp[to][j] + score[to]);
			}
		}
	}
}

void solve() {
	cin >> n >> m;
	ll x, w;
	for (int i = 1; i <= n; i++) {
		cin >> x >> w;
		g[x].push_back(i);
		score[i] = w;
	}
	dfs(0);
	cout << dp[0][m] << "\n";
} 


signed main() {
	IOS;
	int t = 1;
	//cin >> t;
	while (t--) {
		solve();
	}

}

B - “访问”美术馆

https://www.luogu.com.cn/problem/P1270

题意

小偷要去博物馆偷画 深度优先的次序给出走一条通道的时间w[i] 和画室有几幅画v[i],如果v[i]==0说明是分叉路 再分成两条通道
规定小偷偷一幅画要花费5分钟
给出警察来的时间m 求小偷最多能偷几幅画

思路

小偷能偷走画 说明花费的时间比警察来的少 且成功返回出口所以一条通道要么不走 要么走两边
可以看成一个树形背包问题
容量是m-1 物品的价值是v[i] 体积是w[i]*2
又 输入是按深度优先给出 所以我们要用dfs实时输入值 处理
经分析 这个肯定是一颗二叉树 如果点权是0就会再分出两条路 如果不是零直接新建一个点不会再分一条路
所以我们只要在点权是0 的时候再递归两次Dfs这样就可以成功存图了
然后就可以进行树形dp了 父亲结点有其所有儿子转移过来

dp[fa][i]=max(dp[fa][i],dp[now][ijk5w[to]]+dp[to][j]+k)

其中dp[i][j]代表到当前节点已经花费了i分钟 k代表小偷再相应画室偷了几幅画
再分析 第一条路是肯定要走的不管它是通到一个画室还是一个转弯口 所以可以把背包的大小缩小到m-w[1]*2
这样可以避免根节点转移不到了(我的写法是这样的 有些做法不用这样)
注意数组范围 题目说是画室是100以内但通到可以有很多 所以至少开到1000

#include<iomanip>
#include<bits/stdc++.h>
#include<iostream> 
#include<bitset>
#include<algorithm>
#include<cstring>
#include<cmath>
#include<map>
#include<vector>
#define ll long long
#include<unordered_map>
#define ull unsigned long long
#define IOS ios::sync_with_stdio(false);cin.tie(0);cout.tie(0);
using namespace std;
const ll inf = 0x3f3f3f3f;
const ll INF = 0x3f3f3f3f3f3f3f3f;
const double eps = 1e-10;
const ll N = 2e5 + 5;
const int M = 1e6 + 5;
const ll mod = 2147493647;
ll n, m, w[1000], v[1000], dp[1005][1005];
vector<ll>g[N];
ll tot;

void dfs(ll fa) {
	cin >> w[++tot] >> v[tot];
        //来回时间
	w[tot] = 2 * w[tot];
	if (fa == 0) m -= w[tot];
	g[fa].push_back(tot);
	ll now = tot;
	if (!v[now]) {
		dfs(now);
		dfs(now);
		for (auto to : g[now]) {
			for (int i = m; i >= 1; i--) {
				for (int j = 0; j <= i - w[to]; j++) {
                                        //小偷偷了几幅画
					for (int k = 0; k <= v[to]; k++) {
						if(i - j - k * 5 - w[to] >= 0) dp[now][i] = max(dp[now][i], dp[now][i - j - k * 5 - w[to]] + dp[to][j] + k);
					}
				}
			}
		}
	}

    
	return;
}


void solve() {
	cin >> m;
        //开始减1 不然就撞见警察了
	m--;
	dfs(0);
	cout << dp[1][m] << "\n";
}


signed main() {
	IOS;
	int t = 1;
	//cin >> t;
	while (t--) {
		solve();
	}

}

C - Choosing Capital for Treeland

<CodeForces - 219D>

题意

给出几个点和几条单方向边 求以哪个点为根节点时反向边最少

思路

换根 将有向边变成无向边 然后记录存在的那个方向的边 先以任何点跑一遍dfs求得以每个点为根的子树的正向边数(也可以计算反向边都一样)
然后用另一个dfs2换根 用ans[i]代表以i点为根的最多正向边
对于父节点fa 和子节点son ans[son] = ans[fa] + (edgefa>存在 ? 1 : -1);
最后遍历ans数组求最大 然后把符合方案按顺序输出即可

#include<iomanip>
#include<bits/stdc++.h>
#include<iostream> 
#include<bitset>
#include<algorithm>
#include<cstring>
#include<cmath>
#include<map>
#include<vector>
#define ll long long
#include<unordered_map>
#define ull unsigned long long
#define IOS ios::sync_with_stdio(false);cin.tie(0);cout.tie(0);
using namespace std;
const ll inf = 0x3f3f3f3f;
const ll INF = 0x3f3f3f3f3f3f3f3f;
const double eps = 1e-10;
const ll N = 2e5 + 5;
const int M = 1e6 + 5;
const ll mod = 2147493647;
ll n, in[N], cnt[N], ans[N];
map<pair<ll, ll>, ll>mp;
vector<ll>g[N], v;

void dfs(ll x, ll fa) {
	for (auto to : g[x]) {
		if (to == fa) continue;
		dfs(to, x);
		cnt[x] += cnt[to];
		if (mp[{x, to}] == 1)
		    cnt[x]++;
	}
}
//换根
void dfs2(ll x, ll fa) {
	if (x != 1) ans[x] = ans[fa] + mp[{x, fa}];
	for (auto to : g[x]) {
		if (to == fa) continue;
		dfs2(to, x);
	}
}


void solve() {
	cin >> n;
	ll x, y;
	for (int i = 1; i < n; i++) {
		cin >> x >> y;
                //mp[{x, y}] = 1表示x 到y是正向
		mp[{x, y}] = 1;
                //mp[{y, x}] = 1表示y 到x是反向
		mp[{y, x}] = -1;
		g[x].push_back(y);
		g[y].push_back(x);
	}
	dfs(1, 0);
	ans[1] = cnt[1];
	dfs2(1, 0);
	ll mx = 0;
	for (int i = 1; i <= n; i++) {
		if (ans[i] > mx) {
			mx = ans[i];
		}
	}
	for (int i = 1; i <= n; i++) {
		if (ans[i] == mx) {
			v.push_back(i);
		}
	}
	cout << n - 1 - mx << "\n";
	for (int i = 0; i < v.size(); i++) {
		cout << v[i] << " \n"[i == v.size() - 1];
	}
}


signed main() {
	IOS;
	int t = 1;
	//cin >> t;
	while (t--) {
		solve();
	}

}

P1063 [NOIP2006 提高组] 能量项链

https://www.luogu.com.cn/problem/P1063

题意

给出n个数 a[i]代表第i棵珠子的头 a[i + 1]第i棵珠子的尾和第a[i+1]棵珠子的头 珠子是围成一圈的
只能合并相邻两颗珠子 合并两颗珠子 释放l * mid * r的能量 其中mid前一颗的尾且是后一颗的头
求合并成一颗珠子后的最多能量

思路

二倍区间dp
因为是一个圈 就可以变成一个二倍长的链 在长链中找到最优的一倍长链即可
转移方程:

dp[l][r]=max(dp[l][r],dp[l][k]+dp[k+1][r]+a[l]a[k+1]a[r+1])

#include<iomanip>
#include<bits/stdc++.h>
#include<iostream> 
#include<bitset>
#include<algorithm>
#include<cstring>
#include<cmath>
#include<map>
#include<vector>
#define ll long long
#include<unordered_map>
#define ull unsigned long long
#define IOS ios::sync_with_stdio(false);cin.tie(0);cout.tie(0);
using namespace std;
const ll inf = 0x3f3f3f3f;
const ll INF = 0x3f3f3f3f3f3f3f3f;
const double eps = 1e-10;
const ll N = 2e2 + 5;
const int M = 1e6 + 5;
const ll mod = 2147493647;
ll a[N], dp[N][N];
ll n;

void solve() {
	cin >> n;
	for (int i = 1; i <= n; i++) {
		cin >> a[i];
	}
        //预处理将圈变成两倍长的链
	for (int i = 1 + n; i <= n * 2; i++) {
		a[i] = a[i - n];
	}
        //len代表选几个珠子 每个a[i]默认代表第i个珠子
	for (int len = 2; len <= n; len++) {
		for (int l = 1; l + len - 1 <= 2 * n; l++) {
			ll r = len + l - 1;
			for (ll k = l; k < r; k++) {
                                //因为珠子的长度肯定比珠子的个数多1 所以后一颗珠子的尾部就是a[r+1]
				dp[l][r] = max(dp[l][r], dp[l][k] + dp[k + 1][r] + a[l] * a[k + 1] * a[r + 1]);
			}
		}
	}
	ll mx = 0;
        //取最优的一倍长链
	for (int i = 1; i <= n; i++) {
		mx = max(mx, dp[i][i + n - 1]);
	}
	cout << mx << "\n";
} 


signed main() {
	IOS;
	int t = 1;
	//cin >> t;
	while (t--) {
		solve();
	}

}

E - Brackets

<POJ - 2955 >

题意

给出一个字符串 求合法子序列
其中类似()[]、[[]]、[()]的字符串是合法的

思路

区间字符串dp
dp方程:
dp[l][r]:区间l到r的合法子序列有多少
如果s[l]和s[r]是左右匹配的:dp[l][r] = max(dp[l][r], dp[l + 1][r - 1] + 2);
dp[l][r] = max(dp[l][r], dp[l][k] + dp[k + 1][r]);

#include<iomanip>
#include<iostream> 
#include<bitset>
#include<algorithm>
#include<cstring>
#include<cmath>
#include<map>
#include<vector>
#define ll long long
#define ull unsigned long long
#define IOS ios::sync_with_stdio(false);cin.tie(0);cout.tie(0);
using namespace std;
const ll inf = 0x3f3f3f3f;
const ll INF = 0x3f3f3f3f3f3f3f3f;
const double eps = 1e-10;
const ll N = 2e5 + 4;
const int M = 1e6 + 5;
const ll mod = 2147493647;
ll dp[105][105];
string s;
map<char, ll>mp;

void solve() {
	while (cin >> s)
	{
		if (s == "end") break;
		memset(dp, 0, sizeof dp);
		for (int len = 2; len <= s.size(); len++) {
			for (int l = 0; l + len - 1 < s.size(); l++) {
				ll r = l + len - 1;
                                //l, r相邻时要特判 不然先后顺序就反了
				if (l + 1 == r && mp[s[l]] < 0 && mp[s[l]] + mp[s[r]] == 0) {
					dp[l][r] = 2;
					continue;
				}
				if (mp[s[l]] < 0 && mp[s[l]] + mp[s[r]] == 0) dp[l][r] = max(dp[l][r], dp[l + 1][r - 1] + 2);
				for (int k = l; k < r; k++) {
					dp[l][r] = max(dp[l][r], dp[l][k] + dp[k + 1][r]);
				}
			}
		}
		cout << dp[0][s. size() - 1] << "\n";
	}
}


signed main() {
	IOS;
	int t = 1;
	//cin >> t;
        //预处理
	mp['['] = -1, mp[']'] = 1;
	mp['('] = -2, mp[')'] = 2;
	while (t--) {
		solve();
	}

}

F - 不要62

题意

给你n, m两个数求n 到 m之间不含4和62(必须连续相邻)的数的个数

思路

数位dp 记忆化搜索(这题数据不大也可以不记忆化)

#include<iomanip>
#include<iostream> 
#include<bitset>
#include<algorithm>
#include<cstring>
#include<cmath>
#include<map>
#include<vector>
#define ll long long
#define ull unsigned long long
#define IOS ios::sync_with_stdio(false);cin.tie(0);cout.tie(0);
using namespace std;
const ll inf = 0x3f3f3f3f;
const ll INF = 0x3f3f3f3f3f3f3f3f;
const double eps = 1e-10;
const ll N = 2e5 + 5;
const int M = 1e6 + 5;
const ll mod = 2147493647;
string s1, s2;
ll dp[20][20], a[20];

//pos:当前在第几位 pre:上一位的数是几 flag:是否紧贴前一个数 就是说前面的为上都是最大数
ll dfs(ll pos, ll pre, bool flag) {
         //代表当前有一个解
	if (pos <= 0) return 1;
         //如果不是该位最大数 且已经被访问过 直接返回
	if (!flag && pre > 0 && dp[pos][pre] != -1) return dp[pos][pre];
	ll mx;
        //确定该位最大值如果前面已经紧贴即已取最大值那么该位最大只能是a[pos]否则可取到9
	if (flag) mx = a[pos];
	else mx = 9;
	ll res = 0;
	for (int i = 0; i <= mx; i++) {
        //已经不符合了就不用在递归下去了
        if (i == 4 || (pre == 6 && i == 2)) continue;
		res += dfs(pos - 1, i, flag && (i == mx));
	}
	//存下来用于记忆化
	if (!flag) dp[pos][pre] = res;
	return res;
}

void solve() {
	while (cin >> s1 >> s2) {
		if (s1 == "0" && s2 == "0") break;
		memset(dp, -1, sizeof dp);
	    for (int i = 0; i < s1.size(); i++) {
			a[i + 1] = s1[s1.size()- 1 - i] - '0';
		}
		ll num1 = dfs(s1.size(), 0, 1);
		memset(a, 0, sizeof a);
		memset(dp, -1, sizeof dp);
		for (int i = 0; i < s2.size(); i++) {
			a[i + 1] = s2[s2.size() - 1 - i] - '0';
		}
		ll num2 = dfs(s2.size(), 0, 1);
		/*for (int i = 1; i <= 3; i++) {
			for (int j = 0; j <= 9; j++) {
				cout << i << " " << j << " " << dp[i][j] << "\n";
			}
		}*/
                //判断第一个数是否符合 如果符合了会多减一个那就重新加回来
		if (s1.find('4') == s1.npos && s1.find("62") == s1.npos) num1--;
		cout << num2 - num1 << "\n";
	}
	
}


signed main() {
	IOS;
	int t = 1;
	while (t--) {
		solve();
	}

}

D - A sample Hamilton path

题意

一共n个城市
后n行 没行n个数(n <= 21)
第i行 第j个数代表 第i个城市到第j个城市的路径长度 -1 代表没有路 如果i == j 一定是-1;
接下来m行 两个数 a b
代表到b一定要经过a
求最短哈密顿路径

思路

因为n比较小 2的20最多1e6
可以用状压dp 二进制01串表示状态 第i位为0 代表第i个城市还没有走过 1 代表第i个城市走过了
从0到11111 枚举状态 判断当前状态以i为最后走的城市 以j为最后走的城市的上一个状态转移(其中上一个状态的i城市未走过)
&& dp[s][i] = min(dp[s][i], dp[s ^ (1 << i)][j] + a[j][i])&&
用c[i]储存第i个城市之前必须走的城市的二进制和 后面对于一个状态 判断是否合法
还要注意城市是从0开始还是从1开始的

#include<iomanip>
#include<bits/stdc++.h>
#include<iostream> 
#include<bitset>
#include<algorithm>
#include<cstring>
#include<cmath>
#include<map>
#include<vector>
#define ll int
#include<unordered_map>
#define ull unsigned long long
#define IOS ios::sync_with_stdio(false);cin.tie(0);cout.tie(0);
using namespace std;
const ll inf = 0x3f3f3f3f;
const ll INF = 0x3f3f3f3f3f3f3f3f;
const double eps = 1e-10;
const ll N = 3e6 + 5;
const int M = 1e6 + 5;
const ll mod = 2147493647;
ll n, m;
string s;
//c数组装走到i城市一定要走的城市编号和
ll a[25][25], c[1 << 22];
vector<ll>g[25];
//dp[i][j]代表 状态i最后走到j城市
ll dp[1<<22][25];

void solve() {
	while (cin >> n >> m) {
	    for (int i = 0; i < n; i++) {
			for (int j = 0; j < n; j++) {
				cin >> a[i][j];
                                //没有边 直接将路径设为无穷
				if (a[i][j] < 0) a[i][j] = inf;
			}
		}
                // 初始化
		for (int i = 0; i < (1 << n); i++) {
			c[i] = 0;
			for (int j = 0; j < n; j++) {
				dp[i][j] = inf;
			}
		}
                //起点为i城市 花费0
		for (int i = 0; i < n; i++) {
			dp[1 << i][0] = 0;
		}
		ll x, y;
		for (int i = 0; i < m; i++) {
			cin >> x >> y;
			c[y] += (1 << x);
		}
	
		for (ll s = 1; s < (1 << n); s++) {
			for (int i = 0; i < n; i++) {
				if (s & (1 << i)) {
                                        //判断是否走过必走城市
					if ((s & c[i]) != c[i]) break;
					for (int j = 0; j < n; j++) {
						if (i != j && (s & (1 << j))) {
							dp[s][i] = min(dp[s][i], dp[s ^ (1 << i)][j] + a[j][i]);
						}
					}
				}
			}
		}
		ll ans = inf;
		for (int i = 0; i < n; i++) {
			ans = min(ans, dp[(1 << n) - 1][i]);
		}
		if (ans == inf) cout << -1 << '\n';
		else cout << ans << "\n";
	}
	
}


signed main() {
	IOS;
	int t = 1;
	//cin >> t;
	while (t--) {
		solve();
	}

}

F - 互不侵犯

题意

在 N×N 的棋盘里面放 KK 个国王,使他们互不攻击,共有多少种摆放方案。国王能攻击到它上下左右,以及左上左下右上右下八个方向上附近的各一个格子,共 8 个格子。

思路

状压dp
现予处理一行的每种情况是否合法 将所有合法的储存下来(预处理的时候 每种状态 如果((s << 1) & s == 0)说明就是合法的)
然后下一行从前一行转移 进行dp

dp[i][j][s]+=dp[i][jsz[s]][k]

第一位代表前几行 第二维代表已经存在多少个国王 第三位代表第i行的状态
判断两行是否能同时存在 可以判断 s & k、 (s<<1)&k、 s & (k << 1)如果三个都为0则为合法

#include<iomanip>
#include<bits/stdc++.h>
#include<iostream> 
#include<bitset>
#include<algorithm>
#include<cstring>
#include<cmath>
#include<map>
#include<vector>
#define ll long long
#include<unordered_map>
#define ull unsigned long long
#define IOS ios::sync_with_stdio(false);cin.tie(0);cout.tie(0);
using namespace std;
const ll inf = 0x3f3f3f3f;
const ll INF = 0x3f3f3f3f3f3f3f3f;
const double eps = 1e-10;
const ll N = 3e6 + 5;
const int M = 1e6 + 5;
const ll mod = 2147493647;
ll n, m, k;
string s;
ll a[1 << 10], sz[1 << 10];
ll dp[20][100][1 << 10];

ll getone(ll x) {
	ll cnt = 0;
	for (int i = 0; (1 << i) <= x; i++) {
		if (x & (1 << i)) cnt++;
	}
	return cnt;
}

void solve() {
	cin >> n >> m;
	ll tot = 0;
	for (int i = 0; i <= (1 << n) - 1; i++) {
		sz[i] = getone(i);
		if (i & (i << 1)) continue;
		a[++tot] = i;
	}
	dp[0][0][1] = 1;//前0行 一个都没装 状态就是0即第一个状态 这个是1这样才能往后转移
	for (int i = 1; i <= n; i++) {
		for (int j = 0; j <= m; j++) {
			for (int k = 1; k <= tot; k++) {
				for (int s = 1; s <= tot; s++) {
					if (a[k] & a[s] || (a[k] << 1) & a[s] || (a[s] << 1) & a[k])
						continue;
					if(j - sz[a[k]] >= 0) dp[i][j][k] += dp[i - 1][j - sz[a[k]]][s];
				}
			}
		}
	}
	ll ans = 0;
	for (int i = 1; i <= tot; i++) {
		ans += dp[n][m][i];
	}
	cout << ans << "\n";
}


signed main() {
	IOS;
	int t = 1;
	//cin >> t;
	while (t--) {
		solve();
	}

}

AC Challenge

题意

给出每道题的a b值 以及做这道题之前一定要做的题
每道题的得分是 a * t + b其中t代表是第几分钟做的这道题
让你算做大得分 其中可以不做完所有题目

思路

状压dp
可以开一维dp 因为不需要再额外记录前面一道做了啥 s状态已经包含了信息
与上有一题类似 先开个c数组记入一道题之前必须做的所有题的二进制和
后续直接进行状压转移 判断是否合法即可
这里要注意是从1开始编号的 所以再储存某题的必做题时要减1
该题是第几题 可以直接根据这个状态得出

dp[s]=max(dp[s(1<<i)]+a[i]get(s)+b[i],dp[s]);


#include<iostream> 
#define ll int
#define ull unsigned long long
#define IOS ios::sync_with_stdio(false);cin.tie(0);cout.tie(0);
using namespace std;
const ll inf = 0x3f3f3f3f;
const ll INF = 0x3f3f3f3f3f3f3f3f;
const double eps = 1e-10;
const ll N = 3e6 + 5;
const int M = 1e6 + 5;
const ll mod = 2147493647;
ll n, m, k;
string s;
ll a[20], b[20], c[1 << 20], dp[1 << 20];

inline ll get(ll x) {
	ll cnt = 0;
	while (x) {
		cnt += x & 1;
		x >>= 1;
	}
	return cnt;
}

void solve() {
	cin >> n;
	ll num, xx;
	for (int i = 0; i < n; i++) {
		cin >> a[i] >> b[i];
		cin >> num;
		for (int j = 1; j <= num; j++) {
			cin >> xx;
			c[i] += (1 << (xx - 1));
		}
	}   
        //不合法应设为无穷小 因为后续可能有合法的从不合法的转移过来就有值了 会不准确 所以初值都是无穷小
	for (int i = 0; i < (1 << n); i++) {
		for (int j = 0; j < n; j++) {
			dp[i] = -inf;
		}
	}
        //初始化
	for (int i = 0; i < n; i++) {
		if (!c[i])	
		    dp[1 << i] = a[i] + b[i];
	}
	ll ans = 0;
	for (int s = 0; s < (1 << n); s++) {
		for (int i = 0; i < n; i++) {
			if (s & (1 << i)) {
				if ((s & c[i]) != c[i]) continue;
				for (int j = 0; j < n; j++) {
					if (i != j && s & (1 << j)) {
						dp[s] = max(dp[s ^ (1 << i)] + a[i] * get(s) + b[i], dp[s]);
					}
				}
                ans = max(ans, dp[s]);
			}
			
		}
	}
	cout << ans << "\n";
}


signed main() {
	IOS;
	int t = 1;
	//cin >> t;
	while (t--) {
		solve();
	}

}
posted @   Yaqu  阅读(65)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· 分享一个免费、快速、无限量使用的满血 DeepSeek R1 模型,支持深度思考和联网搜索!
· 使用C#创建一个MCP客户端
· ollama系列1:轻松3步本地部署deepseek,普通电脑可用
· 基于 Docker 搭建 FRP 内网穿透开源项目(很简单哒)
· 按钮权限的设计及实现
点击右上角即可分享
微信分享提示