[PA2015]-BZOJ4291 to 4296板刷记

说在前面

并没有什么想说的,但是要保持格式=w=

CSDN最近一直换界面,BUG频出不止……
还me代码缩进!!


Kieszonkowe

BZOJ4291传送门

题面

给定 N N 个数,请从中选出若干个数,使得总和为偶数,请最大化这个总和
范围: N106,ai1000 N ≤ 10 6 , a i ≤ 1000

解法

撒币贪心qwq

下面是代码

#include 
#include 
#include 
using namespace std ;

int N , oddcnt , ans , minodd = 20031109 ;

int main(){
    scanf( "%d" , &N ) ;
    for( int i = 1 , t ; i <= N ; i ++ ){
        scanf( "%d" , &t ) , ans += t ;
        if( t&1 ){
            oddcnt ++ ;
            minodd = min( minodd , t ) ;
        }
    } if( N == 1 && oddcnt == 1 ) puts( "NIESTETY" ) ;
    else printf( "%d" , ans - ( oddcnt&1 ) * minodd ) ;
}

Równanie

BZOJ4292传送门

题面

对于一个正整数 n n ,定义 f(n) f ( n ) 为它十进制下每一位数字的平方的和。现在给定三个正整数 k,a,b k , a , b ,请求出满足 anb a ≤ n ≤ b kf(n)=n k ∗ f ( n ) = n n n 的个数
范围: a,b,k1018 a , b , k ≤ 10 18

解法

第一眼以为是数位dp
然而限制这么强,直接都是等号了……
撒币枚举qwq

下面是代码

#include 
#include 
#include 
using namespace std ;

int ans ;
long long K , A , B ;

bool check( long long n , int sums ){
    while( n ){
        int t = n%10 ; n /= 10 ;
        sums -= t * t ;
    } return sums == 0 ;
}

int main(){
    scanf( "%lld%lld%lld" , &K , &A , &B ) ;
    for( int fn = 0 ; fn <= 81 * 18 ; fn ++ ){
        if( K * fn > B ) break ;
        if( K * fn < A ) continue ;
        ans += check( K * fn , fn ) ;
    } printf( "%d" , ans ) ;
}

Siano

BZOJ4293传送门

题面

农夫Byteasar买了一片 N N 亩的土地,他要在这上面种草。
他在每一亩土地上都种植了一种独一无二的草,其中,第 i i 亩土地的草每天会长高 a[i] a [ i ] 厘米。
Byteasar一共会进行 M M 次收割,其中第 i i 次收割在第 d[i] d [ i ] 天,并把所有高度大于等于 b[i] b [ i ] 的部分全部割去。Byteasar想知道,每次收割得到的草的高度总和是多少,你能帮帮他吗?不能

范围: a[i]106 a [ i ] ≤ 10 6 d[i],b[i]1012 d [ i ] , b [ i ] ≤ 10 12 N,M500000 N , M ≤ 500000
约定:保证割草的时间递增,且任意时刻草的高度不超过 1012 10 12

解法

其实大体的思路是不难想的,只是实现的时候,需要选择合适的方法
首先这些草,顺序是无所谓的,为了方便维护,我们显然可以将所有草按照生长速度排序
然后可以发现,一次就会割掉一段后缀,并且把后缀的高度全部改成一个值,并且获得多余部分的收益
不难想到用线段树维护

线段树呢,在修改的时候,顺便就把时间以及高度带入进去,进一个区间,就把那个区间的信息更新到现在时刻。然后这样的话,判断割草和计算贡献都很方便

下面是自带大常数的代码

#include 
#include 
#include 
using namespace std ;

int N , M , a[1000005] ;
struct Node{
    long long sumV , date , sumH ;
    long long minH , maxH , flag , flag_date ;
    int lf , rg ;
    Node *ch[2] ;
    void change_Date( long long now ){
        if( now == date ) return ;
        sumH += ( now - date ) * sumV ;
        minH += ( now - date ) * a[lf] ;
        maxH += ( now - date ) * a[rg] ;
        date = now ;
    }
    void update(){
        sumH = ch[0]->sumH + ch[1]->sumH ;
        minH = ch[0]->minH , maxH = ch[1]->maxH ;
    }
    void pushdown(){
        if( !flag_date ) return ;
        ch[0]->flag_date = ch[0]->date = flag_date ;
        ch[1]->flag_date = ch[1]->date = flag_date ;
        ch[0]->sumH = flag * ( ch[0]->rg - ch[0]->lf + 1 ) ;
        ch[1]->sumH = flag * ( ch[1]->rg - ch[1]->lf + 1 ) ;
        ch[0]->flag = ch[0]->minH = ch[0]->maxH = flag ;
        ch[1]->flag = ch[1]->minH = ch[1]->maxH = flag ;
        flag_date = 0 ;
    }
}*root , w[2000005] , *tw = w ;

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

long long Modify( Node *nd , int lf , int rg , long long date , long long H ){
    nd->change_Date( date ) ;
    if( nd->maxH <= H ) return 0 ;
    if( nd->minH >= H ){
        long long rt = nd->sumH - H * ( rg - lf + 1 ) ;
        nd->sumH = H * ( rg - lf + 1 ) ;
        nd->minH = nd->maxH = nd->flag = H ;
        nd->flag_date = date ;
        return rt ;
    } int mid = ( lf + rg ) >> 1 ;
    nd->pushdown() ;
    long long rt = Modify( nd->ch[0] , lf , mid , date , H ) + 
                   Modify( nd->ch[1] , mid+1,rg , date , H ) ;
    nd->update() ;
    return rt ;
}

void solve(){
    long long b , d ;
    for( int i = 1 ; i <= M ; i ++ ){
        scanf( "%lld%lld" , &d , &b ) ;
        printf( "%lld\n" , Modify( root , 1 , N , d , b ) ) ;
    }
}

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

Fibonacci

BZOJ4294传送门

题面

众所周知,斐波那契数列F满足:
f0=0,f1=1,fm=fm1+fm2(m2) f 0 = 0 , f 1 = 1 , f m = f m − 1 + f m − 2 ( m ≥ 2 )
现在给出一个数字串 S S ,请找到一个 k k 使得 fk f k S S 为结尾
(特别的,如果 k10100 k ≤ 10 100 范围内没有答案,输出NIE)
范围: |S|18 | S | ≤ 18

解法

看到这个题呢,并没有什么很明显的算法偏向
me就在想,如果只看Fibonacci数列的末尾几位数,它这个东西可能具有循环节
于是me打了一个30的表,并没有发现末尾位有循环= =…
然后就去看了题解……

然后是这样的:Fib数列在模 10m 10 m 意义下,具有长度为 610m 6 ∗ 10 m 的循环节(所以题目上那个 10100 10 100 就是吓人用的)
这显然可以通过定义 f[i][j] f [ i ] [ j ] 表示,在模某个数字的意义下,当前为 i i ,上一个为 j j 打表得到(打表打少了hhhhh)

然后就可以搜索,从低位到高位按位确定,并用矩阵快速幂来判断当前的Fib是否符合条件就好了
感性上觉得这个算法还是很快的

下面是代码

假装此处有代码
(me懒,并不想写这个题的代码)


Hazard

me最最最讨厌细节题啦!!!QAQ
BZOJ4295传送门

题面

N N 个人在轮流玩赌博机,一开始编号为 i i 的人有 a[i] a [ i ] 元钱。赌博机可以抽象为一个长度为 m m 的仅包含 1 1 1 − 1 的序列,若抽到 1 1 ,那么你将得到 1 1 块钱;若抽到 1 − 1 ,你将输掉 1 1 块钱。
1 1 局,第 1 1 个人会抽到序列中的第 1 1 项;第 2 2 局,第 2 2 个人会抽到序列中的第 2 2 项;第 3 3 局,第 3 3 个人会抽到序列中的第 3 3 项……即:第 i i 个人抽完后轮到第 i+1 i + 1 个人去抽,特别地,第 n n 个人抽完后轮到第 1 1 个人去抽。序列第 i i 项被抽到之后,下一个被抽到的将会是第 i+1 i + 1 项,特别地,序列第 m m 项被抽到之后,下一个被抽到的将会是第 1 1 项。
如果在某一轮,有个人输光了所有的钱,那么这场赌博游戏就会结束,请求出游戏在哪一轮结束,或者判断这个游戏会永远进行下去
范围: N,M,a[i]1000000 N , M , a [ i ] ≤ 1000000

解法

这题细节多的....
首先很显然的,这个抽奖应该具有循环节
然后还可以发现,如果是 n n 个人抽长度为 m m 的序列,那么每个人能抽到的位置是 m/gcd(n,m) m / gcd ( n , m )
(比如说 6 6 个人抽长为 4 4 的序列,第一个人只能抽到 1 1 号或 3 3 号)

那么我们可以把这一些人都提出来一起处理(因为他们的抽奖序列是相同的,只是开始位置不同)
一个人抽到没有钱,要么是因为:一个循环的总和为负,经过几个循环之后在某一个位置停下来;要么就是在第一个循环就停下来

大概把这个序列画一个图出来,发现我们需要维护「前缀和」以及「从每个位置开始经过一个循环的最低值」(这个可以用单调队列)
另外,为了能够快速的知道「每一个值会在之后的什么位置出现」,需要倒着处理,处理过程中记录每个位置最后的出现位置

大概就是这些吧,反正需要想清楚再写

下面是代码

#include 
#include 
#include 
#include 
#include 
#define las(x) las[x+1000000]
using namespace std ;

const int maxn = 1000005 ;

char ss[maxn] ;
int N , M , a[maxn] , b[maxn] ;

bool vis[maxn] ;
int id[maxn] , que[2*maxn] , fr , ba ;
int nb[maxn] , sum[2*maxn] , mn[maxn] , las[2*maxn] ;

void solve(){
    int len , All ;
    long long ans = 1LL << 56 ;
    for( int i = 0 ; !vis[i] && i < M ; i ++ ){
        //get sequence
        len = 0 ;
        for( int j = i ; !vis[j] ; j = ( j + N )%M ){
            nb[++len] = b[j] , vis[j] = true , id[len] = j ;
        //  printf( "%d " , nb[len] ) ;
        }
    //  puts( "" ) ;

        //get prefix_min_value & prefix_sum
        fr = 1 , ba = 0 ; 
        for( int j = 1 ; j <= len ; j ++ ){
            sum[j] = sum[j-1] + nb[j] ;
            while( ba >= fr && sum[ que[ba] ] >= sum[j] ) ba -- ;
            que[++ba] = j ;
        } for( int j = 1 ; j <= len ; j ++ ){
            while( que[fr] < j - 1 ) fr ++ ;
            mn[j] = min( sum[ que[fr] ] - sum[j-1] , 0 ) ;
        //  printf( "mn[%d] = %d\n" , j , mn[j] ) ;
            sum[j+len] = sum[j+len-1] + nb[j] ;
            while( ba >= fr && sum[ que[ba] ] >= sum[j+len] ) ba -- ;
            que[++ba] = j + len ;
        } All = - sum[len] ;

        //calc_ans
        for( int j = 2 * len ; j > len ; j -- ) las( sum[j] ) = j ;
        for( int j = len ; j ; j -- ){
        //  printf( "j = %d:\n" , j ) ;
            las( sum[j] ) = j ;
            for( int k = id[j] ; k < N ; k += M ){
            //  printf( "k = %d:\n" , k ) ;
                int val = a[k] ;
                if( val + mn[j] > 0 ) { // will not GG at fitst round
                    if( All <= 0 ) continue ;
                    long long rd = ceil( 1.0 * ( val + mn[j] ) / All ) , tmp = rd * len ;
                    tmp += las( sum[j-1] - ( val - rd * All ) ) - j ;
                //  printf( "rd = %lld , tmp = %lld\n" , rd , tmp ) ;
                    ans = min( ans , tmp * N + k + 1 ) ;
                } else {
                    ans = min( ans , 1LL * N * ( las( sum[j-1] - val ) - j ) + k + 1 ) ;
                //  printf( "pos:: %d \n" , las( sum[j-1] - val ) ) ;
                }
            } //puts( "") ;
        }
    } printf( "%lld" , ans == ( 1LL << 56 ) ? -1 : ans ) ;
}


int main(){
    scanf( "%d" , &N ) ;
    for( int i = 0 ; i < N ; i ++ ) scanf( "%d" , &a[i] ) ;
    scanf( "%d" , &M ) ; scanf( "%s" , ss ) ;
    for( int i = 0 ; i < M ; i ++ ) b[i] = ( ss[i] == 'W' ? 1 : -1 ) ;
    solve() ;
}

Mistrzostwa

BZOJ4296传送门

题面

给定一张 n n 个点 m m 条边的无向图,请找到一个点数最多的点集 S S ,满足:

  1. 对于点集中任何一个点,它至少与 d d 个点集中的点相邻
  2. 该点集连通

输出字典序最小的解(当me写到这里时,还没有SPJ,因此只有这样才能过)

范围: n,m200000,d<n n , m ≤ 200000 , d < n
约定:无自环

解法

直接用个队列,把点度小于 d d 的点全删了
然后dfs找合法连通块就ok

下面是代码

#include 
#include 
#include 
#define GG return (void)puts( "NIE" ) ;
using namespace std ;

int N , M , D , deg[200005] , tp , head[200005] ;
struct Path{
    int pre , to ;
}p[400005] ;

void In( int t1 , int t2 ){
    deg[t1] ++ , deg[t2] ++ ;
    p[++tp] = ( Path ){ head[t1] , t2 } ; head[t1] = tp ;
    p[++tp] = ( Path ){ head[t2] , t1 } ; head[t2] = tp ;
}

bool vis[200005] ;
int que[200005] , ans[200005] , ans_cnt ;
int sta[200005] , topp ;

void dfs( int u ){
    vis[u] = true , sta[++topp] = u ;
    for( int i = head[u] ; i ; i = p[i].pre ){
        int v = p[i].to ;
        if( !vis[v] ) dfs( v ) ;
    }
}

void solve(){
    int fr = 1 , ba = 0 ;
    for( int i = 1 ; i <= N ; i ++ )
        if( deg[i] < D ) que[++ba] = i , vis[i] = true ;
    while( ba >= fr ){
        int u = que[fr++] ;
        for( int i = head[u] ; i ; i = p[i].pre ){
            int v = p[i].to ;
            deg[v] -- ;
            if( !vis[v] && deg[v] < D )
                que[++ba] = v , vis[v] = true ;
        }
    } for( int i = 1 ; i <= N ; i ++ ) if( !vis[i] ){
        topp = 0 , dfs( i ) ;
        if( topp > ans_cnt ){
            ans_cnt = topp ;
            memcpy( ans , sta , ( topp + 2 ) * sizeof( int ) ) ;
        }
    } if( !ans_cnt ) GG ;
    printf( "%d\n" , ans_cnt ) ;
    sort( ans + 1 , ans + ans_cnt + 1 ) ;
    for( int i = 1 ; i <= ans_cnt ; i ++ )
        printf( "%d " , ans[i] ) ;
}

int main(){
    scanf( "%d%d%d" , &N , &M , &D ) ;
    for( int i = 1 , u , v ; i <= M ; i ++ ){
        scanf( "%d%d" , &u , &v ) ;
        In( u , v ) ;
    } solve() ;
}

你可能感兴趣的:(水题,贪心,线段树,------数论------)