[NOIP2023] 词典 题解

[NOIP2023] 词典 题解


知识点

贪心,排序。


题意简述

\(n\) 个两两不同的长度都为 \(m\) 的单词,每个单词内部可以随意排序。

问在对这 \(n\) 个单词排序后,有哪些有可能成为字典序最小的单词。


分析

首先,如果要让某个单词成为字典序最小的,那么我们只要让它内部升序排序,就达到了该单词能够达到的最小字典序。

但是如果其它单词还有比他小的怎么办呢?我们尝试让它们也变的比该单词大即可,让它们全部降序排序,就达到了它们能够达到的最大字典序。

可是我们是要对全部的单词都做一遍,全部比较一遍就变成了 \(O(n^2m)\),肯定是吃不消的,考虑优化。

发现一个显然的事:我们在考虑某一个单词的时候,只需要和其它中的降序排序后最小的进行比较即可,而除了本身就是最小的那个,对于其它的最小就都固定为它了, 那么预处理出来就可以解决这个问题。


代码

时间复杂度:\(O(nm\log_2{m})\),空间复杂度:\(O(nm)\)

#define Plus_Cat "dict"
#include<bits/stdc++.h>
#define INF 0x3f3f3f3f
#define ll long long
#define RCL(a,b,c,d) memset(a,b,sizeof(c)*(d))
#define tomin(a,...) ((a)=min({(a),__VA_ARGS__}))
#define tomax(a,...) ((a)=max({(a),__VA_ARGS__}))
#define FOR(i,a,b) for(int i(a); i<=(int)(b); ++i)
#define DOR(i,a,b) for(int i(a); i>=(int)(b); --i)
#define EDGE(g,i,x,y) for(int i(g.h[x]),y(g[i].v); ~i; y=g[i=g[i].nxt].v)
using namespace std;
constexpr int N(3e3+10);
char tmp[N];
char s[N][N];
int n,m,mi;
bool Greater(char *A,char *B) {
	FOR(i,1,m)if(A[i]^B[i])return A[i]<B[i];
	return 0;
}
namespace Subtask1 {
	bool Check() {
		return n<=300;
	}
	int Cmain() {
		if(n==1)return puts("1"),0;
		FOR(i,1,n) {
			FOR(j,1,m)tmp[j]=s[i][j];
			sort(tmp+1,tmp+m+1,greater<>());
			FOR(j,1,m)s[i][j]=tmp[j];
		}
		FOR(i,1,n) {
			FOR(j,1,m)tmp[j]=s[i][m-j+1];
			bool ans(1);
			FOR(j,1,n)if(i!=j&&!Greater(tmp,s[j])) {
				ans=0;
				break;
			}
			putchar(ans|'0');
		}
		puts("");
		return 0;
	}
}
namespace Subtask {
	int Cmain() {
		if(n==1)return puts("1"),0;
		FOR(i,1,n) {
			FOR(j,1,m)tmp[j]=s[i][j];
			sort(tmp+1,tmp+m+1,greater<>());
			FOR(j,1,m)s[i][j]=tmp[j];
		}
		mi=1;
		FOR(i,2,n)if(Greater(s[i],s[mi]))mi=i;
		FOR(i,1,n) {
			if(i==mi) {
				putchar('1');
				continue;
			}
			FOR(j,1,m)tmp[j]=s[i][m-j+1];
			sort(tmp+1,tmp+m+1),putchar(Greater(tmp,s[mi])|'0');
		}
		puts("");
		return 0;
	}
}
int main() {
#ifdef Plus_Cat
	freopen(Plus_Cat ".in","r",stdin),freopen(Plus_Cat ".out","w",stdout);
#endif
	scanf("%d%d",&n,&m);
	FOR(i,1,n)scanf("%s",s[i]+1);
	if(Subtask1::Check())return Subtask1::Cmain();
	return Subtask::Cmain();
}

总结

优点

  1. 这题打了一个部分分进行对拍,是正好适合第一题的策略。

缺点

  1. 一开始读题读错了。

  2. 打得太慢了。

  3. 没有用 std::string,这题非常适合用 STL,但是由于输入输出较麻烦就没用。

    • 输入:

      直接 getchar() 快读。

    • 输出:

      putchar() 分解了、转 c_str()data()

    #define isblank(ch) ((ch)==' '||(ch)=='\0'||(ch)=='\n'||(ch)=='\r'||(ch)==(EOF))
    //...
    char getc() {
    	return getchar();
    }
    void operator ()(string &x) {
    	static char ch(0);
    	x.clear();
    	while(ch=getc(),isblank(ch));
    	do x.push_back(ch);
    	while(ch=getc(),!isblank(ch));
    }
    //...
    void putc(char c) {
    	putchar(c);
    }
    void operator ()(const string s,const char lst='\n') {
    	printf("%s",s.data()),putc(lst);
    }
    void operator ()(const string s,const char lst='\n') {
    	printf("%s",s.c_str()),putc(lst);
    }
    void operator ()(const string s,const char lst='\n') {
    	for(const char &c:s)putc(c);
    	putc(lst);
    }
    
posted @ 2024-11-22 21:24  Add_Catalyst  阅读(22)  评论(0)    收藏  举报