[AcWing蓝桥杯]之枚举,模拟与排序(C++题解)

目录

连号区间数

递增三元组(枚举+二分+前缀和)

二分法:O(N*logN)

前缀和:O(N)!!!

特别数的和(特别简单)

错误票据

回文日期

移动距离

日期问题

航班时间

外卖店优先级

归并排序(模板)

逆序对的数量(归并排序的应用)(待回看)


连号区间数

1210. 连号区间数 - AcWing题库

暴力思想:

(1)外层循环枚举左端点

(2)内层循环枚举右端点

(3)并进行判断,判断此时集合的左右端点内的集合是否满足条件

条件为:判断集合中的元素是否排序后的元素之间的差值均为1

注意:集合内只有一个数的情况是满足题目条件的

判断的朴素写法:将数组进行排序,遍历数组,判断前一个数是否与后一个数的差值为1,不难知道该暴力方法的时间复杂度为O(N^3*logN)  快速排序的时间复杂度为N*O(logN)

又因为:1≤N≤10000(1e5)必然会超出时间限制(超过了1e8,如果代码量比较大)

关于优化:

由于注意到排序后,每个响铃元素的差值为1,那么就有这样一个性质:在集合中

最大值-最小值==j-i(集合的右端点的索引-集合左端点的索引)

不难推出来吧^ ^,因为差值为1这个特殊的性质

 y总的简明代码:

#include <cstring>
#include <iostream>
#include <algorithm>

using namespace std;

const int N = 10010, INF = 100000000;

int n;
int a[N];

int main()
{
    cin >> n;
    for (int i = 0; i < n; i ++ ) cin >> a[i];

    int res = 0;
    for (int i = 0; i < n; i ++ )   // 枚举区间左端点
    {
        int minv = INF, maxv = -INF;
        for (int j = i; j < n; j ++ )   // 枚举区间右端点
        {
            minv = min(minv, a[j]);
            maxv = max(maxv, a[j]);
            if (maxv - minv == j - i) res ++ ;
        }
    }

    cout << res << endl;

    return 0;
}

注意一下:minv和maxv的位置即可;


递增三元组(枚举+二分+前缀和)

1236. 递增三元组 - AcWing题库

暴力解法:

第一层for循环枚举a数组,第二层for循环枚举b数组,第三层for循环枚举c数组

判断条件:a[i]<b[i] && b[i]<c[i],满足条件让res++即可,但同理,这样写的时间复杂度为O(N^3)

 优化方案:

发现枚举三个数组感觉有点蠢,能不能只枚举一个数组呢?如果可以,那应该是枚举哪一个数组?

直觉上应该选择一个中间的数组,即数组b,因为它可以间接影响到a,c数组

二分法:O(N*logN)

思想:

书接上回,由上已经推出了只遍历b数组,那么我们就应该在a数组中寻找比b[i]小的数,在c数组中寻找比b[i]大的数,那么满足题意的数量就为(a中比b[i]小的数)*(c中比b[i]大的数)

 寻找:不难会想到二分法,那么很明显,需要先对数组进行排序,因为我们要搜索的只是a,c数组,所以只需将a,c数组进行排序即可

端点:对于a数组我们要寻找的比目标值小的那个数,所以必然是二段性中的第一段的末尾,因为是从小到大进行排序的

同理,对于c数组,必然是二段性中的第二段的首段,因为是从小到大进行排序

那么就可以知道,在a数组中在第一段的末尾前的数均满足要求,所以个数为left+1

同理,在c数组中第二段首段之后的数均满足要求,所以个数为n-right

特殊情况:当在a数组中找不到比b[i]小的数,就应该将left=-1,同理将right=n

                   至于为什么?看看上面的个数就知道了

对于满足个数要求,要开long long res,因为特别理想情况会爆int

#include<iostream>
#include<cstring>
#include<cstdio>
#include<algorithm>
using namespace std;
typedef long long LL;
const int N = 1e5 + 10;
int a[N], b[N], c[N];
int main()
{
    int n;
    scanf("%d", &n);
    for (int i = 0; i < n; i++)  scanf("%d", &a[i]);
    for (int i = 0; i < n; i++)  scanf("%d", &b[i]);
    for (int i = 0; i < n; i++)  scanf("%d", &c[i]);
    sort(a, a + n);  //二分需要满足单调性
    //sort(b, b + n);
    sort(c, c + n);
    LL res = 0;  //答案可能会很大,会爆int
    for (int i = 0; i < n; i++)
    {
        int l = 0, r = n - 1;  //二分查找a数组中最后一个小于b[i]的数的下标
        while (l < r)
        {
            int mid = (l + r + 1) / 2;
            if (a[mid] < b[i])   l = mid;
            else   r = mid - 1;
        }
        if (a[l] >= b[i])   //如果未找到小于b[i]的数,将x标记为-1,后续计算时 x+1==0
        {
            l = -1;
        }
        int x = l;
        l = 0, r = n - 1;
        while (l < r)
        {
            int mid = (l + r) / 2;
            if (c[mid] > b[i])   r = mid;
            else  l = mid + 1;
        }
        if (c[l] <= b[i])   //如果未找到大于b[i]的数,将y标记为n,后续计算时 n-y==0;
        {
            r = n;
        }
        int y = r;
        res += (LL)(x + 1)*(n - y);
    }
    printf("%lld\n", res);
    return 0;
}

前缀和:O(N)!!!

看注释

#include<iostream>
#include<cstdio>
#include<cstring>
#include<algorithm>
using namespace std;
typedef long long ll;
const int N = 100010;//N为数组中元素的最大值

int n;
int a[N], b[N], c[N];
int as[N];//as[i]表示在a[]中有多少个数小于b[i]
int cs[N];//cs[i]表示在c[]中有多少个数大于b[i]
int cnt[N], s[N];//

int main()
{
	cin >> n;
	for (int i = 0; i < n; i++) cin >> a[i], a[i]++;
	for (int i = 0; i < n; i++) cin >> b[i], b[i]++;
	for (int i = 0; i < n; i++) cin >> c[i], c[i]++;

	for (int i = 0; i < n; i++) cnt[a[i]]++;//存储a大小对应出现的次数
	for (int i = 1; i < N; i++) s[i] = s[i - 1] + cnt[i];//次数的前缀和
	for (int i = 0; i < n; i++) as[i] = s[b[i] - 1];//a小于b[i]对应的总次数

	memset(cnt, 0, sizeof cnt);
	memset(s, 0, sizeof s);

	for (int i = 0; i < n; i++) cnt[c[i]]++;
	for (int i = 1; i < N; i++) s[i] = s[i - 1] + cnt[i];
	for (int i = 0; i < n; i++) cs[i] = s[N - 1] - s[b[i]];//(前缀和)最后面的数-中间数

	ll res = 0;
	for (int i = 0; i < n; i++) res += (ll)as[i] * cs[i];

	cout << res << endl;

	return 0;
}

特别数的和(特别简单)

1245. 特别数的和 - AcWing题库

#include<iostream>
using namespace std;

bool check(int num)
{
    bool flag=false;
    while(num)
    {
        int j=num%10;
        if(j==2 || j==1 || j==9 || j==0)
        {
            flag=true;
            break;
        }
        num=num/10;
    }
    return flag;
}

int main()
{
    int num;
    cin>>num;
    
    int res=0;
    for(int i=1;i<=num;i++)
    {
        if(check(i)) res+=i;
    }
    cout<<res<<endl;
    
    return 0;
}

错误票据

1204. 错误票据 - AcWing题库

这道题本身不难的:只是简单的要求重数和漏的数而已,难的是输入

思路核心:

记录最大值和最小值,由于题目说了,漏的数不为最小值和最大值,所以漏的数和重数必然在最小数和最大数之间,遍历即可,哈希表的思想

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

const int N=1e5+10;
const int INF=0x3f3f3f3f;
int ha[N];

int main()
{
    int n;
    cin>>n;
    int minv=INF;
    int maxv=-INF;
    int tp;
    while(cin>>tp)
    {
        if(tp<minv) minv=tp;
        if(tp>maxv) maxv=tp;
        ha[tp]++;
    }
    int ans1=0,ans2=0;
    for(int i=minv;i<=maxv;i++)
    {
        if(ha[i]==0) ans1=i;
        if(ha[i]==2) ans2=i;
    }
    
    cout<<ans1<<" "<<ans2<<endl;
    
    return 0;
}

回文日期

466. 回文日期 - AcWing题库

题目概述:给定一个起始日期和一个终止日期,输出所有满足回文性质的日期个数

思路一:遍历所有日期,判断其是否为回文串

思路二:由于字符串的格式为8个,所以先遍历前4个数字,再将其构造为回文串,再判断其是否为合法日期

注意:因为字符串的格式为8个数字,所以遍历+调用是否判断回文串又要遍历一遍,很可能导致超时,所以思路二才是我们所采取的较优解

#include <cstdio>
#include <cstring>
#include <iostream>
#include <algorithm>

using namespace std;

int months[13] = { 0, 31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31 };

bool check(int date)
{
    int year = date / 10000;
    int month = date % 10000 / 100;
    int day = date % 100;

    if (!month || month >= 13 || !day) return false;

    if (month != 2 && day > months[month]) return false;
    if (month == 2)
    {
        bool leap = year % 4 == 0 && year % 100 || year % 400 == 0;
        if (day > 28 + leap) return false;
    }

    return true;
}

int main()
{
    int date1, date2;
    cin >> date1 >> date2;

    int res = 0;
    for (int i = 1000; i < 10000; i++)
    {
        int x = i, r = i;//构造后四个:先构造回文串,再判断其是否合法
        for (int j = 0; j < 4; j++) r = r * 10 + x % 10, x /= 10;

        if (r >= date1 && r <= date2 && check(r)) res++;
    }

    printf("%d\n", res);
    return 0;
}

移动距离

1219. 移动距离 - AcWing题库

这个叫啥距离公式来着,忘了,但是没关系,很简单,只要找到这个图与C++的二维数组的关系即可,

#include <cstring>
#include <iostream>
#include <algorithm>

using namespace std;

int main()
{
    int w, m, n;
    cin >> w >> m >> n;
    m --, n -- ;//为了对应

    int x1 = m / w, x2 = n / w;
    int y1 = m % w, y2 = n % w;
    if (x1 % 2) y1 = w - 1 - y1;//奇偶情况
    if (x2 % 2) y2 = w - 1 - y2;

    cout << abs(x1 - x2) + abs(y1 - y2) << endl;//公式

    return 0;
}

日期问题

1229. 日期问题 - AcWing题库

普通枚举,没啥可说

#include <cstdio>
#include <cstring>
#include <iostream>
#include <algorithm>

using namespace std;

int days[13] = {0, 31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31};

bool check_valid(int year, int month, int day)
{
    if (month == 0 || month > 12) return false;
    if (day == 0) return false;
    if (month != 2)
    {
        if (day > days[month]) return false;
    }
    else
    {
        int leap = year % 100 && year % 4 == 0 || year % 400 == 0;
        if (day > 28 + leap) return false;
    }

    return true;
}

int main()
{
    int a, b, c;
    scanf("%d/%d/%d", &a, &b, &c);

    for (int date = 19600101; date <= 20591231; date ++ )
    {
        int year = date / 10000, month = date % 10000 / 100, day = date % 100;
        if (check_valid(year, month, day))
        {
            if (year % 100 == a && month == b && day == c ||        // 年/月/日
                month == a && day == b && year % 100 == c ||        // 月/日/年
                day == a && month == b &&year % 100 == c)           // 日/月/年
                printf("%d-%02d-%02d\n", year, month, day);
        }
    }

    return 0;
}

航班时间

1231. 航班时间 - AcWing题库

别把自己给绕进去了:要求的是在飞机上的时间,相当于只是用落地时刻减去起飞时刻,为什么要除以2,因为是两地的时间,(其实用1地的时间即可…………这题目好奇怪)

#include<iostream>
using namespace std;
int getTime(void)
{
    int h1,m1,s1,h2,m2,s2,d=0;
    scanf("%d:%d:%d %d:%d:%d (+%d)",&h1,&m1,&s1,&h2,&m2,&s2,&d);
    int time=d*24*3600+h2*3600+m2*60+s2-(h1*3600+m1*60+s1);
    return time;
}
int main()
{
    int t;
    scanf("%d",&t);
    for(int i = 0; i < t; i++)
    {
        int time1=getTime();
        int time2=getTime();
        int t=(time1+time2)/2;
        printf("%02d:%02d:%02d\n", t/3600, t/60%60, t%60);
    }
    return 0;
}
/*
//去乘起飞时间+航行时间+时差=去乘降落时间  (公式一)
//回程起飞时间+航行时间-时差=回程降落时间  (公式二)
//求:航行时间
//已知:去乘起飞时间,回程起飞时间,去乘降落时间,回程降落时间
//根据公式一+公式二:
//去乘起飞时间+回程起飞时间+2*航行时间=去乘降落时间+回程降落时间
//航行时间=(去乘降落时间-去乘起飞时间+回程降落时间-回程起飞时间)/2
*/

外卖店优先级

1241. 外卖店优先级 - AcWing题库

模拟:注释写得很清楚了

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

#define x first
#define y second
using namespace std;

typedef pair<int, int> PII;
const int N = 100010;

int n, m, T;
int score[N], last[N];
bool st[N];
PII order[N];//订单的时刻和编号

int main()
{
	cin >> n >> m >> T;//n是外卖店的数量,m是订单数,T为时刻
						//第一关键字:时刻   第二关键字:店铺编号
	for (int i = 0; i < m; i++) cin >> order[i].x >> order[i].y;
	sort(order, order + m);//按照第一关键字进行排序

	for (int i = 0; i < m;)//枚举订单数量
	{
		int j = i;//判断之后有没有相同的订单数(即时刻和店铺编号相同)
		while (j < m && order[j] == order[i]) j++;
		int t = order[i].x;
		int id = order[i].y;
		int cnt = j - i;//相同的订单的数量
		i = j;//下一次for循环从j处开始遍历

		score[id] -= t - last[id] - 1;//上一个拿到订单的时间last[id]和t之间
		//也就是假设上一次的订单时刻是3,此时的订单时刻是6,那么没有订单的时刻就是4,5==6-3-1==2
		if (score[id] < 0) score[id] = 0;
		if (score[id] <= 3) st[id] = false;//如果为负值更新为0。如果小于等于3

		score[id] += cnt * 2;//t时刻拿到订单,并且拿到的数量为cnt
		if (score[id] > 5) st[id] = true; //如果大于5,更新优先缓存st[id] = true

		last[id] = t;//用这次的得到订单的时间存入上次获取订单的时间
	}

	for(int i=1;i<=n;i++)//遍历外卖店的数量
		if (last[i] < T)//如果最后一次获取订单的时间小于<规定时间
		{//如果说最后拿到订单的时刻是5   由于已经确定了最后一次没有订单所以:6-5=1:也就是时刻6没有订单
			score[i] -= T - last[i];//那么最后一次计算得分将不会进行-1的操作
			if (score[i] <= 3) st[i] = false;
		}
	int res = 0;
	for (int i = 1; i <= n; i++) res += st[i];//遍历符合的外卖店即可

	cout << res << endl;

	return 0;
}

归并排序(模板)

活动 - AcWing

#include <cstring>
#include <iostream>
#include <algorithm>

using namespace std;

const int N = 100010;

int n;
int q[N], w[N];

void merge_sort(int l, int r)
{
    if (l >= r) return;

    int mid = l + r >> 1;

    merge_sort(l, mid), merge_sort(mid + 1, r);//分治

    int i = l, j = mid + 1, k = 0;
    while (i <= mid && j <= r)//结束条件:有一个到达边界
        if (q[i] < q[j]) w[k++] = q[i++];//小的放入
        else w[k++] = q[j++];
    while (i <= mid) w[k++] = q[i++];//使它们都到达边界
    while (j <= r) w[k++] = q[j++];

    for (i = l, j = 0; i <= r; i++, j++) q[i] = w[j];
}

int main()
{
    scanf("%d", &n);
    for (int i = 0; i < n; i++) scanf("%d", &q[i]);

    merge_sort(0, n - 1);

    for (int i = 0; i < n; i++) printf("%d ", q[i]);

    return 0;
}

逆序对的数量(归并排序的应用)(待回看)

活动 - AcWing

#include <iostream>

using namespace std;

const int N = 1e6 + 10;
int cmp[N];
long long int res = 0;

void merge_sort(int q[], int l, int r)
{
    if (l >= r) return;
    int mid = l + r >> 1;
    merge_sort(q, l, mid), merge_sort(q, mid + 1, r);
    int i = l, j = mid + 1,k = 0;

    while (i <= mid && j <= r)
    {
        if (q[i] <= q[j])
        {
            cmp[k ++] = q[i ++];
        }
        else
        {
            res += mid - i + 1;//结果为:从从i到mid的这些数(注意已经类似于有序)
            cmp[k ++] = q[j ++];
        }
    }
    //循环出来必有一个到达了端点
    while (i <= mid) cmp[k ++] = q[i ++];
    while (j <= r) cmp[k ++] = q[j ++];

    for (int i = l, j = 0; i <= r; i ++, j ++) q[i] = cmp[j];
}

int main()
{
    int n;
    cin >> n;
    int q[n];
    for (int i = 0; i < n; i ++) scanf("%d", &q[i]);
    merge_sort(q, 0, n - 1);
    cout << res << endl;
    return 0;

}

  • 3
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值