CF1132.Educational Codeforces Round 61(简单题解)

时间:2021-05-30 13:51:07

A .Regular Bracket Sequence

题意:给定“((” , “()” ,  “)(”,  “))”四种,问是否可以组成合法括号匹配

思路:设四种是ABCD,B可以不用管,而C在A或者D存在时可以不考虑,然后就是A=D。

#include<bits/stdc++.h>
#define ll long long
#define rep(i,a,b) for(int i=a;i<=b;i++)
using namespace std;
const int maxn=200010;
ll A,B,C,D,ans;
int main()
{
    scanf("%lld%lld%lld%lld",&A,&B,&C,&D);
    ans=B;
    if(A||D) ans+=C; ans+=2LL*min(A,D);
    if(ans==A+B+C+D) puts("1");
    else puts("0");
    return 0;
}

 

B .Discounts

题意:给定N个物品,Q次询问,每次询问给定P,表示你可以选P个,最便宜的那个不用付款,问最少付款额。

思路:排序,贪心。

#include<bits/stdc++.h>
#define ll long long
#define rep(i,w,v) for(int i=w;i<=v;i++)
using namespace std;
const int maxn=2000010;
ll a[maxn],sum,ans;
int main()
{
    int N,M,x; scanf("%d",&N);
    rep(i,1,N) scanf("%lld",&a[i]),sum+=a[i];
    sort(a+1,a+N+1);
    scanf("%d",&M);
    rep(i,1,M) {
        scanf("%d",&x);
        printf("%lld ",sum-a[N+1-x]);
    }
    return 0;
}

 

C .Painting the Fence

题意:给定长度为N个点,M条线段,问选择M-2条,最多可以覆盖多少个点(N,M<5000)

思路:首先如果点被覆盖数大于2,不用考虑了,一定被覆盖。所以先求出被1个线段覆盖的,被2覆盖的,然后枚举不选的线段,前缀和更新答案即可。

#include<bits/stdc++.h>
#define ll long long
#define rep(i,w,v) for(int i=w;i<=v;i++)
using namespace std;
const int maxn=5100;
int sum[maxn],num[3][maxn],ans,tot; pair<int,int>s[maxn];
int main()
{
    int N,M;
    scanf("%d%d",&N,&M);
    rep(i,1,M){
        scanf("%d%d",&s[i].first,&s[i].second);
        sum[s[i].first]++; sum[s[i].second+1]--;
    }
    rep(i,1,N) sum[i]+=sum[i-1];
    rep(i,1,N){
        if(sum[i]) tot++;
        num[1][i]+=num[1][i-1];
        num[2][i]+=num[2][i-1];
        if(sum[i]==1) num[1][i]++;
        if(sum[i]==2) num[2][i]++;
    }
    sort(s+1,s+M+1);
    rep(i,1,M)
     rep(j,i+1,M){
        if(s[j].first>s[i].second) ans=max(ans,tot-(num[1][s[i].second]-num[1][s[i].first-1])-(num[1][s[j].second]-num[1][s[j].
first-1])); else if(s[j].second<=s[i].second) ans=max(ans,tot-(num[1][s[j].first-1]-num[1][s[i].first-1])-(num[1][s[i].second]-num[1][s[j].second])-(num[2][s
[j].second]-num[2][s[j].first-1])); else ans=max(ans,tot-(num[1][s[j].first-1]-num[1][s[i].first-1])-(num[1][s[j].second]-num[1][s[i].second])-(num
[2][s[i].second]-num[2][s[j].first-1])); } printf("%d\n",ans); return 0; }

D .Stressful Training

题意:有N台电脑,每台电脑初始电量a[],每单位时间掉电b[],现在给一个充电器,这个充电器同一单位时间只能给一个电脑充电,问每单位时间至少充多少,满足每台电脑都至少坚持K时间。

思路:二分X,二分之后这么写呢,是线性的呢。 求出每个人最晚的一些充电时间,然后总量不大于K,然后就是线性的向前摊平,如果同一时间个数大于1,则false。

这两个限制使得每次二分是线性的。

#include<bits/stdc++.h>
#define ll long long
#define rep(i,w,v) for(int i=w;i<=v;i++)
using namespace std;
const int maxn=200010;
ll a[maxn],b[maxn],ans=-1;int N,K,vis[maxn];
bool check(ll x)
{
    int cnt=0; rep(i,1,K) vis[i]=0;
    rep(i,1,N){
        ll L=a[i];
        while(L-b[i]*(K-1)<0){
            int t=L/b[i]+1;
            if(L/b[i]+1<=K) vis[L/b[i]+1]++;
            cnt++; if(cnt==K) return false;
            L+=x;
        }
    }
    for(int i=K;i>1;i--) {
        if(vis[i]) vis[i-1]+=vis[i]-1;
    }
    if(vis[1]>1) return false;
    return true;
}
int main()
{
    ll L=0,R=1e18,Mid;
    scanf("%d%d",&N,&K);
    rep(i,1,N) scanf("%lld",&a[i]);
    rep(i,1,N) scanf("%lld",&b[i]);
    while(L<=R){
        Mid=(L+R)/2;
        if(check(Mid)) R=Mid-1,ans=Mid;
        else L=Mid+1;
    }
    printf("%lld\n",ans);
    return 0;
}

 

E .Knapsack

题意:给定8种物体的个数a[],重量分别对应1到8,给定W,问选处的不超过W的最大重量。 (W<1e18,a<1e16)

思路:二进制拆分物体,然后暴力背包,可能会空间爆炸,用ans减去没用的一些空间就够了。

这样的话,物体要从大到小排序,这样才能保证map的空间,以及减枝效果。

(这是比较暴力的做法了,有更优美的解法

#include<bits/stdc++.h>
#define ll long long
using namespace std;
map<ll,bool>mp,tmp;
map<ll,bool>::iterator it;
const int maxn=20000;
ll ans,W,a[maxn],v[maxn],sum[maxn]; int tot,cnt;
int main()
{
    scanf("%lld",&W);
    for(int i=1;i<=8;i++){
        scanf("%lld",&a[i]);
        if(!a[i]) continue; cnt++; ll t=1;
        ans=max(ans,min(W/i,a[i])*i);
        while(1){
            tot++; v[tot]=min(t,a[i])*i;
            a[i]-=min(t,a[i]); t=t*2;
            if(!a[i]) break;
        }
    }
    if(cnt==1){
        printf("%lld\n",ans); return 0;
    }
    sort(v+1,v+tot+1); reverse(v+1,v+tot+1);
    for(int i=tot;i>=1;i--) sum[i]=sum[i+1]+v[i];
    mp[0]=true;
    for(int i=1;i<=tot;i++){
        tmp.clear();
        for(it=mp.begin();it!=mp.end();it++){
            ll x=it->first;
            tmp[x]=true;
            if(x+v[i]<=W) tmp[x+v[i]]=true;
        }
        mp.clear();
        for(it=tmp.begin();it!=tmp.end();it++){
           if(it->first+sum[i+1]<ans) continue;
           mp[it->first]=true;
           ans=max(it->first,ans);
        }
    }
    printf("%lld\n",ans);
    return 0;
}

 

F .Clear the String

题意:给定长度为N的字符串,每次可以删去一段连续的相同字符,求最小删去次数。(N<500)

思路:区间DP,反过来就是“刷墙”这种题:每次可以刷一段,问最少刷的次数。 如果col[L]=col[R],我们可以先刷[L,R],然后去刷[L+1,R-1];如果中间有相同的,还可以分子区间。所以是个需要枚举中间点的O(N^3)区间DP。(比赛的时候想N^2做,WA了。

#include<bits/stdc++.h>
#define ll long long
#define rep(i,w,v) for(int i=w;i<=v;i++)
using namespace std;
const int maxn=510;
int dp[maxn][maxn]; char a[maxn];
int main()
{
    int N,tot=1; scanf("%d%s",&N,a+1);
    rep(i,2,N){
        if(a[i]!=a[i-1]) a[++tot]=a[i];
    }
    N=tot;
    memset(dp,0x3f,sizeof(dp));
    rep(len,1,N){
        rep(L,1,N-len+1){
            int R=L+len-1;
            if(len==1) dp[L][R]=1;
            else if(len==2) dp[L][R]=(a[L]==a[R]?1:2);
            else {
                dp[L][R]=len;
                if(a[L]==a[R]) dp[L][R]=min(dp[L+1][R],dp[L][R-1]);
                else{ dp[L][R]=min(dp[L+1][R],dp[L][R-1])+1;
                   rep(k,L,R) dp[L][R]=min(dp[L][R],dp[L][k]+dp[k][R]-1);
                }
            }
        }
    }
    printf("%d\n",dp[1][N]);
    return 0;
}