说在前面
周六晚上打比赛
然后手速(其实是脑速)贼慢的切了C/D
然后E写了一个
n2log
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
k
个)
当然也可以直接把所有区间取出来,然后把可以删掉的数字拿出来排个序得到答案。
出题人说这题数据范围可以出到 ,并且说: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
i
只怪 先消耗 点生命值,然后再补回
bi
b
i
点生命值,问最少需要多少血
上面那道题,显然是先打回血的怪,再打掉消耗大的怪
先打一次性消耗大的怪(不计回血)的原因是,如果先打那种回血较多的怪,血量就会被慢慢消耗以至于无法打死大怪
然后看这道题,是类似的。显然小于B的A是无用的,所以先把所有小于B的A全部换成B
然后,答案至少得是
∑ibi
∑
i
b
i
,要在这个基础上增加来满足
A
A
的限制
然后根据上一题得到的经验,我们想优先选择 大的,因为如果选了太多
A−B
A
−
B
小的点之后,可能就无法满足某些点的
A
A
的限制了(这种情况会发生在那些 很大,而且
A−B
A
−
B
也很大的点上)
但是这种策略还存在一个问题,就是万一我选了这个点,然后把图分成了几块,这样就导致如果我先选这个点,以后还得经过这个点且需要满足
A
A
的限制,这显然是不优秀的。那么对于这种点,我们肯定是先把所有其它连通块填完了,只剩下最后某一个连通块,把这个割点搞了之后,就进入最后的那个连通块,把它填了,显然这样是不劣的
所以我们现在有了一个策略:对于不是割点的点,按照 从小到大挨个挨个填,对于割点特殊考虑(枚举填到最后剩下的一个,选最优策略)
然后这个步骤,实际上就是按照 A−B A − B 从小到大排序建树的过程,叶子结点的值最小,根权值最大,然后直接在树上dp就可以了
代码me还没有写,不过可以参考这个:传送门,感觉逻辑比较清晰