0%

思路讲解

类似于Floyd的递推公式

Untitled

1
2
3
4
5
6
7
FOR(j,0,sum){
FOR(i,0,SZ(edges)-1){
ll u,v,a,b;u=edges[i][0],v=edges[i][1],a=edges[i][2],b=edges[i][3];
if(dp[u][j]>=INF) continue;
dp[v][j+a]=min(dp[v][j+a],dp[u][j]+b);
}
}

根本原因:保证状态转移的顺序正确性,避免“后效性”。

动态规划的一个基本原则是,当你计算一个状态 dp[S] 时,所有它所依赖的状态 dp[S'] 都必须是已经计算出来的、并且是最终的(最优的)值。

让我们来分析一下这个特定的状态转移 dp[v[i]][c] = ... dp[u[i]][c - a[i]] ...

  • 为了计算 c 这个总和的状态,我们依赖于 c - a[i] 这个总和的状态。

  • 因为边权 a[i] 是正整数 (a[i] >= 1),所以 c - a[i] 永远小于 c

c (a的和) 作为外层循环,并从小到大枚举,就完美地解决了这个问题。

  1. 当外层循环执行到 c = 1 时,它会用所有 dp[...][0] 的值来更新 dp[...][1]

  2. 当外层循环执行到 c = 2 时,它会用所有 dp[...][0]dp[...][1] 的值来更新 dp[...][2]

  3. 当我们计算 dp[...][c] 这一“层”的所有状态时,所有 c 值更小的“层”(比如 dp[...][c-1], dp[...][c-2] 等)都已经计算完毕并且得到了最优解。

这确保了我们每次进行状态转移时,dp[u[i]][c - a[i]] 已经是我们能找到的、到达节点 ua 和为 c - a[i] 的最优值(即最小 b 和)。

AC代码

327896817

心路历程(WA,TLE,MLE……)

思路讲解

【【直播回放】VP 四川省赛 2025年06月13日19点场】 【精准空降到 30:57】 https://www.bilibili.com/video/BV1iXM6zmEXT/?share_source=copy_web&vd_source=6ca0bc05e7d6f39b07c1afd464edae37&t=1857

看哥哥的视频。

主要就是树上dp,维护前缀和后缀。

AC代码

https://codeforces.com/group/vXvHT09g9Y/contest/105949/submission/327796835

心路历程(WA,TLE,MLE……)

思路讲解

那么容易想到,原来出现过的 $a???a??\rightarrow aaaaa??

$ 并不会增加。

a?a?????c??c?aaa?????cccc?a?a?????c??c?→aaa?????cccc?

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
for(int i=0;i<M;++i){
char ch=i+'a';
vector<ll> pos;
for(int j=1;j<=N;++j){
if(S[j]==ch){
pos.push_back(j);
}
}
if(pos.size()>=2){
for(int i=pos[0];i<=pos.back();++i){
if(S[i]=='?'){
S[i]=ch;
}
}
}
}

那么继续,我们把拆贡献的思路拓展到极致,就是说其实无论怎么样搞,搞完前面这套操作,还要继续操作的话,必定会增加1个代价,那么我们只要构造增加一个代价的字符串就行了。

用代码描述就是这样。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
FOR(i,1,N){
if(S[i]=='?'){
if(chs.empty()){
if(S[i-1]>='a' && S[i-1]<='z'){
S[i]=S[i-1];
}
}else{
S[i]=chs.back();chs.pop_back();
}
}
}
ROF(i,N,1){
if(S[i]=='?'){
if(chs.empty()){
if(S[i+1]>='a' && S[i+1]<='z'){
S[i]=S[i+1];
}
}else{
S[i]=chs.back();chs.pop_back();
}
}
}

AC代码

https://ac.nowcoder.com/acm/contest/view-submission?submissionId=78079278

心路历程(WA,TLE,MLE……)

思路讲解

先把所有点替换为其父节点,然后使用

LCAa1,a2=a1(a1为深度较浅点)a11>a2的路径上LCA(a1,a2)=a1(a1为深度较浅点)\Rightarrow a1在1->a2的路径上这个结论。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
for(int i=1;i<=M;++i){
sort(que[i].begin(),que[i].end(),cmp);
for(int j=0;j<que[i].size();++j){
que[i][j]=Fa[que[i][j]];
}
ll din=in[que[i].back()],dout=out[que[i].back()];
bool isB=false;
for(int j=(int)que[i].size()-2;j>=0;--j){
ll node=que[i][j];
if(in[node]<=din && out[node]>=dout){ // node是不是最深点的父节点?

}else{
cout<<"NO\n";isB=true;
break;
}
}
if(!isB)cout<<"YES\n";
}

AC代码

https://codeforces.com/group/vXvHT09g9Y/contest/1328/submission/327207452

心路历程(WA,TLE,MLE……)

思路讲解

我们这里有一个很好的分离:在将每条边设置为0 的 MST 查询之后,发生变化的那些边正是权重为1 的边。可以用这种方法找到权重为1的所有边,使用M次询问。

但是这有一个前提,就是这条边必须是两个联通分量的唯一连接,否则如果有其他连接,其他连接都是0,最小生成树就是0,无法判断了。

那么我个人认为,其实就是能找出所有的1,然后剩余的都是0就行了。

AC代码

心路历程(WA,TLE,MLE……)