【问题描述】
有一个有字母表,一共有N行M列,你从左上角开始出发,目的地是右下角。每次你只能往右或往下走一步。将你经过的格子里面的字母按照访问顺序组成一个单词。求你能得到的字典序最小的单词是什么?
【输入】
第一行包含N和M,(1<=N,M<=2000)
接下来N行,每行包含M个小写字母。
【输出】
输出最小字典序的单词。
40%的数据,每个格子的右、下的字母不同。
【输出样例】
4 5
ponoc
ohoho
hlepo
mirko
4 5
bbbbb
bbbbb
bbabb
bbbbb
2 5
qwert
yuiop
【输出样例】
pohlepko
bbbbabbb
qweiop
题解:
题目要求字典序最小,因此不能简单地把字母转换成数字进行dp求和最小的,因为ab优于ba,ae优于bc。
正解:运用贪心的思路,每次从一个位置出发,选其右方和下方小的字符,把它的x放入队列,相同则两个都放入。
从左上到右下, 一共会走n+m-1步,而对于当前步step可以到达的点,其位置一定满足x+y-1=step,这样就可以通过队列中的x确定字符位置。
提醒:最好用滚动数组模拟队列,否则可能会T。
第一个代码是顺着思路码的,虽然长但是便于理解。第二个代码是经过简化的。
#include<iostream> #include<cstring> #include<cstdio> #include<cstdlib> #include<cmath> #include<algorithm> using namespace std; const int N=2005; int n, m, q[2][N], l[2], r[2];//q: 记录x l、r: 记录队列左右界限 char s[N][N], note[N<<1]; bool p, vis[N];//p: 记录当前队列 vis: 去重 int main() { scanf( "%d%d", &n, &m ); for( int i=1; i<=n; i++ ) scanf( "%s", s[i]+1 ); note[1]=s[1][1]; l[p]=1; r[p]=0; q[p][ ++r[p] ]=1; int all=n+m-1; for( int step=2; step<=all; step++) { memset( vis, 0, sizeof vis ); p^=1; l[p]=1; r[p]=0; char minc='z'+1;//记录当前队列的最小值 while( l[ p^1 ]<=r[ p^1] ) { int x=q[ p^1 ][ l[ p^1 ] ]; l[ p^1 ]++; int y=step-x;//确定当前位置 if( x<n && y<m ) {//可以向右方和下方移动 if( s[x+1][y]<s[x][y+1] ) {//向下方移动更优 if( s[x+1][y]<minc ) l[p]=1, r[p]=0;//更新队列 if( s[x+1][y]<=minc && !vis[x+1] ) { q[p][ ++r[p] ]=x+1; vis[x+1]=1; minc=s[x+1][y]; } } else if( s[x+1][y]>s[x][y+1] ) {//向右方移动更优 if( s[x][y+1]<minc ) l[p]=1, r[p]=0; if( s[x][y+1]<=minc && !vis[x] ) { q[p][ ++r[p] ]=x; vis[x]=1; minc=s[x][y+1]; } } else {//相同 if( s[x][y+1]<minc ) l[p]=1, r[p]=0; if( s[x][y+1]<=minc ) { if( !vis[x] ) q[p][ ++r[p] ]=x, vis[x]=1; if( !vis[x+1] ) q[p][ ++r[p] ]=x+1, vis[x+1]=1; minc=s[x][y+1]; } } } else if( x<n ) {//只能向下方移动 if( s[x+1][y]<minc ) l[p]=1, r[p]=0;//更新队列 if( s[x+1][y]<=minc && !vis[x+1] ) { q[p][ ++r[p] ]=x+1; minc=s[x+1][y]; vis[x+1]=1; } } else {//只能向右方移动 if( s[x][y+1]<minc ) l[p]=1, r[p]=0; if( s[x][y+1]<=minc && !vis[x] ) { q[p][ ++r[p] ]=x; minc=s[x][y+1]; vis[x]=1; } } } note[step]=minc; } printf( "%s\n", note+1 ); return 0; }
#include<cstring> #include<cstdio> #define re(p) p^1 const int N=2005; int n, m, q[2][N], l[2], r[2]; char s[N][N], note[N<<1], minc; bool p, vis[N]; void Solve( int x, int y ) { if( s[x][y]<minc ) l[p]=1, r[p]=0; if( s[x][y]<=minc && !vis[x] ) q[p][ ++r[p] ]=x, vis[x]=1, minc=s[x][y]; } int main() { scanf( "%d%d", &n, &m ); for( int i=1; i<=n; i++ ) scanf( "%s", s[i]+1 ); note[1]=s[1][1]; l[p]=1; r[p]=0; q[p][ ++r[p] ]=1; int all=n+m-1; p^=1; for( int step=2; step<=all; step++, p^=1 ) { minc='z'+1; l[p]=1; r[p]=0; while( l[ re(p) ]<=r[ re(p) ] ) { int x=q[ re(p) ][ l[ re(p) ] ], y=step-x; l[ re(p) ]++; if( x<n && y<m ) { if( s[x+1][y]<s[x][y+1] ) Solve( x+1, y ); else if( s[x+1][y]>s[x][y+1] ) Solve( x, y+1 ); else Solve( x+1, y ), Solve( x, y+1 ); } else if( x<n ) Solve( x+1, y ); else Solve( x, y+1 ); } note[step]=minc; memset( vis, 0, sizeof vis ); } printf( "%s\n", note+1 ); return 0; }
P.S.据说还有一种做法:开滚动数组char s[2][MAXN][MAXN*2]记录字符串,直接用字符串进行比较,就可以用dp了
[NOIP模拟赛]单词
最新推荐文章于 2023-08-20 14:15:57 发布