0
点赞
收藏
分享

微信扫一扫

【解题报告】2022牛客寒假算法基础集训营1

修炼之士 2022-01-26 阅读 25

【解题报告】2022牛客寒假算法基础集训营1

  • 2022牛客寒假算法基础集训营1
    由于题目难度有些偏基础,太基础的今后就不细讲了。
    话不多说,开冲!

A:九小时九个人九扇门

题意 | 简单

  • 经典 999 游戏,之前就看过极限脱出系列的攻略全流程了…(咳咳
    数字根:一个数字的各个位数字和,如果该和不是一位数,继续操作,直到一位数字,就是这个数字的数字根
    比如 87->(8+7=15)15->(1+5=6)6 所以 87的数字根为6
  • n n n 个人,给定每个人的数字 a i a_i ai
    选出一些人的集合,求集合的数字根为 1 ∼ 9 1\sim 9 19 的集合选法,取模 998244353 998244353 998244353
    集合的数字根:为集合的所有元素的和的数字根
    1 ≤ n ≤ 1 0 5 1\le n\le 10^5 1n105
    1 ≤ a i ≤ 1 0 9 1\le a_i\le 10^9 1ai109

思路 | 数学 + dp

  • 容易猜想或者得到,设 f ( x ) f(x) f(x) 表示 x x x 的数字根,那么有:
    f ( x ) = ( x − 1 )   m o d   9 + 1 (1) f(x)=(x-1)\bmod 9+1\tag{1} f(x)=(x1)mod9+1(1)
    还有一些性质:
    f ( x + y ) = f ( f ( x ) + f ( y ) ) f ( x + 9 ) = f ( x ) f ( 9 x ) = 9 f ( x y ) = f ( f ( x ) × f ( y ) ) f(x+y)=f(f(x)+f(y))\\ f(x+9)=f(x)\\ f(9x)=9\\ f(xy)=f(f(x)\times f(y)) f(x+y)=f(f(x)+f(y))f(x+9)=f(x)f(9x)=9f(xy)=f(f(x)×f(y))
    这些都可以由上面 ( 1 ) (1) (1) 式推导出来。
    如果让我证明的话,感觉不大好证,我会用反证法,先假设 ( 1 ) (1) (1) 式成立
    然后我能得到 f ( x + y ) = f ( f ( x ) + f ( y ) ) f(x+y)=f(f(x)+f(y)) f(x+y)=f(f(x)+f(y)) 也是同时成立的,并且这个和数字根的定义相同
    所以原 ( 1 ) (1) (1) 成立
  • 不管怎么样,我们可以利用这个 f ( x + y ) = f ( f ( x ) + f ( y ) ) f(x+y)=f(f(x)+f(y)) f(x+y)=f(f(x)+f(y))
    d p [ i ] [ x ] dp[i][x] dp[i][x] 表示考虑完前 i i i 个数字,数字根为 x ( x ≤ 9 ) x(x\le9) x(x9) 的方案数,然后转移即可。
    随便计算数字根,用定义法 O ( 1 ) O(1) O(1) 还是递归法 O ( log ⁡ ) O(\log ) O(log) 都行

代码

  • 时间复杂度: O ( n ( 10 + O ( 算 一 个 数 字 根 ) ) ) O(n(10+O(算一个数字根))) O(n(10+O()))
    空间复杂度:滚动数组了一下, Θ ( 20 ) \Theta(20) Θ(20)
const int st = 0;

int cnt[15][2];

int cal(int a){		// 你当然可以写 return (x-1) % 9 + 1 更快速得到数字根
    if(a < 10)return a;
    return cal(cal(a / 10) + a % 10);
}

int main()
{
    cnt[0][st] = 1;
    int n;cin >> n;
    for(int i = 1;i <= n;++i){
        int t;cin >> t;
        for(int j = 0;j < 10;++j){
            int aim = cal(j + t);
            cnt[aim][st^1] = (cnt[aim][st^1] + cnt[j][st]) % MOD;
        }
        for(int j = 0;j < 10;++j){
            cnt[j][st] = (cnt[j][st] + cnt[j][st^1]) % MOD;
            cnt[j][st^1] = 0;
        }
    }

    for(int i = 1;i < 10;++i){
        cout << cnt[i][st] << ' ';
    }
    return 0;
}

B:炸鸡块君与FIFA22

题意 | 中等

  • 给定一个长度为 n n n 的字符串 S S S ,和 q q q 个询问
    字符串只包含三种字符:
    • W W W 表示赢,加一分。
    • L L L 表示输,扣一分。如果扣分前分数是三的倍数,则不扣分
    • D D D 表示平局,分数不变。
  • 每组询问给定 l , r , s l,r,s l,r,s ,表示初始分数为 s s s ,经历 S [ l : r ] S[l:r] S[l:r] 之后的分数为多少
  • 1 ≤ n , q ≤ 2 × 1 0 5 1\le n,q\le 2\times 10^5 1n,q2×105
    1 ≤ l ≤ r ≤ n 1\le l\le r\le n 1lrn
    0 ≤ s ≤ 1 0 9 0\le s\le10^9 0s109

思路 | 数据结构 + 简单同余

  • 容易想到,快速计算分数肯定要用能分治的数据结构。我这里使用倍增表。
    倍增表就是:
    d p [ i ] [ j ] = ⨁ i + 2 j − 1 p = i a r r p dp[i][j]=\underset{p=i}{\overset{i+2^j-1}{\bigoplus}}arr_p dp[i][j]=p=ii+2j1arrp
    它比线段树要内存小,更快,但是无法修改操作。同样要求操作满足结合律。
  • 这里想到,分数是三的倍数扣分不记录,则按分数取余 3 3 3 分别记录,就变成 d p [ i ] [ j ] [ 0 / 1 / 2 ] dp[i][j][0/1/2] dp[i][j][0/1/2]
    想好转移。
    d p [ i ] [ j ] [ k ] = d p [ i ] [ j − 1 ] [ k ] + d p [ i + 2 j − 1 ] [ j − 1 ] [ k ′ ] dp[i][j][k]=dp[i][j-1][k]+dp[i+2^{j-1}][j-1][k^\prime] dp[i][j][k]=dp[i][j1][k]+dp[i+2j1][j1][k]
    在这里插入图片描述
  • 容易得到整段的分数等于左边段的分数加上右边段的分数
    这个时候 k ′ k' k 变成了什么呢?当然就是 k ′ = ( k + d p [ i ] [ j − 1 ] [ k ] % + 3 ) % 3 k'=(k+dp[i][j-1][k]\%+3)\%3 k=(k+dp[i][j1][k]%+3)%3,注意分数可能有负,所以要这么处理。
  • 然后是查询。查询就是把 [ l : r ] [l:r] [l:r] 分成不同个 2 p 2^p 2p 的段,来让查询复杂度达到 O ( log ⁡ ) O(\log) O(log) 级别,具体可以看代码

代码

  • 时间复杂度: O ( ( n + q ) log ⁡ n ) O((n+q)\log n) O((n+q)logn)
int dp[MAX][22][3];
int p2[22];		// 预处理 2^p
int main()
{
    IOS;

    p2[0] = 1;
    for(int i = 1;i <= 20;++i)
        p2[i] = p2[i-1] * 2;

    int n,q;cin >> n >> q;
    string ss;
    cin >> ss;
    ss = " " + ss;
    for(int i = 1;i <= n;++i){
        for(int j = 0;j < 3;++j){			// 计算 dp 初值
            dp[i][0][j] = 0;
            if(ss[i] == 'W')dp[i][0][j]++;
            else if(ss[i] == 'L' && j != 0)dp[i][0][j]--;
        }
    }

    for(int i = 1;i <= 20;++i)			// 预处理转移 dp
    for(int p = 1;p <= n;++p){
        if(p + p2[i] - 1 > n)break;
        for(int j = 0;j < 3;++j){
            int f1 = dp[p][i-1][j];
            int f2 = dp[p+p2[i-1]][i-1][((j+f1)%3+3)%3];
            dp[p][i][j] = f1 + f2;
        }
    }

    for(int i = 1;i <= q;++i){
        int l,r,s;
        cin >> l >> r >> s;
        int delta = 0;
        int now = s % 3;
        for(int j = 20;j >= 0;--j){			// 查询
            if(l + p2[j] - 1 <= r){
                delta += dp[l][j][now];
                now = (now + dp[l][j][now] % 3 + 3) % 3;
                l += p2[j];
            }
        }
        cout << s + delta << '\n';
    }
    return 0;
}

C:Baby’s first attempt on CPU

题意 | 简单

  • 一共有 n n n 个语句。若语句 i i i 向语句 j j j 调用( j < i j<i j<i),则这里两个语句之间必须至少间隔三个语句,否则会出现问题。
    给定语句调用表。
    你要插入最少数量的空白语句,来满足所有语句调用没有问题。
  • 3 ≤ n ≤ 100 3\le n\le 100 3n100

思路 | 简单dp

  • 分类讨论即可
    当我们讨论下面第 i i i 个语句时,注意到,如果有需要,只会在它的左边放空白语句是最优的。
    放置之后,蓝色调用将不会出现问题
    绿色调用若有,则间隔需求降低。
    在这里插入图片描述
  • 然后贪心处理,解就是一定的最优的了。

代码

  • 时间复杂度: O ( n ) O(n) O(n)
int aa[105][5];

int main()
{
    int ans = 0;
    int n;cin >> n;

    for(int i = 1;i <= n;++i)
        cin >> aa[i][1] >> aa[i][2] >> aa[i][3];


    for(int i = 1;i <= n;++i){
        if(aa[i][1] == 1){
            ans += 3;
            aa[i+1][2] = aa[i+1][3] = 0;
            aa[i+2][3] = 0;
        }else if(aa[i][2] == 1){
            ans += 2;
            aa[i+1][2] = aa[i+1][3] = 0;
            aa[i+2][3] = 0;
        }else if(aa[i][3] == 1){
            ans++;
            aa[i+1][3] = aa[i+1][2];
            aa[i+1][2] = 0;
            aa[i+2][3] = 0;
        }
    }
    cout << ans;
    return 0;
}

D:牛牛做数论

题意 | 中等

  • H ( x ) = φ ( x ) x H(x)=\frac{\varphi(x)}{x} H(x)=xφ(x)

在这里插入图片描述

思路 | 数论

  • 欧拉函数是积性函数,可以写成 φ ( x ) = x ∏ ( p i − 1 p i ) \varphi(x)=x\prod (\cfrac{p_i-1}{p_i}) φ(x)=x(pipi1)
    所以得到 H ( x ) = ∏ ( p i − 1 p i ) H(x)=\prod (\cfrac{p_i-1}{p_i}) H(x)=(pipi1)
  • 那么最大值,就是 [ 2 , n ] [2,n] [2,n] 中最大的质数取到的值
    最小值,就是范围之内最多、尽量小的质数的乘积,暴力检查前 10 10 10 个质数即可。

代码

  • 时间复杂度: O ( T n ) O(T\sqrt{n}) O(Tn ),要有一个质数判定,根据素数密度定理,判定次数为常数
bool is_p(int x){
    for(int i = 2;1LL * i * i <= x;++i){
        if(x % i == 0)return false;
    }
    return true;
}

int pp[]={2,3,5,7,11,13,17,19,23,29};

int main()
{
    int T;cin >> T;
    while(T--){
        int n;cin >> n;
        if(n == 1){
            cout << -1 << endl;
            continue;
        }
        int L,R;
        for(int i = n;;--i){
            if(is_p(i)){
                R = i;
                break;
            }
        }

        L = 2;
        for(int i = 1;i < 10;++i){
            if(1LL * L * pp[i] <= n)L *= pp[i];
            else break;
        }

        cout << L << " " << R << endl;

    }

    return 0;
}

E:炸鸡块君的高中回忆

  • 签到题
    先特判,再做下述操作:
    操作1:花两步,校园里人少掉 m − 1 m-1 m1
    操作2:花一步,校园里人少掉 m m m,但是只能做一次。
  • 设操作1 做了 k k k 次,答案就是最小的 k k k ,满足:
    ( m − 1 ) k + m ≥ n 即 k ≥ n − m m − 1 (m-1)k+m\ge n\\ 即 \qquad k\ge \frac{n-m}{m-1} (m1)k+mnkm1nm
    答案就是 2 k + 1 2k+1 2k+1
int main()
{
    IOS;
    int T;cin >>T;
    while(T--){
        int n,m;
        cin >> n >> m;
        if(m == 1){
            if(n == 1)cout << 1 << '\n';
            else cout << -1 << '\n';
            continue;
        }
        int k = ceil((1.0 * n - m) / (m - 1));
        cout << 1LL * k * 2 + 1 << '\n';
    }
    return 0;
}

F:中位数切分

题意 | 中等

  • 把长度为 n n n 的序列划分成最多份,满足每份的中位数 ≥ m \ge m m 。若长度为偶数,中位数取中间的左边那个数字。
  • 1 ≤ n ≤ 1 0 5 1\le n\le 10^5 1n105
    1 ≤ a i , m ≤ 1 0 9 1\le a_i,m\le 10^9 1ai,m109

思路 | 数据结构 + dp

  • 划分满足无后效性,想到 d p dp dp ,则设: d p [ i ] dp[i] dp[i] 表示考虑玩前 i i i 个元素,最多划分的段数
  • 看到中位数,想到把元素变化。若 a i ≥ m a_i\ge m aim ,即为 1 1 1,否则记为 − 1 -1 1
    这样,序列的中位数 ≥ m \ge m m 转化为序列的和 ≥ 1 \ge 1 1
  • d p dp dp 的转移,如下:
    d p [ i ] = max ⁡ { d p [ j ] + 1 } , 满 足 [ j + 1 , i ] 这 一 段 的 元 素 和 ≥ 1 dp[i]=\max\{dp[j]+1\},满足[j+1,i] 这一段的元素和 \ge 1 dp[i]=max{dp[j]+1}[j+1,i]1
  • 元素和,想到前缀和。我们用 p r e pre pre 数组记录前缀和,于是后面就变成了:
    d p [ i ] = max ⁡ { d p [ j ] + 1 } , p r e [ i ] − p r e [ j ] ≥ 1 dp[i]=\max\{dp[j]+1\},pre[i]-pre[j]\ge 1 dp[i]=max{dp[j]+1}pre[i]pre[j]1
    由于 p r e [ i ] pre[i] pre[i] 是固定的,我们移动元素位置,得到:
    d p [ i ] = max ⁡ { d p [ j ] + 1 } , p r e [ j ] ≤ p r e [ i ] − 1 dp[i]=\max\{dp[j]+1\},pre[j]\le pre[i]-1 dp[i]=max{dp[j]+1}pre[j]pre[i]1
  • 于是,右边就变成了求区间最小值,线段树搞定。
    最后答案当然就是 d p [ n ] dp[n] dp[n]
  • 注意到, p r e pre pre 数组是可以为负数的,所以我们每个位置的元素增加一个增量 O = 1 e 5 + 2 O=1e5+2 O=1e5+2 即可。
    还注意到,有些位置的分隔是不可行的,因为求的是区间最大值,用 − 1 -1 1 做初始化。

代码

  • 时间复杂度: O ( n log ⁡ n ) O(n\log n) O(nlogn)
    这里还使用了快读,就把完整代码贴上来吧。
#include<bits/stdc++.h>
using namespace std;
#define IOS ios::sync_with_stdio(false);cin.tie(NULL);cout.tie(NULL);
void show(){std::cerr << endl;}template<typename T,typename... Args>void show(T x,Args... args){std::cerr << "[ " << x <<  " ] , ";show(args...);}
typedef long long ll;

namespace Fast_IO{ ///orz laofu
    const int MAXL((1 << 18) + 1);int iof, iotp;
    char ioif[MAXL], *ioiS, *ioiT, ioof[MAXL],*iooS=ioof,*iooT=ioof+MAXL-1,ioc,iost[55];
    char Getchar(){
        if (ioiS == ioiT){
            ioiS=ioif;ioiT=ioiS+fread(ioif,1,MAXL,stdin);return (ioiS == ioiT ? EOF : *ioiS++);
        }else return (*ioiS++);
    }
    void Write(){fwrite(ioof,1,iooS-ioof,stdout);iooS=ioof;}
    void Putchar(char x){*iooS++ = x;if (iooS == iooT)Write();}
    inline int read(){
        int x=0;for(iof=1,ioc=Getchar();(ioc<'0'||ioc>'9')&&ioc!=EOF;)iof=ioc=='-'?-1:1,ioc=Getchar();
        if(ioc==EOF)exit(0);
        for(x=0;ioc<='9'&&ioc>='0';ioc=Getchar())x=(x<<3)+(x<<1)+(ioc^48);return x*iof;
    }
    inline long long read_ll(){
        long long x=0;for(iof=1,ioc=Getchar();(ioc<'0'||ioc>'9')&&ioc!=EOF;)iof=ioc=='-'?-1:1,ioc=Getchar();
        if(ioc==EOF)exit(0);
        for(x=0;ioc<='9'&&ioc>='0';ioc=Getchar())x=(x<<3)+(x<<1)+(ioc^48);return x*iof;
    }
    /**
    #define lll __int128
    inline lll read_lll(){
        lll x=0;for(iof=1,ioc=Getchar();(ioc<'0'||ioc>'9')&&ioc!=EOF;)iof=ioc=='-'?-1:1,ioc=Getchar();
        if(ioc==EOF)exit(0);
        for(x=0;ioc<='9'&&ioc>='0';ioc=Getchar())x=(x<<3)+(x<<1)+(ioc^48);return x*iof;
    }*/
    template <class Int>void Print(Int x, char ch = '\0'){
        if(!x)Putchar('0');if(x<0)Putchar('-'),x=-x;while(x)iost[++iotp]=x%10+'0',x/=10;
        while(iotp)Putchar(iost[iotp--]);if (ch)Putchar(ch);
    }
    void Getstr(char *s, int &l){
        for(ioc=Getchar();ioc==' '||ioc=='\n'||ioc=='\t';)ioc=Getchar();
        if(ioc==EOF)exit(0);
        for(l=0;!(ioc==' '||ioc=='\n'||ioc=='\t'||ioc==EOF);ioc=Getchar())s[l++]=ioc;s[l] = 0;
    }
    void Putstr(const char *s){for(int i=0,n=strlen(s);i<n;++i)Putchar(s[i]);}
} // namespace Fast_IO
using namespace Fast_IO;

const int MAX = 2e5+50;
const int MOD = 998244353;
const int PHI = MOD-1;

ll qpow(ll a,ll n){/* */ll res = 1LL;while(n){if(n&1)res=res*a%MOD;a=a*a%MOD;n>>=1;}return res;}
ll qpow(ll a,ll n,ll p){a%=p;ll res = 1LL;while(n){if(n&1)res=res*a%p;a=a*a%p;n>>=1;}return res;}
ll npow(ll a,ll n){/* */ll res = 1LL;while(n){if(n&1)res=res*a;a=a*a;n>>=1;if(res<0||a<0)return 0;}return res;}
ll inv(ll a){/* */return qpow(a,MOD-2);}
ll inv(ll a,ll p){return qpow(a,p-2,p);}

#define ls (p<<1)
#define rs (p<<1|1)
#define md ((l+r)>>1)

const int O = 1e5+2;
const int U = 2e5+4;
int tree[MAX*4];
inline void push_up(int p){
    tree[p] = max(tree[ls],tree[rs]);
}
inline void build(int p,int l,int r){
    if(l == r){
        tree[p] = -1;
        return;
    }
    build(ls,l,md);
    build(rs,md+1,r);
    push_up(p);
}
inline void add(int p,int l,int r,int k){
    tree[p] = k;
}
inline void update(int p,int l,int r,int ux,int uy,int k){
    if(ux <= l && uy >= r){
        add(p,l,r,k);
        return;
    }
    if(ux <= md)update(ls,l,md,ux,uy,k);
    if(uy >  md)update(rs,md+1,r,ux,uy,k);
    push_up(p);
}
inline int query(int p,int l,int r,int qx,int qy){
    int res = -1;
    if(qx <= l && r <= qy)return tree[p];
    if(qx <= md)res = max(res,query(ls,l,md,qx,qy));
    if(qy >  md)res = max(res,query(rs,md+1,r,qx,qy));
    return res;
}

int pre[100050];

int main()
{
    int T;
    scanf("%d",&T);
    while(T--){
        int n,m;scanf("%d%d",&n,&m);
        for(int i = 1;i <= n;++i){
            int t;scanf("%d",&t);
            if(t >= m)t = 1;
            else t = -1;
            pre[i] = pre[i-1] + t;
        }
        build(1,1,U);
        update(1,1,U,O,O,0);
        int ans = -1;
        for(int i = 1;i <= n;++i){
            int dpj = query(1,1,U,1,O + pre[i] - 1);
            if(dpj != -1){
                int dpi = dpj + 1;
                if(i == n)ans = dpi;
                update(1,1,U,O + pre[i],O + pre[i],dpi);
            }
        }
        printf("%d\n",ans);
    }

    return 0;
}

G:ACM is all you need

题意 | 中偏难

  • 给定一维函数上 n n n 个整数点 f i f_i fi
    定义 l o c a l   m i n local\ min local min 表示满足 f i < min ⁡ ( f i − 1 , f i + 1 ) f_i<\min(f_{i-1},f_{i+1}) fi<min(fi1,fi+1)
  • 变化,表示让所有的值 f i f_i fi 变成 f i = ∣ f i − b ∣ + b f_i=|f_i-b|+b fi=fib+b,其中 b b b 是自己选择的一个整数
    求经过一次变化, l o c a l   m i n local\ min local min 的最小值是多少
  • ∑ n ≤ 1 0 6 \sum n\le 10^6 n106
    1 ≤ f i ≤ 1 0 9 1\le f_i\le 10^9 1fi109

思路 | 思维 + 差分

  • 首先,每个点都加 b b b 当然不影响 l o c a l   m i n local\ min local min ,我们把变化看做 f i = ∣ f i − b ∣ f_i=|f_i-b| fi=fib
    看到几何意义,就是这个函数上的每个点距离 y = b y=b y=b 的直线的距离。
    我们可以当做把这个函数沿着某个 y y y 向上翻折,但是这样对整体答案不好考虑。
  • 我们只考虑单独一个位置。
    由于 l o c a l   m i n local\ min local min 只和当前位置与前后两个相邻位置有影响。
    且仔细分析,我们只用关注增减情况即可,只有四种抽象情况:
    在这里插入图片描述
  • 因为如果存在 f i = f i − 1 f_i=f_{i-1} fi=fi1 或者 f i = f i + 1 f_i=f_{i+1} fi=fi+1,这个点变化前和变化后当然都不会是 l o c a l   m i n local\ min local min
    接下来,我们分析当 y y y 处于什么情况下, l o c a l   m i n local\ min local min 的变化值
    我们记录成一个段 [ L , R , D ] [L,R,D] [L,R,D] ,表示 y ∈ [ L , R ] y\in [L,R] y[L,R] 时候, l o c a l   m i n local\ min local min 的变化值为 D D D
    当然, R R R 可以是正无穷,我们使用一个正大值 I N F INF INF 表示。(为什么 L L L 不用负大值记录,可以仔细想想)
  • 于是问题就变成,给定许多个段,求处于哪个位置, l o c a l   m i n local\ min local min 是最小的
    我们只要对段排序,位置肯定是某个端点处的位置。
    这个时候我们可能会想到,退出某个段, D D D 的影响就没了,应该把 D D D 退回,这样就写成滑动窗口了,我们可以用简单差分:再累加一个 [ R + 1 , I N F , − D ] [R+1,INF,-D] [R+1,INF,D] 的段把影响去掉即可。

代码

  • 时间复杂度: O ( n log ⁡ n ) O(n\log n) O(nlogn)
int aa[MAX];

int cnt;
struct node{
    ll L,R;
    int delta;
    bool operator<(const node &ND)const{
        if(L != ND.L)return L < ND.L;
        return R < ND.R;
    }
}seg[2 * MAX];

int main()
{
    int T = read();
    while(T--){
        int n = read();
        for(int i = 1;i <= n;++i){
            aa[i] = read();
        }

        int ans1 = 0;
        cnt = 0;
        for(int i = 2;i <= n-1;++i){
            if(aa[i] == aa[i-1] || aa[i] == aa[i+1])continue;
            ll L,R,D;
            if(aa[i-1] < aa[i] && aa[i] < aa[i+1]){
                L = (aa[i] + aa[i-1]) / 2 + 1;
                R = (aa[i] + aa[i+1] + 1) / 2 - 1;
                D = 1;
                cnt++;
                seg[cnt].L = L;
                seg[cnt].R = R;
                seg[cnt].delta = D;

                cnt++;
                seg[cnt].L = R+1;
                seg[cnt].R = INF;
                seg[cnt].delta = -D;

            }else if(aa[i-1] < aa[i] && aa[i] > aa[i+1]){
                int L1 = (aa[i] + aa[i-1]) / 2 + 1;
                int L2 = (aa[i] + aa[i+1]) / 2 + 1;
                L = max(L1,L2);
                R = INF;
                D = 1;

                cnt++;
                seg[cnt].L = L;
                seg[cnt].R = R;
                seg[cnt].delta = D;

            }else if(aa[i-1] > aa[i] && aa[i] > aa[i+1]){
                L = (aa[i] + aa[i+1]) / 2 + 1;
                R = (aa[i] + aa[i-1] + 1) / 2 - 1;
                D = 1;

                cnt++;
                seg[cnt].L = L;
                seg[cnt].R = R;
                seg[cnt].delta = D;

                cnt++;
                seg[cnt].L = R+1;
                seg[cnt].R = INF;
                seg[cnt].delta = -D;
            }else{
                ans1++;
                int L1 = (aa[i] + aa[i-1] + 1) / 2;
                int L2 = (aa[i] + aa[i+1] + 1) / 2;
                L = min(L1,L2);
                R = INF;
                D = -1;

                cnt++;
                seg[cnt].L = L;
                seg[cnt].R = R;
                seg[cnt].delta = D;
            }

        }

        sort(seg+1,seg+1+cnt);
        int ans = ans1;
        int delta = 0;

        for(int i = 1;i <= cnt;++i){
            delta += seg[i].delta;
            if(i == cnt || (seg[i].L != seg[i+1].L))ans = min(ans,ans1+delta);		// 可以想想为什么要这个if
        }

        cout << ans << '\n';

    }
    return 0;
}

H:牛牛看云

题意 | 简单偏中等

  • 给定 n , a i n,a_i n,ai,求
    ∑ i = 1 n ∑ j = i n ∣ a i + a j − 1000 ∣ \sum_{i=1}^n \sum_{j=i}^n |a_i+a_j-1000| i=1nj=inai+aj1000
  • 3 ≤ n ≤ 1 0 6 3\le n\le 10^6 3n106
    0 ≤ a i ≤ 1000 0\le a_i\le 1000 0ai1000

思路 | 思维

  • 第一个想法, 右侧的 ∑ \sum 中, a i − 1000 a_i-1000 ai1000 是一个常量,我们可以想做:
    ∑ i = 1 n ∑ j = i n ∣ a j − x ∣ \sum_{i=1}^n \sum_{j=i}^n |a_j-x| i=1nj=inajx
    其中 x = a i − 1000 x=a_i-1000 x=ai1000
    可以转化成这 n − i + 1 n-i+1 ni+1 个点到某个点 x x x 之间的距离和,但是貌似挺复杂的,我们有更简单的方法
  • 第二个想法,我们利用好 a i a_i ai 的值域比较小,我们可以把题目化成:
    a n s = p a i r x × ∣ x − 1000 ∣ ans=pair_x \times |x-1000| ans=pairx×x1000
    其中 p a i r x pair_x pairx 表示有多少对 ( i , j ) (i,j) (i,j),满足 1 ≤ i ≤ j ≤ n 1\le i\le j\le n 1ijn 满足 x = a i + a j x=a_i+a_j x=ai+aj
    我们可以记 c n t x cnt_x cntx 表示 n n n 个数字中有多少个数字为 x x x
  • 怎么算呢,如果 a i = a j a_i=a_j ai=aj ,合法对数明显为 c n t ( c n t + 1 ) 2 \frac{cnt(cnt+1)}{2} 2cnt(cnt+1)
    如果 a i ≠ a j a_i\ne a_j ai=aj ,合法对数也明显为 c n t a i × c n t a j cnt_{a_i} \times cnt_{a_j} cntai×cntaj

代码

  • 时间复杂度: O ( 100 0 2 ) O(1000^2) O(10002)
int aa[MAX];
int cnt[1005];

int cal(int x){
    return abs(x - 1000);
}

int main()
{
    ll ans = 0;
    int n = read();
    for(int i = 1;i <= n;++i){
        aa[i] = read();
        cnt[aa[i]]++;
    }

    for(int i = 0;i <= 2000;++i){
        for(int j = 0;j <= 1000;++j){
            if(i-j < 0 || i-j > 1000)continue;
            if(i - j == j){
                ans += 1LL * cnt[j] * (cnt[j] + 1) / 2 * cal(i);
                continue;
            }else if(i-j < j){
                ans += 1LL * cnt[i-j] * cnt[j] * cal(i);

            }
        }
    }

    Print(ans);
    Write();
    return 0;
}

I:B站与各唱各的

题意 | 中等

  • T T T 组样例,每组给定 n , m n,m n,m
    表示有 n n n 个人, m m m 句歌词。每个人可以选择每句歌词唱不唱,不能与他人沟通。
    若某句歌词被所有人唱了,或者所有人都没唱,那么这句是失败的,否则是成功的。
    每个人都十分聪明,求唱成功的句的期望取模 1 e 9 + 7 1e9+7 1e9+7

思路 | 数学

  • 我们随便想一想,容易得到一个策略:每个人对每句随机选择 ( 1 2 概 率 ) (\frac{1}{2} 概率) 21 唱与不唱,那么策略是最优的。
    每句假设是独立的。如果每个人采取这样的策略,那么唱失败的概率只有 2 2 n \cfrac{2}{2^n} 2n2
    容易得到,答案即为:
    m × 2 n − 2 2 n m\times \frac{2^n-2}{2^n} m×2n2n2

代码

  • 时间复杂度: O ( T log ⁡ n ) O(T\log n) O(Tlogn)
ll qpow(ll a,ll n){/* */ll res = 1LL;while(n){if(n&1)res=res*a%MOD;a=a*a%MOD;n>>=1;}return res;}
ll qpow(ll a,ll n,ll p){a%=p;ll res = 1LL;while(n){if(n&1)res=res*a%p;a=a*a%p;n>>=1;}return res;}
ll npow(ll a,ll n){/* */ll res = 1LL;while(n){if(n&1)res=res*a;a=a*a;n>>=1;if(res<0||a<0)return 0;}return res;}
ll inv(ll a){/* */return qpow(a,MOD-2);}
ll inv(ll a,ll p){return qpow(a,p-2,p);}


int main()
{
    int T;cin >> T;
    while(T--){
        int n,m;cin >> n >> m;
        cout << 1LL * m * (qpow(2,n) - 2 + MOD) % MOD * inv(qpow(2,n)) % MOD << endl;
    }
    return 0;
}

J:小朋友做游戏

  • 签到。考虑选 a a a 个吵的, b b b 个不吵的,满足 a + b = n a+b=n a+b=n a ≤ n 2 a\le \frac{n}{2} a2n
    选吵的 x x x 个,只要选幸福度最高的 x x x 个吵的即可。做一个排序然后一个前缀和即可。

K:冒险公社 (天了噜,简单题没人做?)

题意 | 简单

  • n n n 个岛,每个岛为 R G B RGB RGB 三色之一
    在这里插入图片描述
  • 3 ≤ n ≤ 1 0 5 3\le n\le 10^5 3n105

思路 | 简单dp

  • 发现,第 i i i 个预测和 [ 1 , i − 3 ] [1,i-3] [1,i3] 的所有岛都没有关系,明显符合 dp 的无后效性
    答案求的是绿色岛的最大值。
    发现和每个岛的颜色有关,直接设 d p [ i ] [ j ] [ k ] [ p ] dp[i][j][k][p] dp[i][j][k][p] 表示走完前 i i i 个岛,其中岛 i , i − 1 , i − 2 i,i-1,i-2 i,i1,i2 的颜色分别为 j , k , p j,k,p j,k,p ,合法方案中绿色岛最多的值
  • 然后考虑转移,每次多走一格岛,明显转移是从 d p [ i − 1 ] [ i 2 ] [ i 3 ] [ i 4 ] dp[i-1][i2][i3][i4] dp[i1][i2][i3][i4] 转移到 d p [ i ] [ i 1 ] [ i 2 ] [ i 3 ] dp[i][i1][i2][i3] dp[i][i1][i2][i3]
    什么时候转移呢?合法转移,看这个位置的罗盘预测是否满足即可

代码

  • 时间复杂度: O ( n × 3 4 ) O(n\times 3^4) O(n×34)
/**
0 1 2
R G B
*/
int dp[MAX][3][3][3];

int main()
{
    int n;cin >> n;
    string ss;cin >> ss;

    for(int i = 2;i < n;++i)
    for(int i1 = 0;i1 < 3;++i1)
    for(int i2 = 0;i2 < 3;++i2)
    for(int i3 = 0;i3 < 3;++i3)
        dp[i][i1][i2][i3] = -1;

    for(int i = 0;i < 3;++i)
    for(int i2 = 0;i2 < 3;++i2)
    for(int i3 = 0;i3 < 3;++i3){
        dp[1][i][i2][i3] = 0;
        if(i == 1)dp[1][i][i2][i3]++;
        if(i2 == 1)dp[1][i][i2][i3]++;
    }

    int ans = -1;

    for(int i = 2;i < n;++i)
    for(int i1 = 0;i1 < 3;++i1)
    for(int i2 = 0;i2 < 3;++i2)
    for(int i3 = 0;i3 < 3;++i3)
    for(int i4 = 0;i4 < 3;++i4)
    if(dp[i-1][i2][i3][i4] != -1){
        int pre = dp[i-1][i2][i3][i4];
        int R = 0,G = 0;

        if(i1 == 0)R++;
        else if(i1 == 1)G++;

        if(i2 == 0)R++;
        else if(i2 == 1)G++;

        if(i3 == 0)R++;
        else if(i3 == 1)G++;

        int now = 0;
        if(i1 == 1)now++;

        if(ss[i] == 'G' && G > R){
            dp[i][i1][i2][i3] = max(dp[i][i1][i2][i3],pre + now);
        }else if(ss[i] == 'R' && R > G){
            dp[i][i1][i2][i3] = max(dp[i][i1][i2][i3],pre + now);
        }else if(ss[i] == 'B' && R == G){
            dp[i][i1][i2][i3] = max(dp[i][i1][i2][i3],pre + now);
        }

        if(i == n-1)ans = max(ans,dp[i][i1][i2][i3]);
    }

    cout << ans;

    return 0;
}

L:牛牛学走路

  • 签到
举报

相关推荐

0 条评论