2017.11.6机房小测-前缀和/栈思想/题目性质+trie辅助

致出题人:此篇blog中所提到的题目如果侵犯了您的版权,请与me联系,me将及时删除。



说在前面

很气啊,连续两次考试连100分都没有上。明明这两次考试都有签到题,然而就算是水题都写错了。
有道题,明明想到了正解,只有一个特殊情况需要用for循环判断而已。然而我连那个for循环都没有想出来,明明只是一个”if( x == now ) cnt++”的for循环啊!

考之后有反思过,一开始以为是自己太固执不想写暴力,但是发现即使自己心甘情愿写暴力也没有达到想要的效果。甚至在怀疑是不是自己太过自信了,以至于自信的自大了。
每次考试都在自己慢慢体会,考完之后回过头想一想考试中的状态,才发现自己在考试的时候没有办法像以前一样的沉浸到题目里面,不敢去深入分析了,因为总是会怀疑自己想法的正确性,慢慢的也就不太会去深入分析了。取而代之的是听别人多久开始敲代码来确定是否存在签到题。前一段时间写了很多黑算,很少去深入挖掘正解的思路,导致现在me想题方向也比以前更奇怪了。

11号就NOIP了,已经没有多少时间可以用于调整了…
还剩下最后四次模拟考试,要好好的珍惜了,希望可以通过最后的几次考试重新找到以前的那种感觉。


T1

这里写图片描述

解法

在解法之前的几句话

在写这道题之前,看到过另外一道题,那道题的预处理步骤就和这个十分相似。
最后就深陷DP的泥潭无法自拔,最后只拿到了60分。

进入正题

me现在需要求一些数,使得它们的和是N的倍数,也就是在取模N的意义下和为0。

DP做法(60分):定义vis[i]表示最早能够凑到i(取模之后)的方案中的最后一个数是多少。设当前的数字为x。对于每个数字,for一遍0到N-1,如果vis[i]被访问过而vis[i+x]还没有被访问过,就更新vis[i+x]的数字为x。在每个数字枚举完之后,判断vis[0]是否被访问,如果被访问,递归输出来源路径即可。

正解(100分):可以发现,其中只要有一些数字的和在模N意义下为0,那么它们就是答案了。运用鸽巢(抽屉)原理,我们发现前N个数,就有N个不同的前缀和。这N个前缀和中如果存在sum[i]为0,那么答案就是1到i。如果不存在,那么一定存在一对不同的i,j使得sum[i]==sum[j],那么答案就是j+1到i。

下面是自带大常数的代码

注释掉的部分是60分的写法…

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

int N , a[1000005] , fr[1000005]/* , sta[1000005] , topp */ , sum[1000005] ;
int vis[1000005] ;

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

/*
void print(){
    int now = 0 , cnt = 0 ;
    do{
        now -= a[ fr[now] ] ;
        cnt ++ ;
        if( now < 0 ) now += N ;
    }while( now != 0 ) ;
    printf( "%d\n" , cnt ) ;
    do{
        printf( "%d " , fr[now] ) ;
        now -= a[ fr[now] ] ;
        if( now < 0 ) now += N ;
    }while( now != 0 ) ;
}

void solve(){
    register int i , j ;
    for( i = 1 ; i <= N ; i ++ ){
        int tmp = topp ;
        if( !vis[ a[i] ] ){
            vis[ a[i] ] = true ; fr[ a[i] ] = i ;
            sta[++topp] = a[i] ;
        }
        for( j = 1 ; j <= tmp ; j ++ ){
            int num = sta[j] + a[i] ;
            if( num >= N ) num -= N ;
            if( !vis[num] ){
                vis[num] = true ;
                sta[++topp] = num ;
                fr[num] = i ;
            }
        }
        if( vis[0] ){
            print() ;
            return ;
        }
    }
    printf( "-1" ) ;
}*/

void solve(){
    for( int i = 1 ; i <= N ; i ++ ){
        sum[i] = ( sum[i-1] + a[i] ) %N ;
        if( !vis[ sum[i] ] && sum[i] != 0 )
            vis[ sum[i] ] = i ;
        else{
            printf( "%d\n" , i - vis[ sum[i] ] ) ;
            for( int j = vis[ sum[i] ] + 1 ; j <= i ; j ++ )
                printf( "%d " , j ) ;
            exit(0) ;
        }
    }
}

int main(){
    N = read_() ;
    for( int i = 1 ; i <= N ; i ++ )
        a[i] = read_() %N ;
    solve() ;
}


T2

这里写图片描述这里写图片描述

解法

正解:和[BZOJ2456]几乎是差不多的。不难想到,只有一本书的种类出现超过了N/2次,才会出现选择一些书不看的情况。【正确性显然,不然可以像1,2,1,2,1,2这样错开看书】
然后,先for一遍按照BZOJ2456那样处理。再for一遍去check最后剩下的那个数字是否出现了超过N/2次(这道题和BZOJ2456不一样,不保证某一个数字会出现超过半数,因此需要这一步)。如果超过了N/2次就算出答案然后输出,不然的话就输出0。

PS

就是这里的check,me没有想到。然后改写了哈希,但是没有像下面这样处理,而是直接哈希,最后输出还输出错了,subtask一分没得=A=#

另解:hash。我们其实只需要判断是否有一本书出现了N/2次。如果有一种类型的书出现超过了N/2次,那么在某一组(假设为第i组,count[i],x[i],y[i],z[i])里,该类型也应该出现超过count[i]/2次。那么在该组的前三项中,如果第一项和第三项相同,那么这个数就会在该组里出现超过count[i]/2次(因为出现了循环节,并且长度必须不大于2,这样才符合上述),那么这个数也就有潜力成为那本出现次数最多的那本书。对这些”有潜力的书”分配哈希值之后,再for一遍统计这些潜力数的出现次数即可。

下面是自带大常数的代码

注释掉的部分是正解
留下的部分是另一个机房大佬(lemonoil)强行用hash卡了20多次之后过的…

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

int N , M , K , mmod ,/* now , cnt */cnt[971][2777] ;
long long c[1005] , x[1005] , y[1005] , z[1005] ;

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

/*void Modify( int x ){
    if( now == x ) cnt ++ ;
    else{
        if( !cnt ){
            now = x ;
            cnt = 1 ;
        } else cnt -- ;
    }
}*/

int main(){
    scanf( "%d%d" , &M , &K ) ;
    mmod = ( 1 << K ) - 1 ;
    register int i , j , k ;
    for( i = 1 ; i <= M ; i ++ ) c[i] = read_() ;
    for( i = 1 ; i <= M ; i ++ ) x[i] = read_() ;
    for( i = 1 ; i <= M ; i ++ ) y[i] = read_() ;
    for( i = 1 ; i <= M ; i ++ ) z[i] = read_() ;


    for( i = 1 ; i <= M ; i ++ ){
        int las = x[i] ;
        cnt[las%971][las%2777] ++ ;
        N += c[i] ;
        for( j = 1 ; j < c[i] ; j ++ ){
            las = ( 1LL * las * y[i] + z[i] ) & mmod ;
            cnt[las%971][las%2777] ++ ;
        }
    } 
    for( i = 0 ; i < 971 ; i ++ )
        for( j = 0 ; j < 2777 ; j ++ )
                if( cnt[i][j] > N / 2 ){
                    printf( "%d" , 2 * cnt[i][j] - N - 1 ) ;
                    return 0 ;
                }
    printf( "%d" , 0 ) ;
    return 0 ;
    /*
    for( int i = 1 ; i <= M ; i ++ ){
        long long las = x[i] ;
        Modify( las ) ;
        N += c[i] ;
        for( j = 1 ; j < c[i] ; j ++ ){
            las = ( las * y[i] + z[i] ) & mmod ;
            Modify( las ) ;
        }
    }
    cnt = 0 ;
    for( int i = 1 ; i <= M ; i ++ ){
        long long las = x[i] ;
        if( las == now ) cnt ++ ;
        for( j = 1 ; j < c[i] ; j ++ ){
            las = ( las * y[i] + z[i] ) & mmod ;
            if( las == now ) cnt ++ ;
        }
    }
    printf( "%d" , cnt > N / 2 ? cnt - ( N - cnt + 1 ) : 0 ) ;
    */
}


T3

这里写图片描述

解法

直接模拟有10分。

正解:我们需要知道如何去理解”x²”。(我也不知道为什么可以想到这样理解,可以参见[BZOJ1566]管道取珠)对于某一个确定的排名,假设为 i , j , k , x,其中x为自己。这个排名的贡献为9,这个贡献可以假想成:
这里写图片描述
其中排在x前面的每一对点都对答案贡献了1。可以发现在这道题中,这种答案分解的方式对所有贡献适用。
之后就引用一下题解吧:

考虑怎么求出x的答案. 平方相当于是枚举两个人(可以相同), 把这两个人同时排在x前面的天数
计入答案. 那么对于x, 如果我们求出f[i]也就是能力值的二进制中第i+1到M-1位都和他相等且第i位不
同的人有多少个, 那么这些人是否排在他前面只由第i位确定, 一共有2^(M-1)天, 而且不需要从这些人中
枚举两个人了, 因为直接平方即可. 我们只要枚举i和j, f[i]这些人和f[j]这些人同时排在他前面的天数为
2^(M-2), 所以把2 * f[i]* f[j] * 2 ^ (M-2) 计入答案. 具体实现有很多方法, 可以用trie树实现.

下面是自带大常数的代码

#include <cstdio>
#include <cstring>
#include <algorithm>
#define LL long long
using namespace std ;

int N , M , a[200005] , mmod = 1e9 + 7 ;
long long ans ;
struct Node{
    long long cnt ;
    Node *ch[2] ;
}w[200005*32] , *root , *tw = w ;

void InTrie( int x ){
    Node *nd = root ; nd->cnt ++ ;
    for( int i = ( 1 << M ) ; i ; i >>= 1 ){
        int nxt = ( ( i&x ) ? 1 : 0 ) ;
        if( !nd->ch[nxt] ) nd->ch[nxt] = ++tw ;
        nd = nd->ch[nxt] ; nd->cnt ++ ;
    }
}

void dfs( Node *nd , LL siz , LL tmp ){
    long long siz0 = ( nd->ch[0] ? nd->ch[0]->cnt : 0 ) ,
              siz1 = ( nd->ch[1] ? nd->ch[1]->cnt : 0 ) ;
    if( nd->ch[0] ) 
        dfs( nd->ch[0], siz + siz1 , tmp + ( 1 << M - 1 ) %mmod * ( (siz1 * ( siz1 + siz ) )%mmod )%mmod ) ;
    if( nd->ch[1] )
        dfs( nd->ch[1], siz + siz0 , tmp + ( 1 << M - 1 ) %mmod * ( (siz0 * ( siz0 + siz ) )%mmod )%mmod ) ;
    if( !nd->ch[0] && !nd->ch[1] ){
        ans ^= ( tmp % mmod ) ; return ;
    }
}

void solve(){
    dfs( root , 0 , 0 ) ;
    printf( "%d" , ans ) ;
}

int main(){
    root = ++tw ;
    scanf( "%d%d" , &N , &M ) ;
    for( int i = 1 ; i <= N ; i ++ ){
        scanf( "%d" , &a[i] ) ;
        InTrie( a[i] ) ;
    }
    solve() ;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值