天天快乐编程2020年OI集训队 训练10题解

本次训练题目类型为模拟。在我们的比赛中,往往会存在这样的题目,题目不难,但是比较繁琐,如果要写对需要我们先花时间去题目进行分析,然后思考一些小的点,然后再动手去敲代码,这种题被称为模拟题,也有人叫他暴力,暴力出奇迹。

1.4882: 珠心算测验

NOIP2014普及组T1
题意也就是统计n个数两两相加后在数列里存在。
由于数字比较小,我们可以枚举所有的结果,让后把它放进桶里。

#include <bits/stdc++.h>
using namespace std;
//20005就够用,最大值是10000+10000=20000
const int N = 20005;
//t是桶,M1[i]表示值为i的数在集合中两两相加出现了几次,M2[i]表示值为i的数是否在原集合中
int M1[N], M2[N];
int n, a[105], ans, maxn;
int main()
{
    cin >> n;
    for (int i = 1; i <= n; i++)
    {
        cin >> a[i];
        M2[a[i]] = 1;
    }
    //暴力枚举求出可以加出的数
    for (int i = 1; i < n; i++)
    {
        for (int j = i + 1; j <= n; j++)
        {
            M1[a[i] + a[j]]++;
            //求求出数中最大值
            maxn = max(maxn, a[i] + a[j]);
        }
    }
    //只需要枚举到最大值即可
    for (int i = 1; i <= maxn; i++)
    {
        //判断是否满足,满足就ans++
        if (M1[i] > 0 && M2[i])
            ans++;
    }
    cout << ans;
    return 0;
}

candy0014的暴力寻找

#include <bits/stdc++.h>
using namespace std;
int n,a[105],ans;
int main()
{
    cin>>n;
    for(int i=0;i<n;i++) cin>>a[i];
    sort(a,a+n);
    for(int k=2;k<n;k++) 
        for(int i=1;i<k;i++)
            for(int j=0;j<i;j++)
                if(a[k]==a[i]+a[j])
                {
                    ans++,i=k;
                    break;
                }
    cout<<ans<<"\n";
    return 0;
}

直接用STL的set也是很舒服的

#include <bits/stdc++.h>
using namespace std;
//查找集合
set<int> num;
//去重集合
set<int> res;
int n, a[105], tmp;
int main()
{
    cin >> n;
    for (int i = 1; i <= n; ++i)
    {
        cin >> a[i];
        //用集合方便查找
        num.insert(a[i]);
    }
    for (int i = 1; i <= n; ++i)
    {
        for (int j = i + 1; j <= n; ++j)
        {
            //求出所枚举的两数之和
            int tmp = a[i] + a[j];
            //直接在集合中查找tmp
            if (num.count(tmp))
            {
                //直接插入,会自动去重
                res.insert(tmp);
            }
        }
    }
    cout << res.size(); //直接输出集合内元素数量
    return 0;
}

2.4873: 表达式求值

NOIP2013普及组T2
给你一个只包含加减的式子,让你求值。
题目说当答案长度多于4位时,请只输出最后4位,前导0不输出。为什么要有这个条件呢,因为想让你用随时取余定理,加乘是可以随时取余的
我们自己会怎么计算呢,先把乘法算法,再计算加减,所以就可以用栈的思想去做,但是处理的过程中我们要去解决这些符号

#include <bits/stdc++.h>
using namespace std;
//一个存数字并在最后把它们相加的栈
stack<int> S;
int main()
{
	int a, b;
	char c;
	//先输入一个数,以后符号+数字输入,假设前面是0+
	cin >> a;
	//压入栈中
	S.push(a % 10000);
	while (cin >> c >> b)
	{
		//若是乘法,将*之前的数字与*之后的数字积存入
		if (c == '*')
		{
			int x = S.top();
			S.pop();
			//计算乘积,并压入栈
			S.push(x * (b % 10000) % 10000);
		}
		else
		{
			S.push(b % 10000);
		}
	}
	int ans = 0;
	while (!S.empty())
	{
		ans = (ans + S.top()) % 10000;
		S.pop();
	}
	cout << ans;
	return 0;
}

直接利用字符串进行模拟也是可以的

#include<stdio.h>
#include<string.h>
int main()
{
	char a[1000000];
	__int64 i,f=0,x,b[100010]={0},l=0,r,n;
	gets(a);
	n=strlen(a);
	for(i=0;i<n;i++)
	{
		while(a[i]>='0'&&a[i]<='9'&&i<n)
		{
			b[l]=b[l]*10+a[i]-'0';
			i++;			
		}
		if(f)
		b[l]=b[l]*x%10000;
		if(a[i]=='+')
		{l++;f=0;}
		else if(a[i]=='*')
		{
			x=b[l];b[l]=0;f=1;
		}		
	}
	r=0;
	for(i=0;i<=l;i++)
	r=r+b[i]%10000;
	printf("%I64d\n",r%10000);
}

3.6276: 公交换乘

CSP2019入门级T2
无情模拟,用一个数组来装所有的收集到的赠票。每当坐地铁的时候,就直接花钱,然后获得一张赠票,放到数组里面。每当坐公交的时候,看看数组里面有没有时间合适,价格小于现在公交票价的赠票,并且没用过的赠票,直接用时间最早的那一张就可以了。而且45分钟后就过期了,可以用一个队列记录下这张票,这个题卡常,可以直接用数组模拟队列

#include <bits/stdc++.h>
using namespace std;
const int MAXN = 100005;
struct Ticket
{
	//赠票的价格,最晚使用时间和是否需用过
	int price, time, used;
} q[MAXN]; //赠票盒子
int head, tail, n, cost;

int main()
{
	cin >> n;
	for (int i = 0; i < n; ++i)
	{
		int op, price, time;
		//输入每次坐车的种类,价格和发车时间
		cin >> op >> price >> time;
		if (op == 0)
		{
			//如果是坐地铁,直接把价格加到cost里面
			cost += price;
			//新一张赠票插入数组末尾,这张票的最晚使用时间是当前时间+45
			q[tail].time = time + 45;
			//赠票面额就是地铁票价
			q[tail++].price = price;
		}
		else
		{
			//先用一个循环把过期票扔掉
			while (head < tail && q[head].time < time)
			{
				head++;
			}
			bool found = false; //表示是否有合适的赠票,先假设没有
			for (int j = head; j < tail; ++j)
			{
				//循环所有剩余的票,这些一定都没过期,因为题目中时间是按顺序给我们的
				if (q[j].price >= price && q[j].used == 0)
				{
					//如果价格合适,并且没用过,标记找到了,这张票标记用过
					found = true;
					q[j].used = 1;
					break;
				}
			}
			//如果没找到合适的赠票,花钱即可
			if (!found)
				cost += price;
		}
	}
	cout << cost;
	return 0;
}

4.4884: 螺旋矩阵

NOIP2014普及组T3
这个题我们可以想办法去拿那50%的分数,也就是暴力模拟
设置上下左右四个变量,然后暴力每次填数

int j = 1, now = 0, U = 1, D = m, L = 1, R = n;
i = 1;
while (now < N)
{
	while (j < R && now < N)
	{
		c[i][j] = a[now++];
		j++;
	}
	while (i < D && now < N)
	{
		c[i][j] = a[now++];
		i++;
	}
	while (j > L && now < N)
	{
		c[i][j] = a[now++];
		j--;
	}
	while (i > U && now < N)
	{
		c[i][j] = a[now++];
		i--;
	}
	i++;
	j++;
	U++, D--;
	L++, R--;
	//最后一个是特殊的
	if (now == N - 1)
		c[i][j] = a[now++];
}

我们也可以把上面的方法,再用递归实现下,也就是只记录填到哪个数字了,不用数组,一层填4 * (n-1),每次少两行,不断递归下去
1.如果是第 1 行,那么第 j 列的数字就是 j;
2.如果是第 n 列,那么第 i 行的数字就是 n + i - 1;
后两条规律有点难找,但是不要放弃,继续观察:
3.如果是第 n 行,那么第 j 列的数字就是 3×n−2−j+1;
4.如果是第 1 列,那么第 i 行的数字就是 4×n−4−i+2。

int work(int n, int i, int j) {
    if (i == 1)
    	return j;
    if (j == n)
    	return n + i - 1;
    if (i == n)
    	return 3 * n - 2 - j + 1;
    if (j == 1)
    	return 4 * n - 4 - i + 2;
    // 注意,递归的时候,n 要减 2 而不是减 1
    return work(n - 2, i - 1, j - 1) + 4 * (n - 1);
}

真正做法,找规律找到通项公式

#include <bits/stdc++.h>
using namespace std;

int get(int x, int y, int n)
{
	if (x <= y)
	{
		int k = (x < n - 1 - y) ? x : n - 1 - y;
		return 4 * k * (n - k) + 1 + (x + y - k * 2);
	}
	int k = (y < n - 1 - x) ? y : n - 1 - x;
	k = k + 1;
	return 4 * k * (n - k) + 1 - (x + y - (k - 1) * 2);
}

int main()
{
	int n, x, y;
	cin >> n >> x >> y;
	cout << get(x-1, y-1, n);
	return 0;
}

5.6023: 神奇的幻方

NOIP2015提高组Day1T1
大力将题目的情况进行模拟,但是看到了一个有意思的模拟方法,巧妙利用取余可以省去很多步骤

#include <bits/stdc++.h>
using namespace std;
int n,a[40][40],x,y;
int main(){
	scanf("%d",&n);
	x=1,y=(n+1)/2;
	for(int i=1;i<=n*n;i++){
		a[x][y]=i;
		if(!a[(x-2+n)%n+1][y%n+1]) x=(x-2+n)%n+1,y=y%n+1;
		else x=x%n+1;//数学运算
	}
	for(int i=1;i<=n;i++){
		for(int j=1;j<=n;j++){
			printf("%d ",a[i][j]);
		}
		printf("\n");
	}
} 

6.6032: 玩具谜题

NOIP2016提高组Day1T1
一、先来往右边数
那么T的右边一个数的下标就是T+1,T的右边n个数的下标就是T+n啦!
我在访问队列的时候,要循环n次 每次T+=1 然后判断如果T>=len_a 则T-= len_a;循环n次 每次T+=1 然后T%=len_a;
公式为right(T)=(T+n)%len_a;
直接就找到了T右边第n个数的下标。
二、 然后往左边数了
同样的,那么第T个数的左边n个数的下标是:T-n
但是这样可能为负数。之前说过了,最多往左边数len_a-1人,再多数一个就回到原点。
则:T-(n%len_a) 有可能为负数 但是T-(n%len_a)+len_a绝对非负因为 (n%len_a) < len_a ,然后再mod上一个len_a防止超出即可。但是这样的话当 T = len_a 且 n=0 时 会出错
所以改成事先判断n是否为0就不会。
公式: left(T)=(T-(n%len_a)+len_a)%len_a
三、判断向哪一边数人
这个很简单。观察可知当代表方向的bool值与代表小人朝向的bool值不相等,则为顺时针(往左边),相等则为逆时针

#include <bits/stdc++.h>
using namespace std;
int n, m;
vector<string> name;
vector<bool> w;
int main()
{
	cin >> n >> m;
	for (int i = 0; i < n; i++)
	{
		bool a;
		string b;
		cin >> a >> b; //读人
		w.push_back(a);
		name.push_back(b);
	}
	int point = 0; //下标从0开始数
	while (m--)	   //循环m次的简写
	{
		bool f;
		int num;
		cin >> f >> num;
		if (num != 0)
		{
			//异或符^,不等时为1否则为0。
			if (w[point] ^ f)			   
				point = (point + num) % n; //这里如果f和W不等,则顺时针
			else
				point = (point - num % n + n) % n; //否则逆时针
		}
	}
	cout << name[point];
	return 0;
}

7.5997: 时间复杂度

NOIP2017提高组Day1T2
这个题目很变态,需要处理得细节极多,令人窒息,考场遇见这种题目一定要想清楚再写

#include<cstdio>
#include<cstring>
using namespace std;
const int N=101,inf=1e9;
int st[N],val[N],top;
bool bz[N];
int read()
{
    int X=0,w=1; char ch=0;
    while(ch<'0' || ch>'9') {if(ch=='-') w=-1;ch=getchar();}
    while(ch>='0' && ch<='9') X=(X<<3)+(X<<1)+ch-'0',ch=getchar();
    return X*w;
}
int get()
{
    char ch=getchar();
    while((ch<'0' || ch>'9') && ch!='n') ch=getchar();
    if(ch=='n') return inf;
    int ret=ch-'0';
    ch=getchar();
    while(ch>='0' && ch<='9') ret=ret*10+ch-'0',ch=getchar();
    return ret;
}
void readln()
{
    char ch=getchar();
    while(ch!='O' && ch!='F' && ch!='E') ch=getchar();
    if(ch=='O')
    {
        getchar(),getchar();
    }else
    if(ch=='F')
    {
        getchar(),getchar();
        get(),get();
    }
}
int main()
{
    int T=read();
    while(T--)
    {
        int n=read();
        if(n&1)
        {
            printf("ERR\n");
            for(int i=0;i<=n;i++) readln();
            continue;
        }
        getchar(),getchar();
        char s=getchar();
        int t=0,pd=top=0,mx=0;
        memset(bz,false,sizeof(bz));
        memset(st,0,sizeof(st));
        memset(val,0,sizeof(val));
        if(s=='n')
        {
            while(s<'0' || s>'9') s=getchar();
            while(s>='0' && s<='9') t=t*10+s-'0',s=getchar();
        }else s='0';
        for(int i=1;i<=n;i++)
        {
            s=getchar();
            while(s!='F' && s!='E') s=getchar();
            if(s=='E')
            {
                bz[val[top--]]=false;
                if(top<0)
                {
                    for(int j=i+1;j<=n;j++) readln();
                    pd=2;
                    break;
                }
                continue;
            }
            while(s<'a' || s>'z') s=getchar();
            if(bz[s-'a'+1])
            {
                for(int j=i+1;j<=n;j++) readln();
                pd=2;
                break;
            }
            bz[val[top+1]=s-'a'+1]=true;
            int x=get(),y=get();
            if(x>y)
            {
                st[++top]=-1;
                continue;
            }
            if((x==inf && y==inf) || (x<=100 && y<=100) || st[top]<0)
            {
                st[top+1]=st[top];
                top++;
            }else
            {
                st[top+1]=st[top]+1;
                top++;
                if(st[top]>mx) mx=st[top];
            }
        }
        if(pd==2 || top>0)
        {
            printf("ERR\n");
            continue;
        }
        if(mx==t) printf("Yes\n"); else printf("No\n");
    }
    return 0;
}
posted @ 2020-10-24 10:10  暴力都不会的蒟蒻  阅读(141)  评论(0编辑  收藏  举报