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可以从[i−R,i−L]过来,设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,i−R)<=j<=max(0,i−L))
对
于
i
它
会
从
x
∈
[
i
−
R
,
i
−
L
]
中
最
大
的
d
p
[
x
]
转
移
过
来
对于 i 它会从 x∈[i-R,i-L] 中最大的 dp[x] 转移过来
对于i它会从x∈[i−R,i−L]中最大的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+1它会从x∈[i−R+1,i−L+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+2会从x∈[i−R+2,i−L+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)表示把区间i到j变成合法括号序列最少需要添加几个字符;
用
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+1到j−1再输出右括号;
如
果
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过程中存下来,
可以发现k很小,可以用类似前缀和的方式在dfs过程中存下来,
用
p
r
e
[
u
]
[
k
]
表
示
从
根
节
点
到
u
的
路
径
上
每
个
点
的
深
度
的
k
次
方
之
和
。
用pre[u][k]表示从根节点到u的路径上每个点的深度的k次方之和。
用pre[u][k]表示从根节点到u的路径上每个点的深度的k次方之和。
可
以
发
现
u
−
>
v
的
路
径
是
没
有
L
C
A
(
u
,
v
)
以
上
的
点
的
值
的
,
可以发现u->v的路径是没有LCA(u,v)以上的点的值的,
可以发现u−>v的路径是没有LCA(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;
}