Educational Codeforces Round 80 (Rated for Div. 2)(A~E)

总题目传送门

A. Deadline

分析

  • 题意
  1. 给我们一个表达式 x + d/(x + 1) 让求它的最小值,
  • 思路
  1. 基本不等式: a + b > = 2 s q r t ( a b ) a+b>=2*sqrt(a*b) ,当且仅当 a 、b>0, 且 a == b 的时候
  2. 对 原式变形为:[x + 1 + d/(x+1)] - 1, 之后对中括号部分应用基本不等式

代码

#include <bits/stdc++.h>
using namespace std;
void fre() { freopen("A.txt", "r", stdin); freopen("Ans.txt","w",stdout); }
void Fre() { freopen("A.txt", "r", stdin);}
#define ios ios::sync_with_stdio(false)
#define Pi acos(-1)
#define pb push_back
#define fi first
#define se second
#define ll long long
#define ull unsigned long long 
#define db double
#define Pir pair<int, int>
#define PIR pair<Pir, Pir>
#define INF 0x3f3f3f3f
#define mod 998244353

const int mxn = 2e5 + 10;
int n, d;

int main()
{
    /* fre(); */
    int T;
    scanf("%d", &T);
    while(T --)
    {
        scanf("%d %d", &n, &d);
        if(ceil(2 * sqrt(d) - 1) <= n)
            printf("YES\n");
        else
            printf("NO\n");
    }




    return 0;
}

B. Yet Another Meme Problem

分析

  • 题意
  1. 给我们两个数 A、B(1<= A、B<=1e9),又给我们两个数变量a,b,这两个变量的取值范围是: 1 < = a < = A , 1 < = b < = B 1<=a<=A,1<=b<=B ,
  2. 定义 c o n c ( x , y ) conc(x,y) ,举例子conc(12,34)=1234,
  3. 问使这个: a b + a + b = = c o n c ( a , b ) a*b+a+b==conc(a,b) 成立的对应的a、b的数量
  • 思路
  1. 这么大的范围肯定不是暴力的,一定有规律在里面,而且给我们的是一个等式,我们可以通过对这个等式进行变形,获得更多有用的条件,变形如下:
    1. a b + a + b = = a b a*b+a+b==ab
    2. a b + a = a 00... a*b+a=a00... ,这里0的数量位b的十进制位有几位
    3. b + 1 = 100.... b+1=100....
    4. b = 100... 1 b=100... - 1 ,从这里可以推测出等式与a的值无关,所以a去任意值都可以(前提是:1<=a<=A),我们考虑b的取值为:b=9、b=99、b=999、b=999…,可以看出b的取值与B限制有关,
    5. 最终答案就是:A*b的取值个数

代码

#include <bits/stdc++.h>
using namespace std;
void fre() { freopen("A.txt", "r", stdin); freopen("Ans.txt","w",stdout); }
void Fre() { freopen("A.txt", "r", stdin);}
#define ios ios::sync_with_stdio(false)
#define Pi acos(-1)
#define pb push_back
#define fi first
#define se second
#define ll long long
#define ull unsigned long long 
#define db double
#define Pir pair<int, int>
#define PIR pair<Pir, Pir>
#define INF 0x3f3f3f3f
#define mod 998244353

const int mxn = 2e5 + 10;
int n, d;

int main()
{
    /* fre(); */
    int T;
    scanf("%d", &T);
    while(T --)
    {
        ll a; string s;
        cin >> a >> s;
        ll b = s.size();
        int fg = 0;
        for(auto x : s)
        {
            if(x != '9')
            {
                fg = 1;
                break;
            }
        }
        b -= fg;
        printf("%lld\n", a * b);
    }




    return 0;
}

C. Two Arrays

分析

  • 题意
  1. 给我们两个值 n、m
  2. 让我们找出两个序列a、b,它们的长度均为m,元素的范围均为[1,n]
  3. 且a[I]<=b[I],1<=I<=m
  4. a序列位非严格递增,b序列为非严格递减
  5. 问这样的a、b序列存在多少对?
  • 思路
  1. 首先我们将b序列进行翻转(即: b i = b m i + 1 , 1 < = i < = m / 2 b_i=b_{m-i+1},1<=i<=m/2 ),这样b就变成了非严格递增,且b[i]>=a[m] 因此我们可以a、b拼接成一个 非严格递增的序列c,那么c中有2*m个元素,且它们的范围均在1~n之间,
  • 思路1——利用c序列的递增性,进行dp
    1. 我们定义dp[i][j]的含义是:长度为i,以j作为结尾元素的序列,
    2. 状态转移方程: d p [ i ] [ j ] = d p [ i 1 ] [ j ] + d p [ i ] [ j 1 ]         dp[i][j]=dp[i-1][j]+dp[i][j-1]~~~~~~~ ,dp[i][j]的状态转移有两部分:

    第一部分:规定dp[i][j]所代表的序列的最后一位(即:第i为)为j,那么剩下从第1位~到第i-1位的方案数是dp[i-1][j]
    第二部分:规定dp[i][j]所代表的序列的最后一位不是j,那么总的1~i位的方案数位dp[i][j-1]
    这两部分组成了dp[i][j]的方案数的全集

  • 思路二——组合数学
  1. 通过对a、b拼接之后产生了一个有2m个元素的c序列,那么问题就行相当于转化为了,从n个数字中选择2m个元素,每个元素可以被重复选择,
  2. 翻译一下:就是从把2*m个物品放入n个抽屉中,有的抽屉可以一个物品放,
  3. 我们在转化一下:我们先把每个抽屉中,先提前放入一个物品,这样问题就转化成了:我们把2*m+n个物品翻入n个抽屉中,且每个抽屉至少放入一个物品,
  4. 这样我们在利用插板法,把这个2m+n个分隔成n块,2m+n个物品有2m+n-1个空隙,我们从这个2m+n-1个空隙中选择n-1空隙插入板子,
  5. 此时答案为: C 2 m + n 1 n 1 C_{2*m+n-1}^{n-1} ,最后注意 求解的时候,同时存在除法和取模 所以别忘了 乘以 1e9+7的逆元

代码(dp)

#include <bits/stdc++.h>
using namespace std;
void fre() { freopen("A.txt", "r", stdin); freopen("Ans.txt","w",stdout); }
void Fre() { freopen("A.txt", "r", stdin);}
#define ios ios::sync_with_stdio(false)
#define Pi acos(-1)
#define pb push_back
#define fi first
#define se second
#define ll long long
#define ull unsigned long long 
#define db double
#define Pir pair<int, int>
#define PIR pair<Pir, Pir>
#define INF 0x3f3f3f3f
#define mod (ll)(1e9 + 7)

const int mxn = 3e5 + 10;
int dp[25][1005];

int main()
{
    /* fre(); */
    int n, m;
    scanf("%d %d", &n, &m);
    for(int i = 1; i <= n; i ++)
        dp[1][i] = 1;

    m *= 2;
    for(int i = 2; i <= m; i ++)
        for(int j = 1; j <= n; j ++)
            dp[i][j] = (dp[i][j - 1] + dp[i - 1][j]) % mod;

    int res = 0;
    for(int i = 1; i <= n; i ++)
        res = (res + dp[m][i]) % mod;
    printf("%d", res);

    return 0;
}


代码(组合数学)

#include <bits/stdc++.h>
using namespace std;
void fre() { freopen("A.txt", "r", stdin); freopen("Ans.txt","w",stdout); }
void Fre() { freopen("A.txt", "r", stdin);}
#define ios ios::sync_with_stdio(false)
#define Pi acos(-1)
#define pb push_back
#define fi first
#define se second
#define ll long long
#define ull unsigned long long 
#define db double
#define Pir pair<int, int>
#define PIR pair<Pir, Pir>
#define INF 0x3f3f3f3f
#define mod (ll)(1e9 + 7)

const int mxn = 3e5 + 10;

ll q_pow(ll a, ll b)
{
    ll res = 1;
    while(b)
    {
        if(b & 1) res = res * a % mod;
        a = a * a % mod;
        b >>= 1;
    }
    return res;
}

ll cal(ll a, ll b)
{
    ll f[mxn];
    f[0] = 1;
    for(int i = 1; i <= 1025; i ++)
        f[i] = f[i - 1] * i % mod;
    
    ll up = f[a];
    ll down = f[b] * f[a - b] % mod;

    return up * q_pow(down, mod - 2) % mod;
}


int main()
{
    /* fre(); */
    int n, m;
    scanf("%d %d", &n, &m);
    printf("%lld", cal(2 * m + n - 1, n - 1));

    return 0;
}

D. Minimax Problem

分析(思维+二分+二进制枚举状态)

  • 题意
  1. 给我们n(1<=n<=3e5)个数组 a 1 a 2 . . . a n a_1a_2...a_n ,这些数组的长度均为:m( m < = 8 m<=8 ),现在让我们从中选择两个序列i、j出来,使得产生一个新的序列c( c x = m a x ( a i , x , a j , x , 1 < = x < = m c_x=max(a_{i,x},a_{j,x},1<=x<=m ),问我们通过合理的选择i、j来使c中的最小的元素尽可能的大,输入i,j (注意:i可以与j相同)
  • 思路
  1. 这一题的思路不得不让人觉得奇妙!!!

  2. 看到:最小的元素尽可能的大,其实我们可以考虑一下二分,这题的如果用二分,我们肯定是在[1,1e9]的范围内枚举c中最小元素的值设为md,能否通过在n中合理的选择两个序列来实现这个最小的枚举值md?我们肯定不可以用两层for循环暴力枚举合适的序列 a i , a j a_i,a_j ,之后在用一层for循环枚举判读每个 a i , a j a_i,a_j 位置,这里有两个难点:1不能不能暴力枚举ai、aj序列、2不能对枚举出来的两个序列进行一位一位的枚举判读,这样两个难点是我们要优化的地方

  3. 于是我们就见到了神奇的 二进制枚举, 那么怎么枚举呢?

    1. 举个例子:
    2. 两个序列:a1 = { 1, 2, 3 ,4}、a2{2、1、3、2},我们假设m = 4,枚举最小值md=2,我们下面要这两个序列要做操作是如果 序列中的某个元素>=md,它所对应的二进制位1,否则0,,,那么转化完之后对应的为两个二进制数字:a1->b1=0111,a2->b2=1011,通过这样的转化我们可以将n个a数组,转化位n个二进制数,而这样的二进制数最多有 t=1<<m个,t<=256个,这样我们就可以两次for循环暴力枚举这个不超过256个状态了,这个过程的复杂度最大位256*256,这就解决了第1个难题, 接下来我们看看第二个难题是怎么解决的:我们判断: b 1 b 2 = = ( 1 < < = m ) 1 b1 | b2==(1<<=m)-1 这个等式是否成立,如果成立就意味着: b 1 b 2 = 1111 b1|b2=1111 ,否则会出现例如 1101 0101 1110 1101、0101、1110 等等情况,都表明b1、b2的对应的二进制位上出现同时存在0的情况,那么翻译到 a 1 a 2 a1、a2 两个序列中的意思是,就是这两个序列中的某些个位置上的元素同时出现了<md的情况,(通过这个 | 操作元素符以O(1)的时间完成了对两个二进制数的判读,间接的完成了对序列a1、a2的元素的判断,这就解决了第2个难题了

  4. 通过这个例子我们大致明白了,二进制枚举的过程,其它的就是代码问题,剩下直接看代码

总结

1.对于我们时候二分法枚举答案的时候,对与 数据量大、所有状态难以一一暴力比较的时候,我可以考虑用 二进制枚举,来优化 暴力枚举时的状态过多的问题

代码

#include <bits/stdc++.h>
using namespace std;
void fre() { freopen("A.txt", "r", stdin); freopen("Ans.txt","w",stdout); }
void Fre() { freopen("A.txt", "r", stdin);}
#define ios ios::sync_with_stdio(false)
#define Pi acos(-1)
#define pb push_back
#define fi first
#define se second
#define ll long long
#define ull unsigned long long 
#define db double
#define Pir pair<int, int>
#define PIR pair<Pir, Pir>
#define INF 0x3f3f3f3f
#define mod (ll)(1e9 + 7)

const int mxn = 3e5 + 10;
int ar[mxn][10];
int n, m;
int a1, a2;

void rd(int & x)
{
    int ans = 0, f = 1; char ch = getchar();    
    while(! isdigit(ch)) { if(ch == '-') f = -1; ch = getchar(); } 
    while(  isdigit(ch)) { ans *= 10; ans += ch - '0'; ch = getchar(); }
    x = ans;
}

bool judge(int md)
{
    vector<int> bit(1 << m, -1);        //n个数列最多产生,1 << m 种情况(每种情况 经过二进制转化之后 对应一个 bit容器下标),初始化为-1:表示 数对应的情况不存在
    //遍历统计 哪些情况出现过
    for(int i = 0; i < n; i ++)
    {
        int idx = 0;
        for(int j = 0; j < m; j ++)
            if(ar[i][j] >= md)
                idx ^= (1 << j);
        bit[idx] = i;                   //bit容器存储对应情况的 数组下标
    }

    //特殊情况(作为答案的两个数组 是同一个数组)
    for(int i = 0; i < (1 << m); i ++)
        if(bit[(1 << m) - 1] != -1)
        {
            a1 = a2 = bit[(1 << m) - 1];    
            return true;
        }

    //暴力枚举255 x 255 种情况
    for(int i = 0; i < (1 << m); i ++)
        for(int j = 0; j < (1 << m); j ++)
        {
            if(bit[i] != -1 && bit[j] != -1 && (i | j) == (1 << m) - 1)
            {
                a1 = bit[i], a2 = bit[j]; return true;
            }
        }

    return false;
}




int main()
{
    /* fre(); */
    /* rd(n), rd(m); */
    scanf("%d %d", &n, &m);
    for(int i = 0; i < n; i ++)
        for(int j = 0; j < m; j ++)
            /* rd(ar[i][j]); */
            scanf("%d", &ar[i][j]);

    int l = 0, r = 1e9;
    while(l <= r)
    {
        int md = l + r >> 1;
        if(judge(md))
            l = md + 1;
        else
            r = md - 1;
    }

    printf("%d %d", a1 + 1, a2 + 1);

    return 0;
}


E. Messenger Simulator(树状数组/线段树)

分析

  • 题意
  1. 给我们一个长度位n的序列a:1,2,3…n,又给我们m个在0~n范围的内的数的序列b,对与bi个数,我们将a中值位bi的元素挪到 第一个位置,在bi之前的元素均向后挪1位,当我们i从1到m进行m次这样的操作以后,问我们a中的每个元素,出先过的最大、最小位置分别是几?
  • 思路
  1. 这一题中如果我们用暴力的模拟方法的话我们发现,占用复杂度较高的过程是:把某个元素a中的某个元素bi移动到首位置之后,我们要把所以有在bi之前的元素都向后平移1位,由于a中的元素非常多,我们不可能一位一位的向后平移
  2. 而在这一题中,我们通过巧妙的 在a序列的前面预留m个位置这样如果有元素bi被平移到a首位置的时候,我们直接放到提前放到 预留的m个空位中的一个,这样就避免了 『将bi之前所有的元素向后平移1位的过程了』,
  3. 其次用 我们用 树状数组/线段树 来快速的位置 每个元素的前面的 有多少个元素

举例
a数组:1 2 3 4
b数组:3 2
我们预留空位之后a数组变为:0 0 1 2 3 4,
在第一次操作之前:我们 用树状数组统计维护将要被操做元素3之前的元素个数ct,并且维护3的最大位置 r[3]=max(3, ct+1)
第一次操作:我们把3平移到首位:0 3 1 2 0 4,
在第二次操作之前:我们 用树状数组统计维护将要被操做元素2之前的元素个数ct,并且维护2的最大位置 r[2]=max(2, ct+1)
第二次操作:我们包2平移的首位:2 3 1 0 0 4,
最后我们 在对所有元素跑一遍 树状数组,统计每个元素的前面有几个元素,并且维护:对应每个元素 位置最大值

对于a中某个元素,位置最小值,只要这个元素在b中出现过,那么就是1,

代码

#include <bits/stdc++.h>
using namespace std;
void fre() { freopen("A.txt", "r", stdin); freopen("Ans.txt","w",stdout); }
void Fre() { freopen("A.txt", "r", stdin);}
#define ios ios::sync_with_stdio(false)
#define Pi acos(-1)
#define pb push_back
#define fi first
#define se second
#define ll long long
#define ull unsigned long long 
#define db double
#define Pir pair<int, int>
#define PIR pair<Pir, Pir>
#define INF 0x3f3f3f3f
#define mod (ll)(1e9 + 7)

const int mxn = 6e5 + 10;       //因为要预留空间,所以数组c要开2倍大
int c[mxn];
int l[mxn], r[mxn];
int pos[mxn];
int n, m;

int lowbit(int x) { return x & (-x); }
void add(int p, int t)
{
    while(p < mxn) { c[p] += t; p += lowbit(p); }
}

int ask(int p)
{
    int sum = 0;
    while(p) { sum += c[p]; p -= lowbit(p); }
    return sum;
}

int main()
{
    /* fre(); */
    int n, m; 
    scanf("%d %d", &n, &m);
    for(int i = 1; i <= n; i ++)
    {
        l[i] = r[i] = i;
        add(i + m, 1);
        pos[i] = i + m;
    }
    
    int last = m;
    while(m --)
    {
        int x;
        scanf("%d", &x);
        l[x] = 1;                   
        
        int num = ask(pos[x]);
        r[x] = max(r[x], num);          //维护最大值
        if(num == 1) continue;          //如果时在第1位直接跳过
        add(pos[x], -1);                //移出
        pos[x] = last;                  //添加到第1位,所在的位置
        add(pos[x], 1);                 //添加到第1位

        last --;                        //第一位 所在的位置往前挪1
    }

    //重新维护一下所有数的位置
    for(int i = 1; i <= n; i ++)
        r[i] = max(r[i], ask(pos[i]));

    for(int i = 1; i <= n; i ++)
        printf("%d %d\n", l[i], r[i]);

    return 0;
}


猜你喜欢

转载自blog.csdn.net/qq_34261446/article/details/106693376