[ARC098]-补一发题解

版权声明:转载嘛....也不是不可以(故作沉思),记得带上me的ID啊qwq https://blog.csdn.net/Izumi_Hanako/article/details/80489185

说在前面

周六晚上打比赛
然后手速(其实是脑速)贼慢的切了C/D
然后E写了一个 n 2 log ,线段树老是WA也是够了
然后F就GG了,不过这题确实很巧me估计me想出来考试早结束了


C

传送门
一个zz题
枚举那个位置作为中间点,左边朝左的需要转向,右边朝右的需要转向
搞个前缀和 / 后缀和,然后扫一遍

#include <cstdio>
#include <cstring>
#include <algorithm>
using namespace std ;

int pre[300005] , suf[300005] , len ;
char a[300005] ;

template< typename T >
void smin( T &x , T y ){ if( x > y ) x = y ; }

int main(){
    scanf( "%d" , &len ) , scanf( "%s" , a + 1 ) ;
    for( int i = 1 ; i <= len ; i ++ )
        pre[i] = pre[i-1] + ( a[i] == 'W' ) ;
    for( int i = len ; i ; i -- )
        suf[i] = suf[i+1] + ( a[i] == 'E' ) ;
    int ans = 1 << 20 ;
    for( int i = 1 ; i <= len ; i ++ )
        smin( ans , pre[i-1] + suf[i+1] ) ;
    printf( "%d" , ans ) ;
}

D

传送门
对于每个数位,都只有至多一个数字拥有,才能统计入答案
显然是有单调性的一个玩意,所以two pointer扫一扫就好了

#include <cstdio>
#include <cstring>
#include <algorithm>
using namespace std ;

int N , a[200005] ;

void solve(){
    long long ans = 0 ;
    int Lpt = 1 , Rpt = 1 , sum = 0 , xsum = 0 ;
    while( Rpt <= N ){
        sum += a[Rpt] , xsum ^= a[Rpt] ;
        while( sum != xsum ) sum -= a[Lpt] , xsum ^= a[Lpt++] ;
        ans += Rpt - Lpt + 1 , Rpt ++ ;
    } printf( "%lld" , ans ) ;
}

int main(){
    scanf( "%d" , &N ) ;
    for( int i = 1 ; i <= N ; i ++ )
        scanf( "%d" , &a[i] ) ;
    solve() ;
}

E

传送门
这个数据范围很小,然而并没有什么好的dp做法,于是考虑从答案入手
当我们固定了小端的数字时,显然希望最大的被删掉的数字尽量小
因为这个东西不容易二分,所以直接枚举删除最小的是哪一个,然后check
假设最小能被删掉的是 u ,那么把小于 u 的数字都看成断点,然后从小到大枚举数字看可不可以删除,只要存在一个区间包含当前数字且不经过断点就好了。然后me就直接上了线段树…(记录一下每个断点,查询当前数字所在区间数字总个数是否够 k 个)

当然也可以直接把所有区间取出来,然后把可以删掉的数字拿出来排个序得到答案。

出题人说这题数据范围可以出到 10 5 ,并且说:This part is left as an exercise for readers
然而me还没有去想

#include <cstdio>
#include <cstring>
#include <algorithm>
using namespace std ;

bool ban[2005] ;
int N , K , Q , a[2005] , rk[2005] , sa[2005] ;
int pre[2005] , nxt[2005] ;
struct Node{
    short sum ;
    Node *ch[2] ;
} *root , w[4005] , *tw = w ;

template< typename T > void smin( T &x , T y ){ if( x > y ) x = y ; }
bool cmp( const int &A , const int &B ){ return a[A] < a[B] ; }

Node *build( short lf , short rg ){
    Node *nd = ++tw ;
    if( lf != rg ){
        short mid = ( lf + rg ) >> 1 ;
        nd->ch[0] = build( lf , mid ) ;
        nd->ch[1] = build( mid+1,rg ) ;
    } return nd ;
}

void reset( Node *nd , short lf , short rg ){
    if( lf != rg ){
        short mid = ( lf + rg ) >> 1 ;
        reset( nd->ch[0] , lf , mid ) ;
        reset( nd->ch[1] , mid+1,rg ) ;
        nd->sum = nd->ch[0]->sum + nd->ch[1]->sum ;
    } else nd->sum = !ban[lf] ;
}

short Query( Node *nd , short lf , short rg , short L , short R ){
    if( L <= lf && rg <= R ) return nd->sum ;
    short mid = ( lf + rg ) >> 1 , rt = 0 ;
    if( L <= mid ) rt += Query( nd->ch[0] , lf , mid , L , R ) ;
    if( R >  mid ) rt += Query( nd->ch[1] , mid+1,rg , L , R ) ;
    return rt ;
}

void set_zero( Node *nd , short lf , short rg , short pos ){
    if( !nd ) return ; nd->sum -- ;
    short mid = ( lf + rg ) >> 1 ;
    if( pos <= mid ) set_zero( nd->ch[0] , lf , mid , pos ) ;
    else set_zero( nd->ch[1] , mid+1,rg , pos ) ;
}

void preWork(){
    sort( sa + 1 , sa + N + 1 , cmp ) ;
    for( int i = 1 ; i <= N ; i ++ ) rk[ sa[i] ] = i ;
    root = build( 1 , N ) ;
}

void solve(){
    int ans = 0x3b9aca00 ; nxt[N+1] = N + 1 ;
    for( int i = 0 , cnt , st , ed ; ; i ++ ){

        for( int j = 1 ; j <= i ; j ++ ) ban[ sa[j] ] = true ;
        for( int j = 1 ; j <= N ; j ++ ) pre[j] = ban[j] ? j : pre[j-1] ;
        for( int j = N ; j >= 1 ; j -- ) nxt[j] = ban[j] ? j : nxt[j+1] ;

        cnt = st = 0 , reset( root , 1 , N ) ;

        for( int j = i + 1 ; j <= N && cnt < Q ; j ++ ){
            if( nxt[ sa[j] ] - pre[ sa[j] ] - 1 < K ) continue ;
            if( Query( root , 1 , N , pre[ sa[j] ] + 1 , nxt[ sa[j] ] - 1 ) >= K ){
                if( !st ) st = j ;
                if( ++cnt == Q ) ed = j ;
                else set_zero( root , 1 , N , sa[j] ) ;
            }
        } if( cnt != Q ) break ;

        smin( ans , a[ sa[ed] ] - a[ sa[st] ] ) ;
        for( int j = 1 ; j <= i ; j ++ ) ban[ sa[j] ] = false ;

    } printf( "%d" , ans ) ;
}

int main(){
    scanf( "%d%d%d" , &N , &K , &Q ) ;
    for( int i = 1 ; i <= N ; i ++ )
        scanf( "%d" , &a[i] ) , sa[i] = i ;
    preWork() ; solve() ;
}

F

传送门
这个题,有一种很熟悉的感觉
就是类似这种题:去打怪,打死第 i 只怪 先消耗 a i 点生命值,然后再补回 b i 点生命值,问最少需要多少血
上面那道题,显然是先打回血的怪,再打掉消耗大的怪
先打一次性消耗大的怪(不计回血)的原因是,如果先打那种回血较多的怪,血量就会被慢慢消耗以至于无法打死大怪

然后看这道题,是类似的。显然小于B的A是无用的,所以先把所有小于B的A全部换成B
然后,答案至少得是 i b i ,要在这个基础上增加来满足 A 的限制
然后根据上一题得到的经验,我们想优先选择 A B 大的,因为如果选了太多 A B 小的点之后,可能就无法满足某些点的 A 的限制了(这种情况会发生在那些 A 很大,而且 A B 也很大的点上)
但是这种策略还存在一个问题,就是万一我选了这个点,然后把图分成了几块,这样就导致如果我先选这个点,以后还得经过这个点且需要满足 A 的限制,这显然是不优秀的。那么对于这种点,我们肯定是先把所有其它连通块填完了,只剩下最后某一个连通块,把这个割点搞了之后,就进入最后的那个连通块,把它填了,显然这样是不劣的

所以我们现在有了一个策略:对于不是割点的点,按照 A B 从小到大挨个挨个填,对于割点特殊考虑(枚举填到最后剩下的一个,选最优策略)

然后这个步骤,实际上就是按照 A B 从小到大排序建树的过程,叶子结点的值最小,根权值最大,然后直接在树上dp就可以了

代码me还没有写,不过可以参考这个:传送门,感觉逻辑比较清晰

猜你喜欢

转载自blog.csdn.net/Izumi_Hanako/article/details/80489185