Educational Codeforces Round 61 (Rated for Div. 2)

Educational Codeforces Round 61 (Rated for Div. 2) 题解

教育场真的好好教育了我(原来教育场是 7problems/2h)

A. Regular Bracket Sequence

题意

给出四组"((","()",")(","))"的个数,判断能否排成能够匹配的括弧

思路

  • 观察知:1,4类型的括弧数量应相等
  • 如果有1,4类型的括弧存在,则3类型的可以有无限个,即放在1,4类型中间
  • 2类型括弧不影响

    实现细节(略)

B. Discounts

题意

折扣\(q_i\)会免掉\(q_i-1\)贵的钱,排序即可解决

实现细节(略)

C. Painting the Fence

题意

给出n个线段,求(n-2)个线段所能覆盖的最大长度

思路

  • 看起来好像线段树的变种题目:
    • 先对n个线段建树,然后枚举删除的两条边
    • 可以维护长度和覆盖的次数(应该能行)?
    • 删边的话就是整个区段-1
    • 复杂度O(N^2logN)(N=5000)>1e8
    • 很可能被卡掉
  • 在讲到n个线段覆盖长度的问题实际上有一种简单方法,打标记+前缀和
    • a[l]++,b[r+1]--;
    • 这样前缀和sum[i]能表示[i]被覆盖的次数
    • 那么如何删边呢?我们只需要删除边上sum[i]=1的区域,因为sum[i]>1的区域有多条线段覆盖
    • 这里的实现细节可以去处理边缩成sum[i]=1的区域
    • 最后一个问题:我们要删除两条边,如果sum[i]>1的区域恰好都在这两条边内,岂不是应该删去而没有删
    • 所以我们可以枚举所有的点sum[i]==2,然后枚举边是否在两条边中,并用len[i][j]记录线段i,j重合区域长度
    • 复杂度:O(N^2)(N=5000)

      实现细节

    //只列核心代码
    int n,q;
    int l[maxn],r[maxn],a[maxn];
    int len[maxn];//??????????????
    int dp[maxn][maxn];
    int sum;//
    int main(){
      cin>>n>>q;
      fr1(i,q){cin>>l[i]>>r[i];a[l[i]]++,a[r[i]+1]--;}//打标记
      fr1(i,n){a[i]+=a[i-1];if(a[i]>0)sum++;}//前缀和a[],sum记录q个painter覆盖长度
    
      for(int i=1;i<=q;i++){//记录每个painter的长度
          for(int j=l[i];j<=r[i];j++){
              if(a[j]==1) len[i]++;
          }
      }
      //??????????
      for(int i=1;i<=n;i++){//统计2次覆盖的情况
          if(a[i]==2){
              int p1=0,p2=0;
              for(int j=1;j<=q;j++){
                  if(l[j]<=i&&i<=r[j]){
                      if(p1==0)p1=j;
                      else p2=j;
                  }
              }
              dp[p1][p2]++;//p2>p1
          }
      }
      //?????????
      int ans=0;
      for(int i=1;i<=q;i++){
          for(int j=i+1;j<=q;j++){
              ans=max(ans,sum-len[i]-len[j]-dp[i][j]);
          }
      }
      pr(ans);
      return 0;
    }

    D. Stressful Training

    题意

    n台电脑,初始电量\(a_i\),每分钟小号电量\(b_i\),需要坚持k分钟(通过样例分析似乎是k-1分钟,或者说是每台电脑分钟的开始有电即可),问需要最少买多大功率的充电器(买一台,轮流充)

    思路

  • 我最开始的思路是:怎么求最大功率-->难道是数学方法-->可这么多台怎么求-->难道可以看作一台-->将情况分成1.任何功率都无法满足2.可以满足,然后将所有电脑看成整体-->1.可以通过找某分钟出现两台无法满足的电脑即可判断,2.数学方法
  • 结果WA WA WA(虽然不会做但还是很有想象力的不是吗)

  • 正确的思路:二分答案(无法数学求出答案的问题就二分啊)
  • 二分之后,对答案的check()贪心处理,即每次选择能坚持时间最短的laptop进行充电(贪心有理)
  • 一个优化,可以使用优先队列,当充电后可坚持时间>k,即可不用再入队列

细节实现

const int maxn = 2e5 + 5;
const int maxm = 1e6+5;
ll a[maxn],b[maxn];
int n,k;
struct lap{
    ll a,b,minu;//minu 表示坚持的分钟
    friend bool operator <(lap l1,lap l2){
        return l1.minu>l2.minu;//for min heap
    }
}laps[maxn];
//二分答案,然后求解
bool check(ll p){
    //db(p);
    priority_queue<lap> pq;
    for(int i=1;i<=n;i++){
        laps[i].a=a[i],laps[i].b=b[i],laps[i].minu=a[i]/b[i];//
        pq.push(laps[i]);
    }
    for(int i=1;i<k;i++){//i: minutes
        lap need=pq.top();//the laptop need charge
        pq.pop();
        need.a+=p,need.minu=need.a/need.b;
        if(need.a/need.b<k){//还需充电的入heap
            pq.push(need);
        }
        //check any remain lack of power
        if(pq.empty()){return true;}//优化
        lap need2=pq.top();
        if(need2.minu<i){//db(i),db(need2.minu);
        return false;}//无法坚持到i minutes
    }
    return true;
}
int main(){
    scanf("%d %d",&n,&k);
    for(int i=1;i<=n;i++)scanf("%lld",&a[i]);
    fr1(i,n)scanf("%lld",&b[i]);
    ll lo=0,hi=1e13,mid;
    while(lo<hi){
        mid=(lo+hi)>>1;
        if(!check(mid)){
            lo=mid+1;
        }
        else{
            hi=mid;
        }
    }
    if(lo==1e13)printf("-1\n");
    else printf("%lld\n",lo);
    return 0;
}

E. Knapsack

题意

  • 教育场就很有教育意义,从k重背包又变种出题目
  • 只有1..8种重量物品,给出数量\(cnt_1..cnt_8\),背包容量W,问如何使其尽可能装满
  • 核心:\(W<=1e18,cnt_i<=1e16\)
  • 能够对背包容量起关键作用是小数量的物品,在还未装满的情况下留下一些小数量的物品用于dp即可(大概是这个思路,至于这个小数量是多少呢?有人认为跟lcm(1,2,3...8)有关,只是猜想)
  • 是不是很难理解,但体会到上面的思想就能解出此题

    code

ll a[10];
ll v;
ll dp[maxn];
int gcd(int a,int b){
    return b==0?a:gcd(b,a%b);
}
int getlcm(int a,int b){
    return a*b/gcd(a,b);
}
//lcm(1,2...8)=840
int main(){
    ll sum=0;
    int lcm=1;
    cin>>v;fr1(i,8){cin>>a[i];sum+=a[i]*i;}
    //db(lcm);
    if(sum<=v){cout<<sum<<endl;return 0;}
    ll ans=0;//记录已加和
    ll res=v;//记录还需凑成的容积
    for(int i=1;i<=8;i++){
        ll add=min(a[i],res/i);//记录所需和剩余的较小值
        ll radd=max(add-(840),0);//最多留下840个items
        ans+=i*radd;
        res-=i*radd;
        a[i]=a[i]-radd;
        //db(a[i]);
    }
    //db(ans),db(res);
    //经过处理所有次数不超过100
    //也保证了res不会超过840*(1+2+3+..+8)=2e4
    dp[0]=0;
    ll maxcap=0;
    for(int i=1;i<=8;i++){//complexity:2e4*8*840=1e8
        for(ll j=res;j>=0;j--){
            for(int k=0;k<=a[i];k++){
                if(j>=k*i)dp[j]=max(dp[j],dp[j-k*i]+k*i);//空间j最大能装的容积
                else break;
            }
        }
    }
    
    ans+=dp[res];
    cout<<ans<<endl;
    return 0;
}

F. Clear the String

题意

lrj箱子问题的简化版本,区间dp可做

code


const int N=505;
int dp[N][N];
string s;
int n;
int call(int lft,int rgt){//区间dp
    if(lft>rgt) return 0;
    if(lft==rgt){return 1;}
    if(dp[lft][rgt]!=-1)return dp[lft][rgt];
    int ans=call(lft+1,rgt)+1;
    for(int nxt=lft+1;nxt<=rgt;nxt++){
        if(s[nxt]==s[lft]){
            ans=min(ans,call(lft+1,nxt-1)+call(nxt,rgt));
        }
    }
    return dp[lft][rgt]=ans;
}
int main(){
    CL(dp,-1);cin>>n>>s;
    int ans=call(0,n-1);
    cout<<ans;
}

G. Greedy Subsequences

不会啊WA

rating++,意料之外的事情,大概有太多人在E题被hack

猜你喜欢

转载自www.cnblogs.com/fridayfang/p/10487049.html