NOIP模拟 2017.10.4 总结

来源:互联网 发布:房贷利息抵扣个税算法 编辑:程序博客网 时间:2024/05/17 23:21

说在前面

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] ) ;}
原创粉丝点击