洛谷每日一练5.7--P4427+UVA1626+P1725 (DP+图论)

P1725

题意:

就是有 n + 1 n+1 n+1 个格子,编号为 0 0 0 n n n ,每个格子上都有一个分数,第0格上的分数保证为0.
当在第 i 个格子的时能选择移动到区间 [ i + L , i + R ] [i+L,i+R] [i+L,i+R] 中的任意一格当移动到一个格子时就会得到这
个格子的分数,最后一跳可以选择恰好跳到 n n n 也可以跳出去(即大于 n n n).

问能得到的最大分数是多少?

思路:

对于一个点 i 来说,它可以跳到区间 [ i + L , i + R ] [i+L,i+R] [i+L,i+R]中的任意一点,当然左右都需要和 0 0 0 m a x max max

反 过 来 说 , 点 i 可 以 从 [ i − R , i − L ] 过 来 , 设 d p [ i ] 为 到 达 i 的 时 获 得 的 最 大 价 值 , 那 么 反过来说,点 i 可以从 [i-R,i-L] 过来,设 dp[i] 为到达 i 的时获得的最大价值,那么 i[iR,iL]dp[i]i
d p [ i ] = m a x ( d p [ j ] ) + A [ i ] ( m a x ( 0 , i − R ) < = j < = m a x ( 0 , i − L ) ) dp[i] = max(dp[j]) + A[i](max(0,i-R) <= j <= max(0,i-L)) dp[i]=max(dp[j])+A[i](max(0,iR)<=j<=max(0,iL))
对 于 i 它 会 从 x ∈ [ i − R , i − L ] 中 最 大 的 d p [ x ] 转 移 过 来 对于 i 它会从 x∈[i-R,i-L] 中最大的 dp[x] 转移过来 ix[iR,iL]dp[x]
而 对 于 i + 1 它 会 从 x ∈ [ i − R + 1 , i − L + 1 ] 中 最 大 的 d p [ x ] 转 移 过 来 而对于 i+1 它会从 x∈[i-R+1,i-L+1]中最大的dp[x]转移过来 i+1x[iR+1,iL+1]dp[x]
对 于 i + 2 会 从 x ∈ [ i − R + 2 , i − L + 2 ] 中 最 大 的 d p [ x ] 转 移 过 来 , 对于 i+2 会从 x∈[i-R+2,i-L+2]中最大的 dp[x] 转移过来, i+2x[iR+2,iL+2]dp[x]
这 就 是 一 个 滑 动 窗 口 求 最 值 问 题 这就是一个滑动窗口求最值问题

#include <cstdio>
#include <queue>
#include <cstring>
#include <iostream>
using namespace std;
const int N = 2e5+10;
const int inf = 0x3f3f3f3f;
int dp[N],A[N];
int n,L,R;
deque<int> q; 
void add(int i){
	while(q.size()&&dp[q.back()] < dp[i]){
		q.pop_back();
	} 
	q.push_back(i);
}
int query(int i){
	//如果最大值来自的下标小于i-R需要弹出 
	while(q.size()&&q.front() < i-R){
		q.pop_front();
	}
	return q.front();
}
int main(){
	scanf("%d%d%d",&n,&L,&R);
	for(int i = 0;i <= n;i++) scanf("%d",A+i);
	//dp(i) = -inf表示这个点不可达 
	memset(dp,-inf,sizeof(dp));
	dp[0] = 0;
	int ans = -inf;
	//第一个可以由其他点跳过来的点是L
	for(int i = L;i <= n;i++){
		add(i-L);
		int mx_id = query(i);
		dp[i] = dp[mx_id] + A[i];
		if(i + R > n) ans = max(ans,dp[i]);
	}
	printf("%d\n",ans);
	return 0;
}

UVA1626

题意:

给出一个括号序列,可能为空行,让你添加最小的括号使得这个括号序列变得合法,空行本身就是合法的括号序列,这题洛谷上好像交不了,找到 POJ1141 感觉上差不多

思路:

首 先 对 于 一 个 合 法 的 括 号 序 列 , 它 要 么 是 这 样 ( ( [ ] ) ) 就 是 头 和 尾 本 身 是 个 合 法 序 列 首先对于一个合法的括号序列,它要么是这样(([])) 就是头和尾本身是个合法序列 (([]))
然 后 头 和 尾 包 住 的 里 面 的 东 西 也 是 合 法 序 列 ; 然后头和尾包住的里面的东西也是合法序列; 西;
或 者 是 这 样 的 ( ) ( ) , 左 边 部 分 是 合 法 括 号 序 列 右 边 部 分 也 是 合 法 括 号 序 列 ; 或者是这样的()(),左边部分是合法括号序列右边部分也是合法括号序列; ()(),
用 f ( i , j ) 表 示 把 区 间 i 到 j 变 成 合 法 括 号 序 列 最 少 需 要 添 加 几 个 字 符 ; 用f(i,j)表示把区间i到j变成合法括号序列最少需要添加几个字符; f(i,j)ij
用 s t ( i , j ) 记 录 下 他 是 以 上 述 哪 种 作 为 添 加 的 , s t ( i , j ) = − 1 就 是 用 第 一 种 , 用st(i,j)记录下他是以上述哪种作为添加的,st(i,j) = -1就是用第一种, st(i,j)st(i,j)=1
否 则 s t ( i , j ) = k 就 是 用 第 二 种 且 [ i , k ] 是 合 法 , [ k + 1 , j ] 是 合 法 ; 否则st(i,j) = k就是用第二种且[i,k]是合法,[k+1,j]是合法; st(i,j)=k[i,k][k+1,j]
首 先 对 于 所 有 的 d p [ i ] [ i ] 都 等 于 1 , 一 个 字 符 都 是 只 需 要 一 个 把 它 变 合 法 首先对于所有的dp[i][i]都等于1,一个字符都是只需要一个把它变合法 dp[i][i]1
用 区 间 D P 的 方 式 进 行 转 移 , 这 里 重 点 是 记 下 状 态 , 等 下 输 出 用区间DP的方式进行转移,这里重点是记下状态,等下输出 DP
输 出 用 递 归 的 方 式 , 因 为 刚 才 记 下 了 s t ( i , j ) , 如 果 s t ( i , j ) = − 1 ; 输出用递归的方式,因为刚才记下了st(i,j),如果st(i,j) = -1; ,st(i,j),st(i,j)=1;
那 么 先 输 出 一 个 左 括 号 再 输 出 i + 1 到 j − 1 再 输 出 右 括 号 ; 那么先输出一个左括号再输出i+1到j-1再输出右括号; i+1j1;
如 果 s t ( i , j ) 不 是 ﹣ 1 , 就 要 先 输 出 左 边 再 输 出 右 边 如果st(i,j)不是﹣1,就要先输出左边再输出右边 st(i,j)1

#include<iostream>
#include<cstring>
#include <cstdio>
using namespace std;
const int N = 105;
int f[N][N]; //d[i][j]表示输入字符串从下标i到下标j至少需要加的括号数 
int st[N][N]; //c[i][j]表示从下标i到下标j的子串分割的下标,-1表示不分割 
string s; //输入的括号串 
int n;
bool check(char ch1,char ch2){
	if(ch1=='('&&ch2==')') return 1;
	if(ch1=='['&&ch2==']') return 1;
	return 0;
}
void dp(){
	for(int i = 0;i < n;i++) f[i][i] = 1;
	for(int len = 1;len < n;len++){
		for(int i = 0;i+len < n;i++){
			int j = i+len;
			int mi = f[i][i] + f[i+1][j];
			st[i][j] = i;
			for(int k = i+1;k < j;k++){
				if(f[i][k]+f[k+1][j] < mi){
					mi = f[i][k] + f[k+1][j];
					st[i][j] = k;
				}
			}
			f[i][j] = mi;
			if(check(s[i],s[j])){
				if(f[i+1][j-1] < mi){
					f[i][j] = f[i+1][j-1];
					st[i][j] = -1;
				}
			}
		}
	}
}
void print(int i,int j){
	if(i > j) return;
	if(i==j){
		if(s[i]=='('||s[i]==')') printf("()");
		else printf("[]");
	}else{
		if(st[i][j] == -1){
			if(s[i]=='('){
				printf("(");print(i+1,j-1);printf(")");
			}else{
				printf("[");print(i+1,j-1);printf("]");
			}
		}else{
			int mid = st[i][j];
			print(i,mid);print(mid+1,j);
		}
	}	
}

int main(){	
	cin>>s; 
	n = s.size();
	dp();
	print(0, n-1);
	puts("");
	return 0;
}

P4427

题意:

给一颗树,q个询问,每个询问给出u,v,k,问u到v的路径上每个点深度的k次方的和是多少

思路:

可 以 发 现 k 很 小 , 可 以 用 类 似 前 缀 和 的 方 式 在 d f s 过 程 中 存 下 来 , 可以发现k很小,可以用类似前缀和的方式在dfs过程中存下来, kdfs
用 p r e [ u ] [ k ] 表 示 从 根 节 点 到 u 的 路 径 上 每 个 点 的 深 度 的 k 次 方 之 和 。 用pre[u][k]表示从根节点到u的路径上每个点的深度的k次方之和。 pre[u][k]uk
可 以 发 现 u − > v 的 路 径 是 没 有 L C A ( u , v ) 以 上 的 点 的 值 的 , 可以发现u->v的路径是没有LCA(u,v)以上的点的值的, u>vLCA(u,v)
那 么 如 果 用 p r e [ u ] [ k ] + p r e [ v ] [ k ] 会 多 出 那 些 值 呢 ? 那么如果用pre[u][k]+pre[v][k]会多出那些值呢? pre[u][k]+pre[v][k]
L C A ( u , v ) 算 了 两 次 , 但 只 需 要 一 次 , L C A ( u , v ) 的 父 节 点 以 上 的 根 本 不 需 要 , 但 算 了 两 次 LCA(u,v)算了两次,但只需要一次,LCA(u,v)的父节点以上的根本不需要,但算了两次 LCA(u,v)LCA(u,v)
所 以 应 该 是 p r e [ u ] [ k ] + p r e [ v ] [ k ] − p r e [ L C A ( u , v ) ] [ k ] − p r e [ f a [ L C A ( u , v ) ] [ 0 ] ] [ k ] 所以应该是pre[u][k]+pre[v][k]-pre[LCA(u,v)][k]-pre[fa[LCA(u,v)][0]][k] pre[u][k]+pre[v][k]pre[LCA(u,v)][k]pre[fa[LCA(u,v)][0]][k]

#include <cstdio>
#include <iostream>
#include <cstring>
using namespace std;
typedef long long ll;
const int N = 3e5+10;
const ll mod = 998244353;
int n,fa[N][22];
ll dep[N];
ll pre[N][55];
struct Edge{
	int to,nex;
}e[N<<1];
int head[N],idx;
void add_edge(int u,int v){
	e[idx].to = v;
	e[idx].nex = head[u];
	head[u] = idx++;
}
ll qpow(ll a,ll b){
	ll res = 1;
	while(b){
		if(b&1) res = res*a%mod;
		a = a*a%mod;
		b>>=1; 
	}
	return res%mod;
}
void dfs(int u,int father){
	dep[u] = dep[father] + 1;
	fa[u][0] = father;
	for(ll i = 1;i <= 50;i++){
		pre[u][i] = (pre[father][i]+qpow(dep[u],i))%mod;
	}
	for(int i = head[u];~i;i = e[i].nex){
		int v = e[i].to;
		if(v == father) continue;
		dfs(v,u);
	}
}
//处理fa数组 
void init(){
	for(int j = 1;j <= 20;j++){
		for(int i = 1;i <= n;i++){
			fa[i][j] = fa[fa[i][j-1]][j-1];
		}
	}
}
//求u和v的LCA 
int lca(int u,int v){
	if(dep[u] < dep[v]) swap(u,v);
	for(int i = 20;i >= 0;i--){
		if(dep[fa[u][i]] >= dep[v]){
			u = fa[u][i];
		}
	}
	if(u==v) return u;
	for(int i = 20;i >= 0;i--){
		if(fa[u][i] != fa[v][i]){
			u = fa[u][i],v = fa[v][i];
		}
	}
	return fa[u][0];
}
int main(){
	memset(head,-1,sizeof(head));
	scanf("%d",&n);
	for(int i = 1,u,v;i < n;i++){
		scanf("%d%d",&u,&v);
		add_edge(u,v);add_edge(v,u);
	}
	dep[0] = -1;dfs(1,0);
	init();
	int q;scanf("%d",&q);
	while(q--){
		int u,v,k;scanf("%d%d%d",&u,&v,&k);
		int LCA = lca(u,v);
		ll tmp1 = (pre[u][k]+pre[v][k])%mod;
		ll tmp2 = (pre[LCA][k]+pre[fa[LCA][0]][k])%mod;
		printf("%lld\n",(tmp1-tmp2+mod)%mod);
	}
	return 0;
}

  • 5
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值