[NOIP模拟赛]单词

【问题描述】

有一个有字母表,一共有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了



NOI(全国青少年信息学奥林匹克竞模拟的测试数据是指用于评测参选手的程序的输入和对应的输出。测试数据是非常重要的,因为它决定了参选手的程序能否正确地解决问题。 在NOI模拟中,测试数据具有以下特点: 1.充分覆盖:测试数据应涵盖各种可能的输入情况,包括边界条件和极端情况。通过提供不同的测试数据,可以考察选手对问题的全面理解和解决能力。 2.随机性和均衡性:为了公平起见,测试数据应该是随机生成的,而不是针对某个特定算法或解法设计的。同时,测试数据应该是均衡的,即各种情况的概率应该大致相等,以避免偏向某些解法。 3.合理性和可行性:测试数据应该是合理和可行的,即符合题目要求的输入数据,并且是选手能够通过编写程序来处理的。测试数据应该考虑到程序的限制和时间复杂度,以充分测试选手的编程能力。 NOI模拟的测试数据通常由经验丰富的考题组负责生成。他们会根据题目的要求和限制,设计出一组合理、充分、随机和均衡的测试数据,以确保参选手的程序在各种情况下都能正确运行,并且能通过性能测试。 总之,测试数据在NOI模拟中起到了至关重要的作用,它既考察了选手对问题的理解和解决能力,又提高了选手编程的技巧和效率。同时,合理和恰当的测试数据也是公平竞的保证,确保每个参选手有相同的机会和条件进行竞争。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值