算法笔记-动态规划

CF41D Pawn

题意:

给定矩阵,每个格子都有一个权值。每次可以向右上或者左上走。要求权值和最大且为 k + 1 k+1 k+1的倍数。询问最大权值和,起始位置和移动方案。

解析:

如果没有倍数限制的话,简单的二维 d p dp dp d p i , j dp_{i,j} dpi,j表示到达 ( i , j ) (i,j) (i,j)能获得的最大权值。

因为整除限制,再加一维: d p i , j , k dp_{i,j,k} dpi,j,k表示到达 ( i , j ) (i,j) (i,j)时,是否存在方案使权值和为 k k k。状态转移的时候记录以下路径。

时间复杂度: O ( n m ( n k ) ) O(nm(nk)) O(nm(nk))

代码:
#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
const int maxn = 1e2+10;
int f[maxn][maxn][maxn*10];
char g[maxn][maxn][maxn*10];
int a[maxn][maxn];
int n, m, kk;
void print(int x, int y, int k){
	if(x == n){
		cout << y << endl;
		return;
	}
	if(g[x][y][k] == 'R'){
		print(x+1, y-1, k-a[x][y]);
		cout << "R"; 
	}
	else if(g[x][y][k] == 'L'){
		print(x+1, y+1, k-a[x][y]);
		cout << "L";
	}
}
int main(){
	cin >> n >> m >> kk;
	for(int i = 1; i <= n; i++)
		for(int j = 1; j <= m; j++)
			scanf("%1d", &a[i][j]);
	//memset(f, 0, sizeof(f));
	for(int i = 1; i <= m; i++)
		f[n][i][a[n][i]] = 1;
	for(int i = n-1; i >= 1; i--){
		for(int j = 1; j <= m; j++){
			for(int k = 1000; k >= a[i][j]; k--){
				if(j >= 2){
					if(f[i+1][j-1][k-a[i][j]]){
						f[i][j][k] = 1;
						g[i][j][k] = 'R';
					}
				}
				if(j <= m-1){
					if(f[i+1][j+1][k-a[i][j]]){
						f[i][j][k] = 1;
						g[i][j][k] = 'L';
					}
				}
			}
		}
	}
	for(int i = 1000; i >= 0; i--){
		if(i%(kk+1) != 0)
			continue;
		for(int j = 1; j <= m; j++){
			if(f[1][j][i]){
				cout << i << endl;
				print(1, j, i);
				return 0;
			}
		}
	}
	cout << -1 << endl;
	return 0;
}


CF893E Counting Arrays

题意:

每次询问 ( x , y ) (x,y) (x,y),长度为 y y y且乘积为 x x x的序列的个数,序列中的元素可以为负数

解析:

首先考虑正负的问题,前 y − 1 y-1 y1个位置元素符号可以为任意,由第 y y y个元素保证乘积为正数,所以对答案的贡献为 2 y − 1 2^{y-1} 2y1

由算数基本定理可得: x = p 1 a 1 p 2 a 2 . . . p m a m x = p_1^{a_1}p_2^{a_2}...p_m^{a_m} x=p1a1p2a2...pmam对于每个 p i p_i pi,在序列中一定要"出现" a i a_i ai次,才能保证乘积为 x x x。将 a i a_i ai p i p_i pi分到 y y y个位置,可以存在位置没有分配到。由隔板法可知,对答案的贡献为 C ( y + a i − 1 , y − 1 ) C(y+a_i-1, y-1) C(y+ai1,y1).

代码:
#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
const int maxn = 1e6+100;
const int mod = 1e9+7;
ll fac[maxn], facv[maxn];
ll C(ll a, ll b){
	ll res = fac[a]%mod*facv[b]%mod*facv[a-b]%mod;
	return res%mod;
}
ll qpow(ll a, ll b){
	ll res = 1;
	while(b){
		if(b&1)
			res = res*a%mod;
		a = a*a%mod;
		b = b >> 1;
	}
	return res%mod;	
}
ll inv(ll x){
	return qpow(x, mod-2)%mod;
}
void init(int x){
	fac[0] = facv[0] = 1;
	for(int i = 1; i <= x; i++){
		fac[i] = fac[i-1]*i%mod;
		facv[i] = inv(fac[i])%mod; 
	}
}
void solve(){
	ll x, y;
	cin >> x >> y;
	ll ans = 1;
	for(ll i = 2; i*i <= x; i++){
		if(x%i == 0){
			ll cnt = 0;
			while(x%i == 0){
				x /= i;
				cnt++;
			}
			ans = ans*C(y+cnt-1, cnt)%mod;
		}
	}
	if(x > 1)
		ans = ans*C(y, 1)%mod;
	ans = ans*qpow(2, y-1)%mod;
	cout << ((ans%mod)+mod)%mod << endl;
}
int main(){
	ios::sync_with_stdio(0);
	cin.tie(0);cout.tie(0);
	init(1000050);
	int T;
	cin >> T;
	while(T--)
		solve();	
	return 0;
}


CF855C Helga Hufflepuff’s Cup

题意:

给定一棵树,可以染 m m m 种颜色,定义一个特殊颜色 k k k,要求保证整棵树上特殊颜色的个数不超过 x x x 个。同时,如果一个结点是特殊颜色,那么它的相邻结点的颜色编号必须全部小于 k k k。求方案数。

解析:

树上dp。
每个点的染色分为三种:小于 k k k,等于 k k k,大于 k k k

d p i , j , s dp_{i,j,s} dpi,j,s 表示以 i i i 为根的子树内有 j j j 个特殊颜色,且 i i i 的染色为 s s s 的方案数。

然后就不会了。以下是看了 一扶苏一 \color{red}{一扶苏一} 一扶苏一 的题解才会的,直接看大佬的题解更明白一点。

当一个结点多个儿子时,要考虑每增加一个点的产生的贡献。

g i , j , s g_{i,j,s} gi,j,s u u u的子树中,考虑前 i i i个儿子,一共选了 j j j k k k,结点 u u u的状态为 s s s

u u u选小于 k k k时,儿子可以选 0 / 1 / 2 0/1/2 0/1/2 三种情况

u u u选等于 k k k时,儿子只能选 0 0 0 一种情况

u u u选大于 k k k时,儿子可以选 0 / 2 0/2 0/2 两种情况

g g g可以滚掉一维

代码:
#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
const int maxn = 1e5+10;
const int maxm = 2e5+10;
const int INF = 0x3f3f3f3f;
const int mod = 1e9+7;
int head[maxn], tot;
struct edge{
	int to, nxt;
}e[maxm<<1];
void add(int a, int b){
	e[++tot].nxt = head[a];
	e[tot].to = b;
	head[a] = tot;
}
ll f[maxn][15][3];
ll g[maxn][2][15][3];
int n, m, k, x;
void init(int u){
	g[u][0][0][0] = k-1;
	g[u][0][0][2] = m-k;
	g[u][0][1][1] = 1;
}
void dfs(int u, int p){
	init(u);
	int pre = 0;
	for(int i = head[u]; i; i = e[i].nxt){
		int v = e[i].to;
		if(v == p)
			continue;
		dfs(v, u);
		pre ^= 1;
		memset(g[u][pre], 0, sizeof(g[u][pre]));
		for(int j = 0; j <= x; j++){
			for(int h = 0; h <= j; h++){
				g[u][pre][j][0] = (g[u][pre][j][0]+g[u][pre^1][j-h][0]*(f[v][h][0]+f[v][h][1]+f[v][h][2])) % mod;
				g[u][pre][j][1] = (g[u][pre][j][1]+g[u][pre^1][j-h][1]*f[v][h][0]) % mod;
				g[u][pre][j][2] = (g[u][pre][j][2]+g[u][pre^1][j-h][2]*(f[v][h][0]+f[v][h][2])) % mod; 
			}
		}
	}
	for(int i = 0; i <= x; i++)
		for(int j = 0; j < 3; j++)
			f[u][i][j] = g[u][pre][i][j];
}

signed main(){
	cin >> n >> m;
	for(int i = 1; i < n; i++){
		int a, b;
		cin >> a >> b;
		add(a, b);
	}	
	cin >> k >> x;
	dfs(1, 0);
	ll ans = 0;
	for(int i = 0; i <= x; i++)
		for(int j = 0; j < 3; j++)
			ans = (ans+f[1][i][j]) % mod;
	cout << ans << endl;
	return 0;
}


CF960F Pathwalks

题意:

给定有向图。求最长的路径,使得这条路径的编号和权值都严格单调递增,编号是指输入的顺序。路径的长度是经过边的数量。

分析:

因为要按照编号递增,所以按照输入顺序依次处理每条边。

d p u , x dp_{u,x} dpu,x是以 u u u为终点,最后一条路径权值为 x x x的最长路径的长度。后边有一条边 ( u , v , w ) (u,v,w) (u,v,w),(表示 u u u v v v,权值为 w w w

d p v , w = m a x { d p u , x } + 1 , x < w dp_{v,w} = max\{dp_{u,x}\}+1,x<w dpv,w=max{dpu,x}+1,x<w

每次转移的时间是 O ( w ) O(w) O(w),会超时。需要单点修改和区间查询,可以用线段树来优化转移。

每个点建一棵线段树,维护权值对应的最长路径。

代码:
#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
const int maxn = 1e5+10;
const int maxm = 2e7+10;
struct edge{
	int x, y, w;
}e[maxn];
struct node{
	int ls, rs;
	int maxx;
}t[maxm];
int tot = 0;
void newnode(int &k){
	k = ++tot;
	t[k].ls = t[k].rs = 0;
	t[k].maxx = 0;
}
void pushup(int k){
	t[k].maxx = max(t[t[k].ls].maxx, t[t[k].rs].maxx);
}
int query(int k, int l, int r, int x, int y){
	if(k == 0)
		return 0;
	if(x <= l && y >= r)
		return t[k].maxx;
	int mid = (l+r) >> 1;
	int res = 0;
	if(x <= mid)
		res = max(res, query(t[k].ls, l, mid, x, y));
	if(y > mid)
		res = max(res, query(t[k].rs, mid+1, r, x, y));
	return res;
}
void modify(int &k, int l, int r, int pos, int v){
	if(!k)
		newnode(k);
	if(l == r){
		t[k].maxx = max(t[k].maxx, v);
		return;
	}
	int mid = (l+r) >> 1;
	if(pos <= mid)
		modify(t[k].ls, l, mid, pos, v);
	else
		modify(t[k].rs, mid+1, r, pos, v);
	pushup(k);
}
int n, m;
int rt[maxn];
int ans = 0;
int main(){
	cin >> n >> m;
	for(int i = 1; i <= m; i++){
		int u, v, w;
		cin >> u >> v >> w;
		int tmp = query(rt[u], 0, 100000, 0, w-1)+1;
		modify(rt[v], 0, 100000, w, tmp);
		ans = max(ans, tmp);
	}
	cout << ans << endl;
	return 0;
} 


CF847E Packmen

题意:

一维直线上有若干包裹和人,人移动一单位距离需要一秒,询问得到所有包裹的最短时间

解析:

答案具有单调性,考虑二分答案。

  • 人的路径不会相交
  • 每个人最多掉头一次

考虑贪心。最左边的包裹只能由最左边的人得到,这个人可以先往左得到这个包裹,然后一直向右走直到无法走;也可以向右走一定的距离,然后向左得到最左边的包裹

代码:
#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
const int maxn = 2e5+10;
const int INF = 0x3f3f3f3f;
int cntp, cnts, ans;
int n;
int p[maxn], s[maxn];
char ss[maxn];
int check(int x){
	int nowp = 1, nows = 1;
	for(int i = 1; i <= n; i++){
		if(ss[i] == '.')
			continue;
		if(ss[i] == 'P'){
			if(p[nowp] > s[nows]){ //左边还有 
				int dis = p[nowp] - s[nows];
				if(dis > x)
					return 0;
				int rdis = x-2*dis;
				while(s[nows]-p[nowp] <= max(0, rdis))
					nows++;//先左再右
				while(2*(s[nows]-p[nowp])+dis <= x)
					nows++;//先右再左 
				 
			}
			else{
				while(s[nows] - p[nowp] <= x)
					nows++;
			}
			nowp++; 
		}
		if(nowp == cntp+1)
			break;
		if(nows == cnts+1)
			break;
	}
	return nows == cnts+1;
}
int main(){
	cin >> n;
	cin >> ss+1;
	for(int i = 1; i <= n; i++){
		if(ss[i] == 'P')
			p[++cntp] = i;
		else if(ss[i] == '*')
			s[++cnts] = i;
	}
	s[cnts+1] = INF;
	int l = 1, r = 2*n;
	while(l <= r){
		int mid = (l+r) >> 1;
		if(check(mid)){
			ans = mid;
			r = mid-1;
		}
		else
			l = mid+1;
	}
	cout << ans << endl;
	return 0;
}
  • 2
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值