算法学习记录:[NOIP2010]机器翻译

题目链接

https://ac.nowcoder.com/acm/contest/20960/1003

记录:

这道题我真的吃💩,我脑子抽了,用滑窗去做,然后下标换算被自己弄的特别复杂(下标套数组再套一层st数组,方便直接查),
把自己绕晕了,555(还没找出问题在哪,但应该是下标的问题,本来应该踢出去的数字还在里面)。
最后队列直接搞定了,以后记住如果下标太乱,先写带数据结构的再手动模拟数据结构

AC代码(手写队列)

#include <iostream>

using namespace std;

const int M = 10000;
int n, m, ans;
int hh, tt = -1;
bool st[M];
int q[M];

int main()
{
    cin >> m >> n;
    
    for (int i = 0; i < n; ++ i)
    {
        int x; cin >> x;
        if (!st[x])
        {
            if (tt - hh + 1 == m) 
                st[q[hh ++ ]] = false;
            
            st[x] = true;
            q[ ++ tt] = x;
            ans ++ ;
        }
    }
    
    cout << ans;
}

AC代码(STL队列)

#include <iostream>
#include <cstring>
#include <queue>
 
using namespace std;
 
const int M = 10000;
int n, m, ans;
bool st[M];
queue<int> q;
 
int main()
{
    cin >> m >> n;
     
     
    for (int i = 0; i < n; ++ i)
    {
        int x; cin >> x;
         
        if (!st[x])
        {
            if (q.size() == m)
            {
                st[q.front()] = false;
                q.pop();
            }
             
            st[x] = true;
            q.push(x);
            ans ++ ;
        }
    }
     
    cout << ans;
}

AC代码(暴力)

自己写的暴力版并不好,因为我们的操作都是拿一个放入一个,所以内存在某个时间点满了以后,内存就一直是满的
所以并不需要枚举 sz,直接让 m -- 至 0 就可以代表内存已满。
也不需要用 cnt 来表示内存所有数字的编号,因为最大的编号再大也不可能超过 i,所以我们可以用 i 来代表 a 数组中的编号,进行枚举。
利用好,可以用的信息。但是在意识不到如何得到自己想要的信息的时候,添一个变量\数组也是比较舒服方法。

#include <iostream>
#include <cstring>

using namespace std;

// 思路:内存中的用st进行标记,暴力枚举内存每次踢掉内存中最早的

const int N = 1005;
const int MAXN = 0x3f3f3f3f;

int n, m, cnt, ans;
bool st[N];
int a[N];

int main()
{
    cin >> m >> n;
    
    memset(a, 0x3f, sizeof a);
	
    for (int i = 0; i < n; ++ i)
    {
	int x; cin >> x;
	int sz = 0;
	if (st[x]) continue;
		
        // st[x] = false, 才需要找到最早的踢掉
        
	for (int j = 0; j <= N; ++ j)
	    if (st[j]) ++ sz;
        
	if (sz == m)
	{
	    int mi = MAXN, mindex = MAXN;
	    for (int j = 0; j <= N; ++ j)
		if (st[j])
                  if (mi > a[j]) 
                  {
                       mi = a[j];
                       mindex = j;
                  }	
	     st[mindex] = false;
	}		
		
        ans ++ , st[x] = true, a[x] = cnt ++ ;
    }

    cout << ans;

    return 0;
}	

AC代码(针对暴力的优化)

注意不要 a[i] = x ,这样得到数组不是连续的,会导致队头下标的计算错误。

#include <iostream>
#include <cstring>
using namespace std;

// 因为靠前的元素一定是已经丢出去的,所以我们只需要判断末尾的m个元素即可
// 将值存入a[],判断末尾的m个元素
const int MAXN = 1005;

int n, m, tt, ans;
int st[MAXN], a[MAXN];

int main()
{
    cin >> m >> n;
    memset(a, -1, sizeof a);
    for (int i = 0; i < n; ++ i)
    {
        int x; cin >> x;
        
        // 先判断存在,再加入
        bool flag = false;
        for (int j = i; j >= tt - m + 1; -- j)
        {
            if (j < 0) break;
            if (a[j] == x)            // 存在
                flag = true;
        }
        
        if (!flag) 
        {
            a[ ++ tt] = x;
            ans ++ ;   
        }
    }

    cout << ans;
    return 0;
}

AC代码(二重优化,队列的雏形出来啦)

#include <iostream>

using namespace std;

// 内层循环枚举的目的就是为了找出是否出现过的数字,
// st[] 照样可以找到出现过的数字,O(1) 优于 O(N),所以并不需要一一枚举。
// 内存的范围已经确定了,内存中的数字也已经确定了,
// 优化的条件也就具备了。

const int N = 1005;
int n, m, ans, tt = -1;
int st[N], a[N];

int main()
{
	cin >> m >> n;
	
	for (int i = 0; i < n; ++ i)
	{
		int x; cin >> x;
		
		if (st[x]) continue;		// 过滤掉内存中的值
		
		a[ ++ tt] = x;
		st[x] = true;
		ans ++ ;
        
                // ps:最开始的情况,如果窗口在1的位置,那么必然0是窗口前的位置
                //     所以严格 > 0。
                //     tt-m+1,是队头的位置,那么前面的位置自然减去 1。
		if (tt - m + 1 > 0) st[a[tt - m + 1 - 1]] = false;	
	}
	
	cout << ans;
	return 0;
}
posted @ 2023-05-19 13:47  想个昵称好难ABCD  阅读(23)  评论(0编辑  收藏  举报