题解 P4117 [Ynoi2018]五彩斑斓的世界

Posted colazcy

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了题解 P4117 [Ynoi2018]五彩斑斓的世界相关的知识,希望对你有一定的参考价值。

题目链接

我觉得AVX2指令集不够爽,于是写了AVX512.到官网查了一天手册,直接拿下最优解

Solution [Ynoi2018]五彩斑斓的世界

题目大意:给定一个长度为\(n\)的序列.每次将\([l,r]\)内大于\(x\)的数减\(x\).询问\([l,r]\)\(x\)出现了多少次

分析:还能有啥分析,直接暴力,指令集优化一下就可以了

所有函数都可以在Intel手册查到

首先,既然我们要用指令集,得\(CPU\)资瓷才行

#include <stdint.h>
#include <iostream>
#include <cpuid.h>
static void cpuid(uint32_t func, uint32_t sub, uint32_t data[4]) 
    __cpuid_count(func, sub, data[0], data[1], data[2], data[3]);

int main() 
    uint32_t data[4];
    char str[48];
    for(int i = 0; i < 3; ++i) 
        cpuid(0x80000002 + i, 0, data);
        for(int j = 0; j < 4; ++j)
            reinterpret_cast<uint32_t*>(str)[i * 4 + j] = data[j];
    
    std::cout << str;

将如上代码丢入在线\(IDE\),得到评测姬\(CPU\)型号

注意:\(Luogu\)采用集群评测,每次得到的\(CPU\)型号不一定相同.(也就是过不了多交几次的原理?)
在我这儿,得到的型号为:

Intel(R) Xeon(R) Gold 6149 CPU @ 3.10GHz

\(Intel\)官网是查不到这颗\(CPU\)的信息的,考虑到\(Luogu\)基于阿里云,那这颗\(CPU\)就多半是阿里云定制的.我们可以查询与它规格相近的Xeon(R) Gold 6148
Intel官网查到这颗\(CPU\)支持的指令集:

SSE4.2, AVX, AVX2, AVX-512

你谷非常良心啊,其它\(OJ\)用的都是酷睿,你谷上了至强可扩展……

  • CodeForces: i3-8100
  • LibreOJ: i5-3470
  • bzoj: 想必这OJ的CPU一定古(垃)老(圾),就不查了(而且也查不到)

所以注意了请不要在考场上使用AVX512指令集,用AVX256都是冒险

CCF所采用的i7-8700k仅支持到AVX2,AVX512要等到Cannon Lake去了

关于AMD:她死了

先上代码,然后下文再讲解函数用法:

#pragma GCC target("avx,avx2,avx512f")
#include <immintrin.h>

#include <cstdio>
using namespace std;
const int maxn = 1e5 + 100;
int n,m,val[maxn],*arr;
inline void modify(int *left,int *right,int x)
    __m512i subval = _mm512_set_epi32(x,x,x,x,x,x,x,x,x,x,x,x,x,x,x,x);
    while(((long long)(left) % 64) && left <= right)//64位对齐
        if(*left > x)*left -= x;
        left++;
    
    while(left + 15 <= right)
        __m512i tmp = _mm512_load_epi32(left);
        _mm512_store_epi32(left,_mm512_mask_sub_epi32(tmp,_mm512_cmpgt_epi32_mask(tmp,subval),tmp,subval));//如果tmp对应位大于subval对应位就是两数相减的值,否则不变
        left += 16;
    
    while(left <= right)
        if(*left > x)*left -= x;
        left++;
    

inline int query(int *left,int *right,int x)
    int ret = 0;
    __m512i sum = _mm512_setzero_epi32(),one = _mm512_set_epi32(1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1);
    while(((long long)(left) % 64) && left <= right)//64位对齐
        if(*left == x)ret++;
        left++;
    
    __m512i tocmp = _mm512_set_epi32(x,x,x,x,x,x,x,x,x,x,x,x,x,x,x,x);
    while(left + 15 <= right)
        __m512i tmp = _mm512_load_epi32(left);
        sum = _mm512_mask_add_epi32(sum,_mm512_cmpeq_epi32_mask(tmp,tocmp),sum,one);//如果tmp和tocmp对应位相等,sum对应位+1否则不变
        left += 16;
    
    while (left <= right)
        if (*left == x) ret++;
        left++;
    
    _mm512_store_epi32(arr,sum);
    for(int i = 0;i < 16;i++)
        ret += arr[i];
    return ret;

int main()

    scanf("%d %d",&n,&m);
    arr = new int[128];
    while((long long)(arr) % 64)arr++;//64位对齐
    for(int i = 1;i <= n;i++)val[i] = read();
    for(int i = 1;i <= m;i++)
        int opt,l,r,x;
        scanf("%d %d %d %d",&opt,&l,&r,&x);
        if(opt == 1)modify(val + l,val + r,x);
        else printf("%d\n",query(val + l,val + r,x));
    
    return 0;

从指令集名字我们可以看出来,$AVX512指令集一次性可以处理\(512\)位数据,也就是\(16\)\(int\)

AVX512指令集的基本数据类型:

__m512,__m512d,__m512i
分别存储单精度浮点数,双精度浮点数,整数

这次我们用__m512i,所以另外两个我们不讲,它可以存储\(64\)\(char\),\(32\)\(short\),\(16\)\(int\),\(8\)\(long\;long\)

__m512i _mm512_set_epi32 (int e15, int e14, int e13, int e12, int e11, int e10, int e9, int e8, int e7, int e6, int e5, int e4, int e3, int e2, int e1, int e0)

这个函数把\(16\)\(int\)打包,\(epi32\)显然指的是每个元素的大小(位数),\(epi64\)就适用于\(long\;long\)

__m512i _mm512_load_epi32 (void const* mem_addr)

这个函数从指定内存地址读取\(512\)位,注意mem_addr一定要64位对齐,否则会抛出一般保护性异常

void _mm512_store_epi32 (void* mem_addr, __m512i a)

这个函数将\(a\)中的值存储到\(mem\_addr\)开始的地址中,同样要\(64\)位对齐

__mmask16 _mm512_cmpgt_epi32_mask (__m512i a, __m512i b)

这个函数比较\(a\)\(b\),返回一个掩码.如果\(a\)对应位$ >$ \(b\)对应位 掩码对应位为\(1\)

__m512i _mm512_mask_add_epi32 (__m512i src, __mmask16 k, __m512i a, __m512i b)

如果写掩码\(k\)对应位为\(0\),那么返回值对应位就是\(src\)的对应位,否则就是\(a\)的对应位加\(b\)的对应位

__m512i _mm512_mask_sub_epi32 (__m512i src, __mmask16 k, __m512i a, __m512i b)

如果写掩码\(k\)对应位为\(0\),那么返回值对应位就是\(src\)的对应位,否则就是\(a\)的对应位减\(b\)的对应位

__mmask16 _mm512_cmpeq_epi32_mask (__m512i a, __m512i b)

这个函数返回一个掩码,如果\(a\)对应位\(=\) \(b\)对应位,掩码对应位为\(1\)

然后记得

#pragma GCC target("avx,avx2,avx512f")
#include <immintrin.h>

没了,基本上就是一个模拟.复杂度\(O(nm/16)\)

跑的挺快的,加个\(mmap\)读入优化轻松冲进2162ms

吸了氧气会变慢就比较迷了,氧气中毒?

以上是关于题解 P4117 [Ynoi2018]五彩斑斓的世界的主要内容,如果未能解决你的问题,请参考以下文章

[Ynoi2018]未来日记

[Ynoi2018]五彩斑斓的世界

[bzoj 5143][Ynoi 2018]五彩斑斓的世界

[Ynoi2018]未来日记 - 题解

[Ynoi2018]未来日记

[Ynoi2018]天降之物