致出题人:此篇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() ;
}