几道DP题目

牛客练习赛139C,25武汉邀请赛FG等

C-大卫的密码_牛客练习赛139

题意:

给定一个 \(n\times m\) 的网格图,我们使用 $(i,j)$ 表示网格中从上往下数第 $i$ 行和从左往右数第 $j$ 列的单元格。左上角为 $(1,1)$ ,右下角为 $(n,m)$ ,每个格子包含一个整数价值,使用 $a_{i,j}$ 表示。 一个光标在上面移动,从 $(s,1)$ 出发,每次可以向右或者向下移动,每个格子至多经过一次。当光标移动到 $(n,i) \left(1\le i\le m \right)$ 格子,也就是最后一行的某个格子时,继续向下移动则会到达 $(1,i)$ 格子。大卫需要移动光标到达 $(t,m)$ ,求最终大卫能获取的最大价值和。

题解:

题解

二维DP+限制。定义状态 $dp[i][j]$ 表示在 $(i,j)$ 位置时的最大价值和。

因为纵向是可以走到底然后从上面走下来的,所以我们考虑转移的时候从左往右一列一列的转移。

设 \(s_n=\sum_{i=1}^n a[i][j]\)​,则转移方程为 \(dp_{i,j} \leftarrow \max_{k} \left( dp_{k,j-1} + \begin{cases} s_i - s_{k-1} & \text{if } ~k < i \\ s_n - (s_{k-1} - s_i) & \text{if } ~k > i \end{cases} \right)\) 观察上面式子,如果在 \(k>i\) 的情况下,

因为这里面只有 $s_{k-1}$ 是不固定的,我们找到最小的 $k$ 进行转移就行了;两种对于从下转移和从上转移不同情况,对应 \(l\) 数组和 \(r\) 数组,分别使用DP求出对于 \(i\) 的最大值。

DP数组可以滚动。

#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
const ll N=5e3+10,M=1e6+10,inf=1e18+10;
ll Tt=1,n,m,s,t;
ll dp[2][N],a[N][N],l[N],r[N],sum[N];
void solve(){
	cin>>n>>m>>s>>t;
	for(int i=1;i<=n;i++) for(int j=1;j<=m;j++) cin>>a[i][j];
	for(int i=1;i<=n;i++) dp[0][i]=-inf;
	dp[0][s]=0;
	for(int j=1;j<=m;j++){
		int now=j&1,pre=(j-1)&1;
		for(int i=0;i<=n+1;i++){
			l[i]=r[i]=-inf,sum[i]=0;
			dp[now][i]=dp[pre][i],dp[now][i]=0;
		}
		for(int i=1;i<=n;i++) sum[i]=sum[i-1]+a[i][j];
		for(int i=1;i<=n;i++) l[i]=max(l[i-1],dp[pre][i]-sum[i-1]);
		for(int i=n;i>=1;i--) r[i]=max(r[i+1],dp[pre][i]+sum[n]-sum[i-1]);
		for(int i=1;i<=n;i++) dp[now][i]=max(l[i],r[i+1])+sum[i];
	}                                                         
	cout<<dp[m&1][t]<<"\n";
}
int main(){
	while(Tt--) solve();
	return 0;
}

P12593 沉石鱼惊旋 - 洛谷

题意:

有一张 \(n\) 个点 \(m\) 条边的简单无向带权连通图 \(G\)。可以进行 \(n\) 次操作,每次操作如下:

选择一个仍未被删除的点 \(u\),然后删除点 \(u\) 和当前与 \(u\) 相连的所有边(即其中一个端点是 \(u\) 的边)。假设本次删除的边的边权分别是 \(w_1, w_2,\dots w_k\),则本次操作的代价是 \(k\times (w_1+w_2+\dots+w_k)\) 。

你的总代价是这 \(n\) 次操作的代价和。将图中所有点和边都删除(即把图删空)的最小总代价是多少。

题解:

题解

状压DP。定义状态为 \(f[s]\) ​为已经删过点的集合为 \(s\) 时所能取得的最小代价,那么容易得出状态转移方程

\(f_S = \min_{\substack{1 \leq j \leq n \& j \notin S}} \left\{ f_{S \cup j} + \sum_{l \notin S} [(l,j)\in E] \sum_{l \notin S \& (l,j) \in E}{w_{l,j}} \right\}\) 边界条件是当 \(S={1,2,...,n}\) 时,\(f_S=0\) ,因为删完了就不需要再付出代价了。答案即为 \(f_\empty\),因为最开始的时候一个点也没删。

时间复杂度为 \(O(2^nn^2)\),可以通过 \(n ≤ 16\) 的范围。

#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
const ll inf=1e18;
ll n,m,g[10][10],dp[1<<10];
bool vis[1<<10];
ll f(ll x){
	if(vis[x]) return dp[x];//记忆化搜索
	if(x==(1<<n)-1) return 0;//全未删
	ll ans=1e18;
	for(int j=1;j<=n;j++){
		if(!(x&(1<<(j-1)))){//第j个点已经删除了
			ll k=0,v=0;
			for(int l=1;l<=n;l++){//枚举所有与j点相连的边
				if(!(x&(1<<(l-1)))&&g[j][l]>0){//第l个点也删了,且j和l有边
					k++,v+=g[j][l];//删的边数++,值+
				}
			}
			ans=min(ans,f(x|(1<<(j-1)))+k*v);
		}
	}
	vis[x]=1,dp[x]=ans;
	return ans;
}
int main(){
	cin>>n>>m;
	for(int i=1;i<=m;i++){
		int u,v,w;cin>>u>>v>>w;
		g[u][v]=g[v][u]=w;
	}
	cout<<f(0);
	return 0;
}

P12597 穿睡衣军训 - 洛谷

题意:

给定两个字符串 \(s,t\),扶苏想让你求出一个字符串 \(x\),满足:

  • \(x\) 是 \(s\) 的子串
  • \(x\) 是 \(t\) 的子序列
  • 在所有满足前述两条的字符串中,$x$ 的长度最长。
  • 在所有满足前述三条的字符串中,$x$ 的字典序最小。

请你帮她求出这样的字符串 $x$。

题解:

此题我使用枚举子串+DP判断子序列超时,时间复杂度太大。

可以 \(O(n^2)\) 枚举子串。写出 \(O(nlog m)\) 的判断子序列。

考虑优化,注意到判断子序列其实是可以继承于上一段的结果,于是我们少一个判断子序列花费的 n,此时复杂度来到 \(O(Tn^2logm)\) 。

#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
void solve(){
	string s,t;cin>>s>>t;
	int n=s.size(),m=t.size();
	s=" "+s,t=" "+t;
	vector<int> ve[35];
	for(int i=1;i<=m;i++) ve[t[i]-'a'].push_back(i);
	string ans;
	for(int i=1;i<=n&&n-i+1>=ans.size();i++){
		string flc;
		for(int j=i,x=-1;j<=n;j++){
			auto tmp=upper_bound(ve[s[j]-'a'].begin(),ve[s[j]-'a'].end(),x);
			if(tmp==ve[s[j]-'a'].end()) break;
			x=ve[s[j]-'a'][tmp-ve[s[j]-'a'].begin()];
			flc+=s[j];
		}
		if(flc.size()>ans.size()||flc.size()==ans.size()&&flc<ans) ans=flc;
	}
	cout<<ans<<'\n';
}
int main(){
	int T;cin>>T;
	while(T--) solve();
	return 0;
}

2025武汉邀请赛-F 背包

题意:

有 $n$ 组物品 ,第 $i$ 组物品有 $a_i$ 个,每个物品的重量为 $2^{b^i}$。

有 $m$ 个背包,每个背包的承重为 $k$。求最小的 $k$,使得所有 $\sum_{i=1}^n a_i$ 个物品都能被放入背包中,每个背包物品总重量不超过 $k$。同一组不同物品可放不同背包。

题解:

2025ICPC武汉邀请赛-F 题解

#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
const ll N=6e6+10,INF=4e18,p=998244353;
ll n,m;
vector<pair<ll,ll> > v(N);
ll fpow(ll a,ll b){
	ll res=1;
	while(b){
		if(b&1) res=(res*a)%p;
		a=(a*a)%p;
		b>>=1;
	}
	return res;
}
void solve(){
	map<ll,ll> mp;
	cin>>n>>m;
	for(int i=1;i<=n;i++){
		ll a,b;cin>>a>>b;
		mp[b]+=a;
	}
	ll idx=0;
	for(auto i:mp){
		if(i.second>m){
			ll sub=i.second-m;
			mp[i.first]-=sub;
			if(sub&1) sub++,mp[i.first]--;
			mp[i.first+1]+=sub/2;
		}
		if(mp[i.first]) v[++idx]={i.first,mp[i.first]};
	}
	ll pre=-1,res=0,fu=0;
	for(ll i=idx;i>=1;i--){
		if(pre==-1){
			res+=fpow(2,v[i].first);
			fu=m-v[i].second;
		}else{
			if(fu&&pre-v[i].first>40){
				cout<<res<<"\n";
				return ;
			}else{
				if(fu){
					for(ll j=pre;j>v[i].first;j--){//2^j
						fu*=2;
						if(fu>2*m){cout<<res<<"\n";return;}
					}
				}
				if(fu>=v[i].second) fu-=v[i].second;
				else{
					v[i].second-=fu;
					res=res+fpow(2,v[i].first);
					res%=p;
					fu=m-v[i].second;
				}
			}
		}
		pre=v[i].first;
	}
	cout<<res<<"\n";
}
int main(){
	int T;cin>>T;
	while(T--) solve();
	return 0;
}

2025武汉邀请赛-G 路径求和问题

题意:

有一个 $n\times m$ 的网格,每个格子写一个整数,第 $i$ 行第 $j$ 列的格子里写着整数 $a_{i,j}$。

从 $(1,1)$ 走到 $(n,m)$ ,只能往下走或者往右走。

一条路径的价值定义为路径上不同整数的数量。对于所有可能路径,求价值之和。

题解:

25 ICPC 邀请赛武汉站 G 题题解

#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
typedef pair<ll, ll> PII; 
const int mod = 998244353;
const int N = 1e6 + 10;
ll fact[N], infact[N];
int n, m;
ll qmi(ll a, ll b){
	ll res = 1;
	while (b) {
		if (b & 1) res = res * a % mod;
		a = a * a % mod;
		b >>= 1;
	}
	return res;
}
void init(){
	fact[0] = infact[0] = 1;
	for (int i = 1; i < N; i ++) fact[i] = fact[i - 1] * i % mod;
	infact[N - 1] = qmi(fact[N - 1], mod - 2);
	for (int i = N - 2; i; i --) infact[i] = infact[i + 1] * (i + 1) % mod;
}
ll C(int a, int b){
	if (b < 0 || a - b < 0) return 0;
	return fact[a] * infact[b] % mod * infact[a - b] % mod;
}
bool cmp(PII a, PII b){
	if (a.first == b.first) return a.second < b.second;
	return a.first < b.first;
}
ll func1(vector<PII>& a, int len){
	vector<ll> f(len, 0);
	ll res = 0;
	for (int i = 0; i < len; i ++) {
		f[i] = C(a[i].first + a[i].second - 2, a[i].first - 1);
		for (int j = 0; j < i; j ++) {
			if (a[j].first <= a[i].first && a[j].second <= a[i].second) {
				f[i] = (f[i] - f[j] * C(a[i].first - a[j].first + a[i].second - a[j].second, a[i].first - a[j].first) % mod + mod) % mod;
			}
		}
		ll num = f[i] * C(n - a[i].first + m - a[i].second, n - a[i].first) % mod;
		res = (res + num) % mod;
	}
	return res;
}
ll func2(vector<vector<ll>>& g, ll tol, int c){
	vector<vector<ll>> dp(n + 1, vector<ll>(m + 1, 0));
	dp[1][1] = 1;
	ll res = 0;
	if (g[1][1] == c) dp[1][1] = 0;
	for (int i = 1; i <= n; i ++)
		for (int j = 1; j <= m; j ++) {
		if (i == 1 && j == 1) continue;
		if (g[i][j] == c) continue;
		dp[i][j] = (dp[i - 1][j] + dp[i][j - 1]) % mod;
	}
	res = (tol - dp[n][m] + mod) % mod;
	return res;
}
void solve(){
	cin >> n >> m;
	vector<vector<ll>> g(n + 1, vector<ll>(m + 1));
	set<int> v;
	for (int i = 1; i <= n; i ++)
		for (int j = 1; j <= m; j ++) {
			cin >> g[i][j];
			v.insert(g[i][j]);
		}
	map<int, vector<PII>> c;
	for (int i = 1; i <= n; i ++)
		for (int j = 1; j <= m; j ++)
			c[g[i][j]].emplace_back(i, j);
	for (auto it : v) sort(c[it].begin(), c[it].end(), cmp);
	int mid = sqrt(n * m);
	ll tol = C(n + m - 2, n - 1);
	ll ans = 0;
	for (int it : v) {
		auto a = c[it];
		int len = a.size();
		ll x;
		if (len <= mid) x = func1(a, len);
		else x = func2(g, tol, it);
		ans = (ans + x) % mod;
	}
	cout << ans << '\n';
}
int main(){
	ios::sync_with_stdio(false),cin.tie(0), cout.tie(0);
	init();
	int t;cin >> t;
	while (t --) solve();
	return 0;
}