[Educational Codeforces Round 80] 解题报告

比赛传送门

A. Deadline

题意:

给你一个n和d (1≤n≤109, 1≤d≤109) ,问是否存在自然数x,使得 x + ⌈ d x + 1 ⌉ ⩽ n x+\left \lceil \frac{d}{x+1}\right \rceil\leqslant n x+x+1dn。存在输出YES,不存在输出NO。

题解:

节约推公式的时间,就写了一个o( n \sqrt{n} n )的暴力,从0到 d \sqrt{d} d 枚举x,再判断式子是否成立即可。
 
 

B. Yet Another Meme Problem:

题意:

有t(1≤t≤100) 组样例,每组样例给出两个整数A and B (1≤A,B≤109),问有多少对(a,b)满足1≤a≤A,1≤b≤B,且ab+a+b=conc(a,b)。其中conc(a,b)表示数字的字符连接,如conc(12,23)=1223。

题解:

设b的数字位数为w,则有ab+a+b=a⋅10w+b。整理得a⋅(10w-b-1)=0。因为a>0,得到b=10w-1,即9,99,999……剩下就不说了。
 
 

C. Two Arrays

题意:

给定n和m (1≤n≤1000, 1≤m≤10)。问有多少对数组a和b满足:
1.数组a和b长度均为m。
2.a和b的元素均为1到n的整数。
3.对于i从1到m每一位,有ai ≤ bi成立。
4.数组a为非递减数组。
5.数组b为非递增数组。
答案模1e9+7。

题解:

容易发现,只要两数组的最后一位满足条件3,根据条件4和5,可以得到条件3必定成立。于是乎,我们两层循环枚举两数组最后一位,此时我们还需要知道,为了满足条件2,3,4,且最后一位给定,还有多少情况即可,可以用dp数组预处理,dp[i][j]表示长度为i的数组,上升空间为j的数组有多少种情况。就ok了。代码如下(m为1的特判纯属是因为发现过不了样例):

#include 
using namespace std;
typedef long long ll;
const ll mod=1e9+7;
 
ll dp[15][1005];
 
int main()
{
    ll n,m;
    scanf("%lld%lld",&n,&m);
    for(int i=1;i<=n;i++)dp[1][i]=i;
    for(int i=2;i<=10;i++)
    {
        for(int j=1;j<=1000;j++)
        {
            for(int k=1;k<=j;k++)
            {
                dp[i][j]=(dp[i][j]+dp[i-1][k])%mod;
            }
        }
    }
    ll ans=0;
    if(m!=1)
    {
        for(int b=1;b<=n;b++)
        {
            for(int a=1;a<=b;a++)
            {
                ans=(ans+dp[m-1][a]*dp[m-1][n-b+1])%mod;
            }
        }
    }
    else
    {
        ans=n*(n+1)/2%mod;
    }
    printf("%lld\n",ans);
    return 0;
}

 
 

D. Minimax Problem

这题被hack了,很气,就不贴代码了。但思路应该没问题。

题意:

给定n个数组,每个数组长度为m。(1≤n≤3⋅105, 1≤m≤8)
选择其中两个数组ai和aj(可以是同一个),定义数组b为在这里插入图片描述
要求这两个数组的其中一种选法,使得
在这里插入图片描述
输出选择的数组的下标。

题解:

首先想到二分这个最小值的最大值,然后考虑如何check。
如果存在两个数组,使得每一位上的最大值大于当前二分值,则check成功。
如果暴力枚举两数组,做法o(n²)显然过不了。
m只有8,考虑转化成二进制。
check当前二分值p的思路:把每个数组转化成二进制数,转化方式为,如果当前位的数大于等于p,则二进制下该位为1,否则为0。然后就会发现当存在两个数的或值为2m-1,则check成功。而m只有8,可能出现的二进制值不过256。所以可以用一个长度为256的数组记录这一个数是否出现,然后暴力两层循环判断是否存在两个数满足条件。
 
 

E. Messenger Simulator

题意:

这是一道题目背景比模型化后的题意更好理解的题。就是消息列表里有n个人的消息,编号从1到n,在列表里从上到下的顺序也是1到n的一个排列。
当某个人给你发消息后,他在列表中的位置会瞬间蹦到第一个,而在他之前的人会往后移一格。
初始时列表里从上到下是1到n。
现在给你一个n和m (1≤n,m≤3⋅105)表示n个人, 在初始情况后又收到m条消息。
接下来m个数,表示收到的是谁的消息。
要求输出每个人的消息在列表中最靠前和最靠后的位置。
样例:
[Educational Codeforces Round 80] 解题报告_第1张图片

题解:

首先,容易处理出最靠前的位置。当这个人发过消息,那他最靠前的位置为1,否则为他初始位置。
现在思考最靠后的位置。当一个人出现后,他的位置变成第一,距离他下次变成第一前,这段时间,他的排名会往后掉发过消息的人种数个位置。
比如:1 3 2 2 1,1在变成第一后,往后掉了两个位置。
所以我们要找的就是,对于a来说,两个a之间相隔的数的种类数。
处理边界情况,神奇地想到在开头加上1到n的倒序表示初始化的情况。如样例变成5 4 3 2 1 3 5 1 4
处理起来会更方便。而像2这样只在初始状态时出现的,则从它记录到结尾的那个数即可。
于是乎问题变成了,n个区间,询问区间数的种类数。
用了莫队o(n n \sqrt{n} n )过了。
代码如下:

#include 
using namespace std;
typedef long long ll;
const ll mod=1e9+7;
int ans1[300005];
int ans2[300005];
int a[600005];
vector<int>v[300005];
 
typedef struct
{
    int l,r,id,B;
}Query;
 
Query Q[1000005];
 
bool cmp(Query a,Query b)
{
    if(a.B==b.B)return a.r<b.r;
    else return a.B<b.B;
}
 
int mp[300005];
int main()
{
    int n,m;
    scanf("%d%d",&n,&m);
    int N=n+m;
    int B=sqrt(N);
    for(int i=1;i<=n;i++)a[i]=n-i+1,ans1[i]=i;
    for(int i=n+1;i<=n+m;i++)scanf("%d",&a[i]),ans1[a[i]]=1;
    for(int i=1;i<=N;i++)v[a[i]].push_back(i);
    int qn=0;
    for(int i=1;i<=n;i++)
    {
        for(int j=0;j<v[i].size();j++)
        {
            if(j==v[i].size()-1)Q[qn++]=Query{v[i][j],N,i,(v[i][j]-1)/B+1};
            else Q[qn++]=Query{v[i][j],v[i][j+1],i,(v[i][j]-1)/B+1};
        }
    }
    sort(Q,Q+qn,cmp);
    int l=1,r=1,ans=1;
    mp[a[1]]=1;
    for(int i=0;i<qn;i++)
    {
        while(r<Q[i].r){r++;mp[a[r]]++;if(mp[a[r]]==1)ans++;}
        while(l>Q[i].l){l--;mp[a[l]]++;if(mp[a[l]]==1)ans++;}
        while(r>Q[i].r){mp[a[r]]--;if(mp[a[r]]==0)ans--;r--;}
        while(l<Q[i].l){mp[a[l]]--;if(mp[a[l]]==0)ans--;l++;}
        ans2[Q[i].id]=max(ans2[Q[i].id],ans);
    }
    for(int i=1;i<=n;i++)printf("%d %d\n",ans1[i],ans2[i]);
    return 0;
}

F. Red-Blue Graph

没时间了,不会。

你可能感兴趣的:(codeforces,题解)