Educational Codeforces Round 61题解

A题

首先)(其实相当于1个即可

所以如果1和4相等并且大于等于1即可

#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
typedef pair<int,int> pll;
const int N=5e5+10;
int main(){
    ios::sync_with_stdio(false);
    int a,b,c,d;
    cin>>a>>b>>c>>d;
    c=min(c,1);
    if(a==d&&a>=c){
        cout<<1<<endl;
    }
    else{
        cout<<0<<endl;
    }
}
View Code

B题

直接找到K大数就行

#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
typedef pair<int,int> pll;
const int N=5e5+10;
int n,m;
int a[N],b[N];
int main(){
    ios::sync_with_stdio(false);
    cin>>n;
    int i;
    ll sum=0;
    for(i=1;i<=n;i++){
        cin>>a[i];
        sum+=a[i];
    }
    sort(a+1,a+1+n);
    reverse(a+1,a+1+n);
    cin>>m;
    for(i=1;i<=m;i++){
        cin>>b[i];
    }
    for(i=1;i<=m;i++){
        cout<<sum-a[b[i]]<<endl;
    }
}
View Code

C题

首先发现就是删掉两个,因此直接枚举两个人

写了发线段树出了点小问题,后来改成直接前缀和维护1的个数,因为只有区间内的1才会消失

#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
typedef pair<int,int> pll;
const int N=2e5+10;
int cnt[N];
int sum[N];
struct node{
    int l,r;
}s[N];
bool cmp(node a,node b){
    if(a.l==b.l)
        return a.r<b.r;
    return a.l<b.l;
}
int main(){
    ios::sync_with_stdio(false);
    int n,m;
    cin>>n>>m;
    int i,j;
    for(i=1;i<=m;i++){
        cin>>s[i].l>>s[i].r;
        for(j=s[i].l;j<=s[i].r;j++)
            cnt[j]++;
    }
    sort(s+1,s+1+m,cmp);
    int ans=0;
    for(i=1;i<=m;i++){
        for(j=s[i].l;j<=s[i].r;j++){
            cnt[j]--;
        }
        int tmp=0;
        for(j=1;j<=n;j++){
            if(cnt[j]==1){
                sum[j]=sum[j-1]+1;
            }
            else{
                sum[j]=sum[j-1];
            }
            if(cnt[j])
                tmp++;
        }
        for(j=i+1;j<=m;j++){
            ans=max(ans,tmp-sum[s[j].r]+sum[s[j].l-1]);
        }
        for(j=s[i].l;j<=s[i].r;j++)
            cnt[j]++;
    }
    cout<<ans<<endl;
}
View Code

D题

这次二分答案是肯定的,在check的时候,其实刚开始想的是复杂度低的正解,也就是找到每个点最晚什么时候要+,之后不断往后跳到K位置,之后找前缀和,如果每个i的前缀和大于i,说明前i个时间点

要大于i的次数充电,那么显然是非法的,这里用了一个贪心+前缀和,可惜写的时候没想清楚,没写出来这个写法,主要是在找到第一次最晚后,不知道下面继续怎么做

另一种用优先队列维护的姿势比较好想,就是每次找到能撑天数最少的点充电,如果发现不行就返回false,这种写法复杂度较高,理论上能过,但是需要一些优化的姿势,我是卡过去的

#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
typedef pair<int,int> pll;
const int N=2e5+10;
int n,k;
ll a[N],b[N];
struct node{
    ll x,y;
    ll k;
    bool operator <(const node &t) const{
        if(k!=t.k)
            return k>t.k;
        if(y!=t.y)
            return y>t.y;
        return x>t.x;
    }
};
bool check(ll x){
    priority_queue<node> q;
    int i;
    for(i=1;i<=n;i++){
        ll tmp=a[i]/b[i];
        q.push({a[i],b[i],tmp});
    }
    if(q.empty())
        return true;
    for(i=1;i<=k;i++){
        auto t=q.top();
        q.pop();
        if(t.k<i-1)
            return false;
        if(t.k>k)
            return true;
        q.push({t.x+x,t.y,(t.x+x)/t.y});
    }
    return true;
}
int main(){
    ios::sync_with_stdio(false);
    cin>>n>>k;
    int i;
    for(i=1;i<=n;i++)
        cin>>a[i];
    for(i=1;i<=n;i++)
        cin>>b[i];
    ll l=0,r=2e12;
    while(l<r){
        ll mid=l+r>>1;
        if(check(mid))
            r=mid;
        else
            l=mid+1;
    }
    if(l==2e12)
        cout<<-1<<endl;
    else
        cout<<l<<endl;
}
优先队列
#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
typedef pair<int,int> pll;
const int N=2e5+10;
int n,k;
ll a[N],b[N];
int cnt[N];
bool check(ll x){
    memset(cnt,0,sizeof cnt);
    int tot=0;
    int i;
    for(i=1;i<=n;i++){
        ll tmp=a[i]/b[i];
        tmp+=1;
        ll t=a[i];
        while(tmp<k&&tot<k){
            cnt[tmp]++;
            ++tot;
            t+=x;
            tmp=(t/b[i])+1;
        }
        if(tot==k)
            return false;
    }
    for(i=1;i<=k;i++){
        cnt[i]+=cnt[i-1];
        if(cnt[i]>i)
            return false;
    }
    return true;
}
int main(){
    ios::sync_with_stdio(false);
    cin>>n>>k;
    int i;
    for(i=1;i<=n;i++)
        cin>>a[i];
    for(i=1;i<=n;i++)
        cin>>b[i];
    ll l=0,r=2e12;
    while(l<r){
        ll mid=l+r>>1;
        if(check(mid))
            r=mid;
        else
            l=mid+1;
    }
    if(l==2e12)
        cout<<-1<<endl;
    else
        cout<<l<<endl;
}
贪心+前缀和

E题

这题确实转化比较巧妙,但是对应的思路其实以前出现过,就是找到lcm的值,我记得以前有一个青蛙跳石子也是这样的,需要牢记一下。

我们看到这个题会发现需要剪枝或者转化,并不是简单的背包dp,那么这里就有一步重要的转化,我们发现只有1-8这些数,他们的最小公倍数是840,而每个值取法都能表示为 c=840/i*p+q,where 0q<840/i

因此我们维护dp状态为f[i][j],表示前i个权值取到j容量可以取到最大的840个数是多少。

之后就是dp转移,然后枚举状态取max,注意不能超过最大值w

#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
typedef pair<int,int> pll;
const int N=2e5+10;
ll cnt[N];
ll f[10][840*8+10];
int main(){
    ios::sync_with_stdio(false);
    ll w;
    memset(f,-1,sizeof f);
    cin>>w;
    int i,j,k;
    ll ans=0;
    for(i=1;i<=8;i++){
        cin>>cnt[i];
        ans+=cnt[i]*i;
    }
    if(ans<=w){
        cout<<ans<<endl;
        return 0;
    }
    f[0][0]=0;
    for(i=1;i<=8;i++){
        for(j=0;j<=840*8;j++){
            if(f[i-1][j]!=-1){
                ll tmp=840/i;
                tmp=min(tmp,cnt[i]);
                for(k=0;k<=tmp;k++){
                    f[i][j+k*i]=max(f[i][j+k*i],f[i-1][j]+(cnt[i]-k)/(840/i));
                }
            }
        }
    }
    ans=0;
    for(i=0;i<=840*8;i++){
        if(i>w)
            break;
        if(f[8][i]!=-1){
            ans=max(ans,i+min(f[8][i],(w-i)/840)*840);
        }
    }
    cout<<ans<<endl;
}
View Code

F题

比较朴素的区间dp,只要三维之后看看前后点是否相同,相同就能-1,别人的解法是中间点和末尾的比较,我们直接比较前后是一样的,虽然中间转移的时候可能状态不一样,但是结果是相等的

这是因为我们小区间的开头其实就是大区间的中间点,这和别人的转移是一样的。

前后相同就能-1就是因为可以先删中间。因为我们枚举到了所有状态,所以转移是正确的

#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
typedef pair<int,int> pll;
const int N=5e5+10;
int f[510][510];
int main(){
    ios::sync_with_stdio(false);
    int n;
    string s;
    cin>>n>>s;
    int i,j,k;
    s=" "+s;
    memset(f,0x3f,sizeof f);
    for(int len=1;len<=n;len++){
        for(i=1;i+len-1<=n;i++){
            j=i+len-1;
            if(len==1){
                f[i][i]=1;
                continue;
            }
            for(k=i;k<j;k++){
                f[i][j]=min(f[i][j],f[i][k]+f[k+1][j]-(s[i]==s[j]));
            }
        }
    }
    cout<<f[1][n]<<endl;
}
View Code

 G题

想了有些时间,终于发现了题目的性质,但是在最后求解的时候犯糊涂了

心路历程:

正向看了一下,发现不太行,转移状态过多,那么正难则反,反向看,又结合题目当中的信息,一个点必须要连后面最接近他的比他大的点

这么一看,每个点只有一个父亲节点,而之前往前看的时候注意到每个点有很多儿子节点,那不就是树吗

于是想到单调栈建树。建完之后一个想法就呼之欲出,每个点的贡献就是给他和他的儿子长度+1,容易想到dfs序建线段树实现区间加

想到这里我又犯模糊了,真实区间又不是连续的,怎么求最大值,其实这是线段树最简单的套路,打的时候有些意识模糊。因为现在每个点的含义就是当前状态下的这个点能连出的最长路,因此只要查询tr[1].mx就能找到全部区间内的最大值

#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
typedef pair<int,int> pll;
const int N=2e6+10;
int n,k;
int h[N],ne[N],e[N],idx;
int dfn[N],id[N],sz[N],times;
vector<int> ans;
int rt;
int q[N];
int a[N];
struct node{
    int l,r;
    int mx;
    int lazy;
}tr[N<<2];
void add(int a,int b){
    e[idx]=b,ne[idx]=h[a],h[a]=idx++;
}
void dfs(int u){
    int i;
    dfn[u]=++times;
    id[times]=u;
    sz[u]=1;
    for(i=h[u];i!=-1;i=ne[i]){
        int j=e[i];
        dfs(j);
        sz[u]+=sz[j];
    }
}
void build(int u,int l,int r){
    if(l==r){
        tr[u]={l,r};
    }
    else{
        tr[u]={l,r};
        int mid=l+r>>1;
        build(u<<1,l,mid);
        build(u<<1|1,mid+1,r);
    }
}
void pushup(int u){
    tr[u].mx=max(tr[u<<1].mx,tr[u<<1|1].mx);
}
void pushdown(int u){
    int x=tr[u].lazy;
    tr[u<<1].mx+=x;
    tr[u<<1|1].mx+=x;
    tr[u<<1].lazy+=x;
    tr[u<<1|1].lazy+=x;
    tr[u].lazy=0;
}
void modify(int u,int l,int r,int x){
    if(tr[u].l>=l&&tr[u].r<=r){
        tr[u].mx+=x;
        tr[u].lazy+=x;
        return ;
    }
    if(tr[u].lazy)
        pushdown(u);
    int mid=tr[u].l+tr[u].r>>1;
    if(l<=mid)
        modify(u<<1,l,r,x);
    if(r>mid)
        modify(u<<1|1,l,r,x);
    pushup(u);
}
int main(){
    ios::sync_with_stdio(false);
    cin>>n>>k;
    int i;
    rt=n+1;
    memset(h,-1,sizeof h);
    for(i=1;i<=n;i++){
        cin>>a[i];
    }
    int hh=0,tt=-1;
    for(i=1;i<=n;i++){
        while(hh<=tt&&a[q[tt]]<a[i]){
            add(i,q[tt]);
            tt--;
        }
        q[++tt]=i;
    }
    while(hh<=tt){
        add(rt,q[tt]);
        tt--;
    }
    dfs(rt);
    build(1,1,n+1);
    for(i=1;i<=k;i++){
        modify(1,dfn[i],dfn[i]+sz[i]-1,1);
    }
    ans.push_back(tr[1].mx);
    for(i=k+1;i<=n;i++){
        modify(1,dfn[i-k],dfn[i-k]+sz[i-k]-1,-1);
        modify(1,dfn[i],dfn[i]+sz[i]-1,1);
        ans.push_back(tr[1].mx);
    }
    for(auto x:ans){
        cout<<x<<" ";
    }
    cout<<endl;
}
View Code

 

posted @ 2020-12-15 22:43  朝暮不思  阅读(73)  评论(0编辑  收藏  举报