T1
构思分讨。
很自然地,我们令 \(dp_{i,j}\) 表示 \([i,j]\) 的初始字母方案数。
但是这个状态信息过少,不足以解决此问题。
于是我们增加状态维度,令 \(dp_{i,j,0/1/2/3}\) 表示 \([i,j]\) 是否能由 W/I/N/D 演变而来。
答案即为 \(dp_{1,n,0} \mid \mid dp_{1,n,1} \mid \mid dp_{1,n,2} \mid \mid dp_{1,n,3}\)。
显然有初始状态 \(dp_{i,j,0/1/2/3} = 1\),因为无论哪个字母,它本身即可演变为它。其余为 \(\infty\)。
关于转移,我们考虑枚举中转点的套路去做。
对于区间 \([i,j]\),我们确定中转点 \(k\) 后,仅需枚举 \(c1,c2,c\),并检查 \(dp_{i,k,c1}=dp_{k+1,j,c2}=dp_{i,j,c}=1\),且 \(c1,c2\) 能演变为 \(c\) 即可。
code
#include<bits/stdc++.h>
using namespace std;
const int N=2e2+5;
string t;
int n,a[4];
int num[N],ok[4][4][4];
int dp[N][N][4];
int main(){
for(int i=0;i<4;i++) cin>>a[i];
num['W']=0,num['I']=1,num['N']=2,num['G']=3;
for(int i=0;i<4;i++){
for(int j=1;j<=a[i];j++){
char c1,c2; cin>>c1>>c2;
ok[i][num[c1]][num[c2]]=1;
}
}
cin>>t,n=t.size(),t='#'+t;
for(int i=1;i<=n;i++) dp[i][i][num[t[i]]]=1;
for(int i=1;i<=n;i++){
for(int j=1;j+i-1<=n;j++){
int s=j,e=i+j-1;
for(int k=s;k<e;k++)
for(int c=0;c<4;c++)
for(int c1=0;c1<4;c1++)
for(int c2=0;c2<4;c2++)
dp[s][e][c]|=(dp[s][k][c1]&&dp[k+1][e][c2]&&ok[c][c1][c2]);
}
}
bool f=0;
if(dp[1][n][0]) cout<<'W',f=1;
if(dp[1][n][1]) cout<<'I',f=1;
if(dp[1][n][2]) cout<<'N',f=1;
if(dp[1][n][3]) cout<<'G',f=1;
if(!f) cout<<"The name is wrong!";
return 0;
}
T2
构思分讨 \(\times \ 2\)。
令 \(dp_{i,j}\) 表示关掉 \([i,j]\) 内所有灯的最少功率。
但是,我们在关 \([i,j]\) 时,最后停在那个点将会影响到答案。
进一步分析,我们发现,关 \([i,j]\) 时,有且仅有可能停在 \(i,j\) 两点上。
这是因为,我们如果停在了满足 \(i<p<j\) 的点 \(p\),则我们会走一遍 \(i \to j \to p\) 或 \(j \to i \to p\),这一定不会比只走 \(i \to j\) 或 \(j \to i\) 更优。
于是我们令 \(dp_{i,j,0/1}\) 表示关掉 \([i,j]\) 且停在 \(i/j\) 的最小功率。
答案即为 \(\min(dp_{1,n,0},dp_{1,n,1})\)。
关于初始状态,因为每次出发时总会将 \(c\) 关掉,
因此有初始状态 \(dp_{i,i,0/1}=\lvert x_i-x_c \rvert + sum_n-w_c\),其余为 \(\infty\)。
其中 \(x_i\) 表示位置,\(w_i\) 表示功率,\(sum_i\) 表示 \(w_i\) 的前缀和。
接下来考虑转移。
这题肯定不能用枚举中转点套路,因为无法确定终止位置。
于是用端点转移套路,具体地:
-
对于 \(dp_{i,j,0}\),可以 \(i+1 \to j \to i\) 或 \(j \to i+1 \to i\)。
因此有转移
-
对于 \(dp_{i,j,1}\),可以 \(i \to j-1 \to j\) 或 \(j-1 \to i \to j\)。
因此有转移
直接做就完了。
code
#include<bits/stdc++.h>
using namespace std;
int n,c;
int x[53],w[53];
int sum[53];
int dp[53][53][2];
int main(){
cin>>n>>c;
for(int i=1;i<=n;i++) cin>>x[i]>>w[i],sum[i]=sum[i-1]+w[i];
memset(dp,0x3f,sizeof(dp));
for(int i=1;i<=n;i++) dp[i][i][0]=dp[i][i][1]=abs(x[i]-x[c])*(sum[n]-w[c]);
for(int i=2;i<=n;i++){
for(int j=1;j+i-1<=n;j++){
int s=j,e=j+i-1;
dp[s][e][0]=min(dp[s+1][e][0]+(x[s+1]-x[s])*(sum[n]-sum[e]+sum[s]),dp[s+1][e][1]+(x[e]-x[s])*(sum[n]-sum[e]+sum[s]));
dp[s][e][1]=min(dp[s][e-1][0]+(x[e]-x[s])*(sum[n]-sum[e-1]+sum[s-1]),dp[s][e-1][1]+(x[e]-x[e-1])*(sum[n]-sum[e-1]+sum[s-1]));
}
}
cout<<min(dp[1][n][0],dp[1][n][1]);
return 0;
}
作业 T1
见 TJ。
作业 T2
构思分讨 \(\times \ 3\)。
法一:
令 \(dp_{i,j,0/1}\) 表示将 \([i,j]\) 变为颜色均为 \(c_i/c_j\) 的连通块的最小步数。
答案即为 \(\min(dp_{1,n,0},dp_{1,n,1})\)。
显然有初始状态 \(dp_{i,i,0/1}\)=0,其余为 \(\infty\)。
从状态易知,此题应用端点转移法进行转移。
于是有转移:
\[\begin{cases} dp_{i,j,0}=\min(dp_{i,j,0},dp_{i+1,j,0}+(c_i \neq c_{i+1}))\\ dp_{i,j,0}=\min(dp_{i,j,0},dp_{i+1,j,1}+(c_i \neq c_j))\\ dp_{i,j,1}=\min(dp_{i,j,1},dp_{i,j-1,0}+(c_j \neq c_i))\\ dp_{i,j,1}=\min(dp_{i,j,1},dp_{i,j-1,1}+(c_j \neq c_{j-1})) \end{cases} \]法一 code
#include<bits/stdc++.h>
using namespace std;
const int N=5e3+5;
int n,c[N];
int dp[N][N][2];
int main(){
cin>>n;
for(int i=1;i<=n;i++) cin>>c[i];
memset(dp,0x3f,sizeof(dp));
for(int i=1;i<=n;i++) dp[i][i][0]=dp[i][i][1]=0;
for(int i=2;i<=n;i++){
for(int j=1;j+i-1<=n;j++){
int s=j,e=j+i-1;
dp[s][e][0]=min(dp[s][e][0],dp[s+1][e][0]+(c[s]!=c[s+1]));
dp[s][e][0]=min(dp[s][e][0],dp[s+1][e][1]+(c[s]!=c[e]));
dp[s][e][1]=min(dp[s][e][1],dp[s][e-1][0]+(c[e]!=c[s]));
dp[s][e][1]=min(dp[s][e][1],dp[s][e-1][1]+(c[e]!=c[e-1]));
}
}
cout<<min(dp[1][n][0],dp[1][n][1]);
return 0;
}
法二:
先预处理,将所有相同颜色全部合并到一起,组成一个色块。
接着令 \(dp_{i,j}\) 表示将 \([i,j]\) 变为一个连通块的最小步数。
答案即为 \(dp_{1,tot}\),\(tot\) 表示色块个数。
显然有初始状态 \(dp_{i,i}\)=0,其余为 \(\infty\)。
仍然采用端点转移法进行转移。
于是有转移:
\[\begin{cases} dp_{i,j}=\min(dp_{i,j},dp_{i+1,j-1}+1)(cc_i=cc_j)\\ dp_{i,j}=\min(dp_{i,j-1}+1,dp_{i+1,j}+1)(cc_i \neq cc_j) \end{cases} \]其中 \(cc_i\) 表示第 \(i\) 个色块的颜色。
法二 code
#include<bits/stdc++.h>
using namespace std;
const int N=5e3+5;
int n,tot,pre;
int c[N],cc[N];
int dp[N][N];
int main(){
cin>>n;
for(int i=1;i<=n;i++) cin>>c[i];
for(int i=1;i<=n;i++){
if(c[i]!=pre) cc[++tot]=c[i]; pre=c[i];
}
memset(dp,0x3f,sizeof(dp));
for(int i=1;i<=tot;i++) dp[i][i]=0;
for(int i=2;i<=tot;i++){
for(int j=1;j+i-1<=tot;j++){
int s=j,e=j+i-1;
if(cc[s]==cc[e]) dp[s][e]=min(dp[s][e],dp[s+1][e-1]+1);
else dp[s][e]=min(dp[s][e-1]+1,dp[s+1][e]+1);
}
}
cout<<dp[1][tot];
return 0;
}