原自于vjudge的题单
前言
\(\quad\)(是不是链接点错了🤓)依旧拖着 线性DP。
其他
\(\quad\) 可恶,vjudge吃我提交记录,还有「禁忌 Coloring Brackets 的二重奏」。
Zuma CF-607B
祖玛豪丸🤓👍
很豪写的 区间DP 题:
题目要求删除回文的字串,计算删除所有的字串的最小操作次数。
状态表示;\(f_{l,r}\) 表示在区间 \([l,r]\) 内的最小操作次数。
显然有一下结论:(此处()中,如果成立值为 1,不成立值为 0)
- \(f_{i,i}=1\)
- \(f_{i,i+1}=1+(a_i ≠ a_{i+1})\)
- 其余状态全设为 \(inf\) 因为后面要计算最小值。
这样我们可以得出状态转移:
- \(f_{l,r}=f{l+1,r-1}\quad (a[l]==a[r])\)
- \(f_{l,r}=min_{k=1}^{r-1} \{ f_{l,k}+f_{k+1,r} \}\)
答案为 \(f_{1,n}\),时间复杂度 \(O(n^3)\) 。
code:
for(int i=1;i<=n;i++){f[i][i]=1;f[i][i+1]=1+(a[i]!=a[i+1]);
}
for(int i=3;i<=n;i++){for(int l=1;l+i-1<=n;l++){int r=l+i-1;if(a[l]==a[r]) f[l][r]=f[l+1][r-1];for(int k=l;k<r;k++){f[l][r]=min(f[l][k]+f[k+1][r],f[l][r]);}}
}
Coloring Brackets CF-149D
为什么你在题单里出现了两次
代码还行,就是观感巨的 区间DP:
题目要求求括号序列的染色方案数,并且有一下限制:
- 每个括号有三种染色方案,染红,染蓝和不染色。
- 对于任意一个括号:它或者对应的匹配括号中的一个着色。
- 相邻的着色括号颜色不同。
首先我们要找到每个括号所匹配的括号(找到它的另一半)用栈就可以轻松解决(代码在后面)。
然后就要考虑状态表示,套用模板设 \(f_{i,j}\) 发现限制很难维护,所以我们要进行修改,根据 限制 3 我们可以设计状态表示为:\(f_{i,j,p,q}\) 表示将区间 \([l,r]\) 染色的方案数,并且两端的颜色为 \(p,q\)。假设 0 代表不染色,1 代表染蓝色,2 代表染红色。
状态转移我们分类讨论:
l==r+1,即区间 \([l,r]\) 的长度为 2,形如 (),则 \(l\) 和 \(r\) 一定是匹配括号,又因为只需给配对括号中的一个括号染色。只有一种方案,则 \(f_{l,r,0,1}=f_{l,r,1,0}=f_{l,r,0,2}=f_{l,r,2,0}=1\)。
又由于 限制 3,\(f_{l,r,0,0} \quad f_{l,r,1,2} \quad f_{l,r,2,1} \quad f_{l,r,1,1} \quad f_{l,r,2,2}\) 的值为 0,不必赋初值。
括号 \(l\) 与括号 \(r\) 配对,形如 (....),容易知道 \(f_{l,r,0,0} \quad f_{l,r,1,2} \quad f_{l,r,2,1} \quad f_{l,r,1,1} \quad f_{l,r,2,2}\) 的值为 0,与上方证法相似。
剩下的情况我们以\(f_{l,r,0,1}\) 为例: 由于它左端为 0,右端为 1。所以它无法由所有的 \(f_{l,r,x,1}\) 转移而来,否则不符合 限制 3。
我们枚举所有可能的颜色 \(i\) 和 \(j\) ,所以有状态转移:
\(j≠1\) \(\quad\) \(f_{l,r,0,1}+=f_{l+1,r-1,i,j}\)
\(j≠2\) \(\quad\) \(f_{l,r,0,2}+=f_{l+1,r-1,i,j}\)
\(i≠1\) \(\quad\) \(f_{l,r,1,0}+=f_{l+1,r-1,i,j}\)
\(i≠2\) \(\quad\) \(f_{l,r,2,0}+=f_{l+1,r-1,i,j}\)
括号 \(l\) 和括号 \(r\) 不匹配,形如 ..(...).....(...)..,我们分别处理出 \(f_{l,vis_l}\) 与 \(f_{vis_l+1,r}\) 然后相乘。 若 \(vis_l\) 与 \(vis_l+1\) 的颜色不相等需要特判,如果相等则不进行状态转移,因为这两个区间可以合并一起处理。(\(vis\) 为标记与该括号相匹配的另一个括号的位置)
状态转移:\(f_{l,r,i,q}+=f_{l,vis_l,i,j}*f_{vis_l+1,r,p,q}\) 。
最后用递归进行DP就可以了,不要忘了在计算过程与结果初取模,答案为所有颜色方案的加和。时间复杂度 \(O(n^3)\) 。
code:
//处理 vis 函数
for(int i=1;i<=n;i++){if(s[i]=='(') sta.push(i);else{vis[sta.top()]=i;vis[i]=sta.top();sta.pop();}
}//递归 DP
void dfs(int l,int r){if(l+1==r){f[l][r][1][0]=f[l][r][2][0]=f[l][r][0][1]=f[l][r][0][2]=1;}else if(vis[l]==r){dfs(l+1,r-1);for(int i=0;i<=2;i++){for(int j=0;j<=2;j++){if(j!=1) f[l][r][0][1]=(f[l][r][0][1]+f[l+1][r-1][i][j])%mod;if(j!=2) f[l][r][0][2]=(f[l][r][0][2]+f[l+1][r-1][i][j])%mod;if(i!=1) f[l][r][1][0]=(f[l][r][1][0]+f[l+1][r-1][i][j])%mod;if(i!=2) f[l][r][2][0]=(f[l][r][2][0]+f[l+1][r-1][i][j])%mod;}}}else{dfs(l,vis[l]);dfs(vis[l]+1,r);for(int i=0;i<=2;i++){for(int j=0;j<=2;j++){for(int p=0;p<=2;p++){for(int q=0;q<=2;q++){if((j==1&&p==1)||(j==2&&p==2)) continue;f[l][r][i][q]=(f[l][r][i][q]+f[l][vis[l]][i][j]*f[vis[l]+1][r][p][q]%mod)%mod;}}}}}
}//计算答案
int ans=0;
for(int i=0;i<=2;i++){for(int j=0;j<=2;j++){ans=(ans+f[1][n][i][j])%mod;}
}
Recovering BST CF-1025D
二叉搜索树 + 区间 DP(不过只用了二叉搜索树的性质与定义):
题目要求我们判断所给序列能否建立一个二叉搜索树并且满足由边连接的两个顶点权值的最大公约数大于 1。
我们可以假设一个节点的 左儿子和右儿子 都是一个区间 \([l,r]\),则如果 区间 \([l,r]\) 为左儿子,那这个区间的根节点为的父亲为 \(r+1\);如果区间 \([l,r]\) 为右儿子,那这个区间的根节点的父亲为 \(l-1\)。
显然我们可以设状态表示为 \(f_{l,r,k,0/1}\) 表示 \(k\) 为根节点 \(l\) 作为左儿子和 \(r\) 作为右儿子是否合法 0非法/1合法。但是观察数据范围,我们会 MLE ,并且会浪费很多空间来存放无效状态。
既然合起来无法实现,那我们可以将 \(f\) 数组分开,用 \(L_{l,r}\) 分别表示用区间 \([l,r-1]\) 做 \(j\) 的左儿子是否合法,\(R_{l,r}\) 表示用区间 \([l+1,r]\) 做 \(l\) 右儿子是否合法。
我们设 \(k\) 为区间 \([l,r]\) 的根,只要 \(L_{l,k}=R_{k,r}=1\) 则说明状态合法,可以转移,若转移完全,无法继续转移,则输出 \(Yes\),否则输出 \(No\)。
处理 \(L\) 和 \(R\),如果 \(k\) 可以和 \(l-1\) 连边,则 \(R_{l,r}\) 合法,如果 \(k\) 可以和 \(r+1\) 连边,则 \(L_{l,r}\) 合法。易知 \(L_{i,i}=R_{i,i}=1\)。时间复杂度:\(O(n^3)\) 。
code:
for(int i=1;i<=n;i++){for(int j=i+1;j<=n;j++){if(gcd(a[i],a[j])>1){f[i][j]=f[j][i]=1;}}
}for(int l=n;l>=1;l--){for(int r=l;r<=n;r++){for(int k=l;k<=r;k++){if(L[l][k]&&R[k][r]){if(l==1&&r==n){puts("Yes");return 0;}if(f[l-1][k]) R[l-1][r]=1;if(f[k][r+1]) L[l][r+1]=1;}}}
}
puts("No");
Minimum Triangulation CF-1140D
非常有意思的题,有三种解法,我只想到了两种,看完大佬的题解后才明白第三种。
一,区间 DP
很好想到,设状态表示为:\(f_{i,j}\) 表示从 \(i\) 逆时针到 \(j\) 所需要的最小权值,可以暴力枚举割点 \(k\) 计算每一个 \(f\) 的值。状态转移为:\(f_{i,j}=min\{ f_{i,k}+f_{k,j}+i*j*k \}\)。最后的 \(f_{1,n}\) 为答案,时间复杂度 \(O(n^3)\) 。
code:
for(int i=n-2;i>=0;i--){for(int j=i+2;j<=n;j++){for(int k=i+1;k<=j;k++){f[i][j]=min(f[i][j],f[i][k]+f[k][j]+i*j*k);}}
}
二,数学分析
我们发现只要所有三角形都是由 1 释放两条线形成的时,三角剖分的权重最小,显然,我们的答案为:\(\sum_{i=2}^{n-1} i*(i+1)\),我们只需枚举计算即可,时间复杂度 \(O(n)\) 。
三,进一步优化
我们在二中的式子加上 \(1×2\) 最后在减去。由于所有式子可以化为:\(n(n-1)=\frac{1}{3}((n-1)n(n+1)-(n-2)(n-1)n)\) 。所以我们可以将原式子化为;\(\frac{1}{3}(n-1)n(n+1)-2\),这样就可以快速计算答案了,时间复杂度为 \(O(1)\) 。
Array Shrinking CF-1312E
很板的一道 区间DP:
题目描述:给定一个序列 \(a\),如果 \(a_i=a_{i+1}\) ,我们可以用 \(a_i+1\) 或 \(a_{i+1}+1)\) 来替换它,求可以得到的最小序列长度。
我们设状态表示为 \(f_{i,j}\) 表示区间 \([l,r]\) 合并所得的最小长度。但是我们发现,如果这样维护,我们无法在下次转移中知道合并后的区间 &[l,r]& 中的值,所以我们考虑令设 \(w_{l,r}\) 表示区间 \([l,r]\) 的和。
所以状态转移为:\(f_{l,r}=min\{ f_{l,k}+f_{k+1,r} \}\)
如果 \(f_{l,k}=f_{k+1,r}=1\) 则说明区间 \([l,r]\) 的长度为 2,如果此时 \(w_{l,k]=w_{k+1,r]\),则说明区间内的两个值相等,可以合并,则:\(f_{l,r}=1\) \(\quad\) \(w_{l,r}=w_{l,k}+1\)。
最后答案为 \(f_{1,n}\) ,时间复杂度为 \(O(n^3)\)
code:
for(int i=2;i<=n;i++){for(int l=1;l+i-1<=n;l++){int r=l+i-1;for(int k=l;k<r;k++){f[l][r]=min(f[l][r],f[l][k]+f[k+1][r]);if(f[l][k]==1&&f[k+1][r]==1&&w[l][k]==w[k+1][r]){f[l][r]=1;w[l][r]=w[k+1][r]+1;}}}
}
Connecting Vertices CF-888F
非常好的题号,使我的代码旋转
我们可以先把多边形拉成一条链,题目条件就可以转化为任意两条线段不能相交。
考虑 区间 DP ,设 \(f_{l,r}\) 为将区间 \([l,r]\) 连在一起的方案数。
如果 \([l,r]\) 连边,则为 \(f_{l,r}+=f_{l,k}*f{k+1,r}\)
如果 \({l,r}\) 不连边,则为 \(f_{l,r}+=f_{l,k}*f_{k,r}\)
但是在实际运行中会出错,会错误统计相关信息,所以要考虑修改状态表示,发现上述会考虑连边与不连边两种情况,我们将它具化到状态表示为 \(f_{l,r,0/1}\) 表示区间 \([l,r]\) 不连边(0)或连边(1)的的方案数。我们将 \(f_{l,r,0}+f_{l,r,1}\) 即为总方案数。
状态转移方程:
处理 \(f_{l,r,0}\),枚举断点转移:\(f_{l,r,0}=(f_{l,k,0}+f_{l,k,1})*(f_{k+1,r,0}+f_{k+1,r,1})\) 。
处理 \(f_{l,r,1}\),枚举断点,但是要算 \([l,k]\) 之间强制连边的方案,需要满足 \(g_{l,k}=1\) \(f_{l,r,1}=f_{l,k,0}*(f_{k,r,0}+f_{k,r,1})\) 。
最后答案为 \(f_{1,n,0}+f_{1,n,1}\) ,初始值 \(f_{i,i,0}=1\) 时间复杂度为 \(O(n^3)\) 。
code:
for(int i=2;i<=n;i++){for(int l=1;l+i-1<=n;l++){int r=l+i-1;if(g[l][r]){for(int k=l;k<r;k++){f[l][r][0]=(f[l][r][0]+(f[l][k][0]+f[l][k][1])*(f[k+1][r][0]+f[k+1][r][1])%mod)%mod;}}for(int k=l+1;k<r;k++){if(g[l][k]){f[l][r][1]=(f[l][r][1]+f[l][k][0]*(f[k][r][0]+f[k][r][1])%mod)%mod;} }}
}
Clear the String CF-1132F
又事一道很板的区间 DP :
题目描述:给定一个字符串,我们可以删去连续的字符相同的字串,求删除完这个字符串的最小操作次数。
状态表示:\(f_{l,r}\) 表示删除区间 \([l,r]\) 的最小操作次数。
枚举断点,判断区间能否被一次删除,合并两个区间的操作次数。由此得状态转移为:\(f_{l,r}=min\{ f_{l,k}+f_{k+1,r}+(s_l ≠ s_k) \}\) 。
最终答案为 \(f_{1,n}\) 复杂度 \(O(n^3)\) 。
code:
for(int i=2;i<=n;i++){for(int l=1;l+i-1<=n;l++){int r=l+i-1;for(int k=l;k<r;k++){f[l][r]=min(f[l][r],f[l][k]+f[k+1][r]-(s[l]==s[r]));}}
}
后言:
\(\quad\) vjudge把我提交记录吐出来了,可喜可贺,可喜可贺😄。