UVALive 4394 String painter
题目大意:有两个字符串A,B,一次刷可以把A串一段刷成同一个字母,问至少要刷几次才能把A串变成B串。串长≤100。
本来以为是个很简单的区间DP,后来发现直接区间DP是不行的,这玩意有后效性:刷完一整块之后这一块就变了。
对于这种问题不如干脆利落一点,直接把 f[ ][ ]设成将空串(即不需要考虑A与B的相同)刷成B串的最小次数。
这个时候的转移方程就是:
for(int i=1;i<=n;++i)f[i][i]=1; for(int len=1;len<n;++len) for(int i=1;i+len<=n;++i){ int j=i+len;f[i][j]=f[i+1][j]+1; for(int k=i+1;k<=j;++k) if(B[i]==B[k]) f[i][j]=min(f[i][j],f[i+1][k]+f[k+1][j]); }
这个转移方程是很巧妙的。
首先赋值最坏情况,作为最大值,然后枚举k,进行更新。
转移方程的思想是:如果在B串中,i和k是一样的,就可以划分区间进行更新。
结论:在涂色的时候,区间的一个端点,一定可以作为第一个涂色。
证明:区间涂色有两种方法:分成左右 / 先整个涂一遍再在里面涂。
分成左右是子问题,先整个涂的话就可以先选择这个端点涂。
所以在转移时,如果i和k是一样的,则可以有f[i][k]=f[i+1][k],只需要在涂k的时候把整个区间涂上就可以了。
同样可以用上面的子问题思考方式证明。
这样就把"空串变B串"解决了。但是我们是要把A串变B串,答案还需要统计一遍。
设g[i]表示A串从1到i全部被涂成B的最小步数,用f来更新g。
这个时候转移方程就是这样:
g[1]=A[1]==B[1]?0:1; for(int i=2;i<=n;++i){ if(A[i]==B[i]){g[i]=g[i-1];continue;} g[i]=f[1][i]; for(int j=1;j<i;++j) g[i]=min(g[i],g[j]+f[j+1][i]); }
这个转移也是比较有意思的,这里就不做分析了。
对于这种显然只能用DP来做的、一般的转移又有后效性的题,不妨状态设大气一点,直接忽略后效性带来的影响,再变换方式统计答案。
#include <iostream> #include <cstdio> #include <cstdlib> #include <algorithm> #include <vector> #include <cstring> #include <queue> #include <complex> #include <stack> #define LL long long int #define dob double #define FILE "4394" using namespace std; const int N = 110; int n,f[N][N],g[N]; char A[N],B[N]; inline int gi(){ int x=0,res=1;char ch=getchar(); while(ch>'9'||ch<'0'){if(ch=='-')res*=-1;ch=getchar();} while(ch<='9'&&ch>='0')x=x*10+ch-48,ch=getchar(); return x*res; } inline void solve(){ n=strlen(A+1); for(int i=1;i<=n;++i)f[i][i]=1; for(int len=1;len<n;++len) for(int i=1;i+len<=n;++i){ int j=i+len;f[i][j]=f[i+1][j]+1; for(int k=i+1;k<=j;++k) if(B[i]==B[k]) f[i][j]=min(f[i][j],f[i+1][k]+f[k+1][j]); } g[1]=A[1]==B[1]?0:1; for(int i=2;i<=n;++i){ if(A[i]==B[i]){g[i]=g[i-1];continue;} g[i]=f[1][i]; for(int j=1;j<i;++j) g[i]=min(g[i],g[j]+f[j+1][i]); } printf("%d\n",g[n]); } int main(){ freopen(FILE".in","r",stdin); freopen(FILE".out","w",stdout); while(~scanf("%s%s",A+1,B+1))solve(); fclose(stdin);fclose(stdout); return 0; }