Codeforces *2600 做题记录

CF3D

题意

一个括号序列,其中有几位为 ?,将第 \(i\)? 修改为 ( 的代价是 \(a_i\),修改为 ) 的代价是 \(b_i\),问将所有 ? 修改后使得序列匹配的最小代价。

分析

贪心。发现一个匹配的括号序列的每一个前缀的左括号数一定不小于右括号数,因此先把问号都替换成右括号,对于每一位判断一下,如果到这一位时右括号数大于左括号数,就把前面右换左代价最小的一位替换成左。这样一定是正确的,因为前面如果左括号多了后面还能补救,但是右括号多了前面就不能完全匹配了。

于是可以从前往后扫,遇到一个问号就换成右括号,并把这个位置的下标和右换左的代价一起扔到一个小根堆里,如果右括号多了就取出堆顶换成左括号。

核心代码

priority_queue<Pair,vector<Pair>,greater<Pair> >q;
signed main(){
    scanf("%s",s+1);n=strlen(s+1);int i,j,cnt=0;
    for(i=1;i<=n;i++) if(s[i]=='?') m++,mp[i]=++mp[0];for(i=1;i<=m;i++) qread(a[i],b[i]);
    for(i=1;i<=n;i++){
        if(s[i]=='(') cnt++,t[i]=s[i];if(s[i]==')') cnt--,t[i]=s[i];
        if(s[i]=='?'){
            t[i]=')';q.push(Pair(a[mp[i]]-b[mp[i]],i));cnt--;ans+=b[mp[i]];
        }if(cnt<0){
            if(q.empty()) return printf("-1\n"),0;
            auto u=q.top();q.pop();t[u.second]='(';cnt+=2;
            // cout<<u.first<<" "<<u.second<<endl;
            ans+=u.first;
        }
    }if(cnt) return printf("-1\n"),0;
    printf("%lld\n%s\n",ans,t+1);
    return 0;
}

record

CF240F

题意

一个长度为 \(n\) 的小写字母串,\(m\) 次操作,每次将子串 \([l,r]\) 重排使其成为字典序最小的回文串(若无法重排成回文串则跳过)。求最后的字符串。

分析

\(26\) 棵线段树维护每种字母在区间中出现次数。首先可以发现一次操作可以完成当且仅当该区间内每种字母出现次数最多有一个是奇数。重排成字典序最小的回文串就可以从小到大放每种字母,前面接着放一半,后面接着放一半,如果有的字母有奇数个就把一个放中间剩下的和偶数个的字母一起放。

发现修改其实就是将一段连续的位置放上某同种字母,查询也只需查询连续区间的字母数,因此可以用线段树求区间和,并支持区间赋值,然后按上面的策略模拟即可。

核心代码

scanf("%d%d%s",&n,&m,s+1);int i,j;for(i=1;i<=26;i++) st[i].build(1,1,n);
for(i=1;i<=n;i++) st[s[i]-'a'+1].upd(1,1,n,i,i,1);
while(m--){
    int l,r;scanf("%d%d",&l,&r);int c=0,cnt=0,p;
    for(i=1;i<=26;i++){
        int tmp=st[i].que(1,1,n,l,r);
        c+=(tmp&1);cnt+=tmp;if(tmp&1) p=i;
    }if(c>1) continue;int nl=l,nr=r;
    for(i=1;i<=26;i++){
        int tmp=st[i].que(1,1,n,l,r);if(!tmp) continue;
        st[i].upd(1,1,n,l,r,0);
        if(tmp&1) st[i].upd(1,1,n,(l+r)>>1,(l+r)>>1,1),tmp--;
        st[i].upd(1,1,n,nl,nl+(tmp>>1)-1,1);nl+=(tmp>>1);
        st[i].upd(1,1,n,nr-(tmp>>1)+1,nr,1);nr-=(tmp>>1);
    }
}for(i=1;i<=n;i++){
    for(j=1;j<=26;j++){
        if(st[j].que(1,1,n,i,i)>0){
            ans[i]=j+'a'-1;break;
        }
    }
}printf("%s\n",ans+1);

record

CF6D

题意

一排人,血量分别为 \(h_i\) ,攻击某个人,会对他造成 \(a\) 点伤害,对旁边的人造成b点伤害。不能打 \(1\) 号和 \(n\) 号,求最少多少次使所有人血量小于 \(0\)

分析

数据范围很小,爆搜即可。发现对于一个人的攻击不会对与其距离超过 \(1\) 的人有影响,因此从左向右枚举每一位攻击的次数,时间复杂度大致为 \(\mathcal{O}((n-2)^{\frac{h_i}{a}})\),最差情况是 \((10-2)^{15}=8^{15} \approx 3.5\times 10^{13}\),会 TLE,考虑剪枝。如果枚举到一半发现此时用的次数已经大于当前的最优次数,说明一定不可能更优,就直接 return,这样复杂度大大优化。

核心代码

int n,a,b,h[17],sum,ans=1e9,path[167],ansp[167],tmp;
void dfs(int x,int step){
    if(step>=ans) return;
    if(x==n){
        if(h[n]<0){
            ans=step;
            for(int i=0;i<=path[0];i++) ansp[i]=path[i];
        }return;
    }
    for(int i=0;i<=qmax((h[x-1]/b),qmax((h[x]/a),(h[x+1]/b)))+1;i++){
        if (h[x-1]<b*i){
            h[x-1]-=b*i;h[x]-=a*i;h[x+1]-=b*i;
            for(int j=1;j<=i;j++) path[++path[0]]=x;
            dfs(x+1,step+i);
            h[x-1]+=b*i;h[x]+=a*i;h[x+1]+=b*i;
            for(int j=1;j<=i;j++) path[0]--;    
        }
    }
}
int main(){
    qread(n,a,b);int i,j;for(i=1;i<=n;i++) qread(h[i]);dfs(2,0);
    printf("%d\n",ans);for(i=1;i<=ansp[0];i++) printf("%d ",ansp[i]);
    return 0;
}

record

posted @ 2022-09-11 14:38  l_x_y  阅读(45)  评论(0编辑  收藏  举报