NOIP模拟 2017.10.4 总结

说在前面

Emmmmmm,每次好不容易想出来解法,实现上却总是各种疏忽,该拿的分拿不满,很气。


题目&&题解

T1

这里写图片描述
这个题好像是没有什么针对的数据结构的,但是其实可以用一个线段树就把这个题水了,因为字母一共只有26种,我们可以直接查询每种数字的个数,然后一段一段的区间修改。如果是升序,从A到Z依次赋值,降序就从Z到A,常数比较大但是可以加一些比如读优和register一样的玄学优化

可以卡过就对了嘛=w=

#include <cstdio>
#include <cstring>
#include <algorithm>
using namespace std ;
int N , M ;
char ss[100005] ;

struct Node{
    int cnt[26] , flag ;
    Node *ls , *rs ;
    Node(){
        memset( cnt , 0 , sizeof( cnt ) ) ;
    }
    void pushdown( int lf , int rg ){
        if( flag != -1 ){
            int mid = ( lf + rg ) >> 1 ;
            for( register int i = 0 ; i < 26 ; i ++ )
                ls->cnt[i] = rs->cnt[i] = 0 ;
            ls->cnt[ flag ] = ( mid - lf + 1 ) ;
            rs->cnt[ flag ] = ( rg - mid ) ;
            ls->flag = rs->flag = flag ;
            flag = -1 ;
        }
    }
    void updata(){
        for( register int i = 0 ; i < 26 ; i ++ )
            cnt[i] = ls->cnt[i] + rs->cnt[i] ;
    }
}w[200005] , *root , *tw = w ;


inline int read_(){
    int rt = 0 ;
    char ch = getchar() ;
    while( ch < '0' || ch > '9' ) ch = getchar() ;
    while( ch >='0' && ch <='9' ) rt = (rt<<1) + (rt<<3) + ch - '0' , ch = getchar() ;
    return rt ;
}

Node *build( int lf , int rg ){
    Node *nd = ++tw ;
    if( lf == rg ) nd->cnt[ ss[lf]-'a' ] = 1 ;
    else{
        int mid = ( lf + rg ) >> 1 ;
        nd->ls = build( lf , mid ) ;
        nd->rs = build( mid+1 , rg ) ;
        nd->updata() ; nd->flag = -1 ;
    }
    return nd ;
}

void Modify( Node *nd , int lf , int rg , int L , int R , int delta ){
    if( L <= lf && rg <= R ){
        nd->flag = delta ;
        for( int i = 0 ; i < 26 ; i ++ )
            nd->cnt[i] = 0 ;
        nd->cnt[delta] = ( rg - lf + 1 ) ;
        return ; 
    }
    int mid = ( lf + rg ) >> 1 ;
    nd->pushdown( lf , rg ) ;
    if( L <= mid ) Modify( nd->ls , lf  , mid , L , R , delta ) ;
    if( R >  mid ) Modify( nd->rs , mid+1, rg , L , R , delta ) ;
    nd->updata() ;
}

Node Query( Node *nd , int lf , int rg , int L , int R ){
    if( L <= lf && rg <= R ) return *nd ;
    else{
        Node rt = Node() , tmp ;
        int mid = ( lf + rg ) >> 1 ;
        nd->pushdown( lf , rg ) ;
        if( L <= mid ){
            tmp = Query( nd->ls , lf , mid , L , R ) ;
            for( register int i = 0 ; i < 26 ; i ++ )
                rt.cnt[i] += tmp.cnt[i] ;
        }
        if( R >  mid ){
            tmp = Query( nd->rs , mid+1 , rg , L , R ) ;
            for( register int i = 0 ; i < 26 ; i ++ )
                rt.cnt[i] += tmp.cnt[i] ;
        }
        return rt ;
    }
}

void print( Node *nd , int lf , int rg ){
    if( lf == rg ){
        for( int i = 0 ; i < 26 ; i ++ )
            if( nd->cnt[i] ){
                printf("%c" , i + 'a' ) ;
                return ;
            }
    }
    int mid = ( lf + rg ) >> 1 ;
    nd->pushdown( lf , rg ) ;
    print( nd->ls , lf , mid ) ;
    print( nd->rs , mid+1 , rg ) ;
}

void solve(){
    for( register int i = 1 , x , L , R ; i <= M ; i ++ ){
        L = read_() ; R = read_() ; x = read_() ;
        int st = L ;
        Node tmp = Query( root , 1 , N , L , R ) ;
        switch( x ){
            case 1:{
                for( register int j = 0 ; j < 26 ; j ++ )
                    if( tmp.cnt[j] ){
                        Modify( root , 1 , N , st , st + tmp.cnt[j] - 1 , j ) ;
                        st += tmp.cnt[j] ;
                    }
                break;
            }
            default:{
                for( register int j = 25 ; j >= 0 ; j -- )
                    if( tmp.cnt[j] ){
                        Modify( root , 1 , N , st , st + tmp.cnt[j] - 1 , j ) ;
                        st += tmp.cnt[j] ;
                    }
            }
        }
    }
    print( root , 1 , N ) ;
}

int main(){
    freopen( "string.in" , "r" , stdin ) ;
    freopen( "string.out", "w" , stdout) ;
    N = read_() ; M = read_() ;
    scanf( "%s" , ss + 1 ) ;
    root = build( 1 , N ) ;
    solve() ;
}

T2

这里写图片描述
这题的模型还是第一次见到,这题很明显是个DP。
关键在于这个DP的状态和转移,定义DP[i][j]为当前已经枚举到第i列,有j个右线段的左端点在i的左边,并且还未处理。注意,凡是我们i所到之处,右端点在i左边的左线段都必须被处理完。每次由i-1转移到i时,将右端点在该列的左线段全部处理了(相当于我们是需要把这些线段全部填充一个1,那么我们可以选择在之前的”空列&&当前列”选择一些列来使用,并且由于它们所在的行不一样,因此是排列而不是组合),并且判断左端点在i位置的右线段是否填充(如果我们把当前列用于右线段填充,那么对于左线段来说,他的可选空列就不包含当前列,但是同时由于左端点在当前位置的右线段可能有很多个,但是一列只能填充一个线段,因此还需要在排列数基础上*左端点在i位置的右线段数量),然后使用排列数和乘法原理转移。

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

const int mmod = 998244353 ;
int N , M , cL[3005] , cR[3005] , sL[3005] , sR[3005] , s[3005] ;
int C[3001][3001] , fac[3001] = {1} , dp[3001][3001] ;

int main(){
//  freopen( "matrix.in" , "r" , stdin ) ;
//  freopen( "matrix.out", "w" , stdout) ;
    scanf( "%d%d" , &N , &M ) ;
    for( int i = 0 ; i <= 3000 ; i ++ ){
        C[i][0] = 1 ;
        for( int j = 1 ; j <= i ; j ++ )
            C[i][j] = ( C[i-1][j] + C[i-1][j-1] ) %mmod ;
    }
    for( int i = 1 ; i <= 3000 ; i ++ )
        fac[i] = 1LL * fac[i-1] * i %mmod ;
    for( int i = 1 , Li , Ri ; i <= N ; i ++ ){
        scanf( "%d%d" , &Li , &Ri ) ;
        ++ cL[Li] ; ++ cR[Ri] ;
    }
    for( int i = 1 ; i <= M ; i ++ ){
        sL[i] = sL[i-1] + cL[i] ;
        sR[i] = sR[i-1] + cR[i] ;
        s[i] = sL[i] + sR[i] ;
    }

    dp[0][0] = 1 ;
    for( int i = 1 ; i <= M ; i ++ ){
        for( int j = 0 ; j <= sR[i-1] ; j ++ ){
            if( !dp[i-1][j] ) continue ;
            int k = i - 1 - s[i-1] + j ;
            if( k < 0 ) continue ;
            dp[i][ j+cR[i] ] = ( dp[i][ j+cR[i] ] + 1LL * dp[i-1][j] * C[k+1][ cL[i] ] %mmod * fac[ cL[i] ] %mmod ) %mmod ;
            if( j + cR[i] )
                dp[i][ j+cR[i]-1 ] = ( dp[i][ j+cR[i]-1 ] + 1LL * dp[i-1][j] * C[k][ cL[i] ] %mmod * fac[ cL[i] ] %mmod * ( cR[i] + j ) ) %mmod ;
        ``

    }
    printf( "%d" , dp[M][0] ) ;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值