对于本周模拟赛的难题进行分析
DAY1
【问题描述】
你是一位精明的逻辑学家,擅长用计算机来解决复杂的逻辑学问题。
在逻辑学推断中,你得到两个 01 数列,长度分别为 29n 和 29m。为了
方便进行数学表达,你将这两个数列转化为长度分别为 n 和 m 的数列,每
一个数字代表一个长度为 29 的 01 串(二进制表达)。记这两个数列为 {Vi} 和 {Ui}。
由这两个数列可以得到矩阵 A,满足 Ai,j = Vi xor Uj。
你希望求得 A 的一个子矩阵使得其内部异或和最大。
【输入格式】
第一行为两个正整数 n,m,用空格隔开。
第二行为 n 个非负整数,表示 {Vi}。
第三行为 m 个非负整数,表示 {Ui}。
n <= 1000 vi,ui<= 2^29
首先我们考虑一下暴力怎么写。我们可以枚举每个矩阵的长度和宽度,然后再枚举这个矩阵的位置,那么也就是,四次方的复杂度。这样的做法可以在考试中拿到30分。
下面我们来考虑一下怎么优化这种做法。我们可以把矩阵的每一个元素都先写成vi * uj的形式 又因为异或的性质 :x^x = 0 , 所以我们可以发现当一个矩阵的长和宽都是偶数时,这个矩阵其实他的异或和一定为0 ; (自己手动模拟可得)可惜我考试的时候没发现
所以 对答案有贡献的只有两种情况:一边为奇一边为偶 或者两边都为奇
当一边为奇一边为偶数的时候, 答案为长度为奇的那一段的异或和 ;
对于这种情况 枚举区间即可 n^2的复杂度显然可过
而当两边都为奇的时候,就是任意两段长度为奇的数异或在一起
如果照旧枚举的话 可能复杂度会到n^3 不符合要求
我们来考率当一段异或和为1011的时候,我们的目标是不是看另外一边是否可以为0100 因为这样答案最优对吧
这样的一个过程像什么呢?
字符串匹配!!!!!
这个时候我们就可以用二进制表示每个数,建立tried树;
查找的时候就可以在tried树上找一遍就好啦
而且ui和vi小于2^29的目的就是确保点不会太多
下面给出具体的代码, 具体的细节请读者自行思考
#include<bits/stdc++.h>
using namespace std ;
#define maxn 1010
#define int long long
#define kkk signed main
#define re register int
inline int read(){
int ans = 0 , f = 1 ; char ch = getchar() ;
while ( ch < '0' || ch > '9') { if( ch == '-' ) f = -1 ; ch = getchar() ; }
while ( ch >= '0' && ch <= '9' ) ans = ( ans << 3 ) + ( ans << 1 ) + ch - '0' , ch = getchar() ;
return ans * f ;
}
int n , m;
int a[maxn] , b[maxn] ;
int ch[14000000][2] , sz;
inline void isert(int x){
// printf("x : %lld\n" , x) ;
int u = 0 ;
for(int i = 29 ; i >= 1 ; --i){
int now = (x >> (i - 1)) & 1 ;
// printf("now : %lld i :%lld\n" , now , i) ;
if(!ch[u][now]){
ch[u][now] = ++sz ;
}
u = ch[u][now] ;
}
}
inline int query(int x){
int u = 0 , ans = 0 ;
for(int i = 29 ; i >= 1 ; i--){
int now = (x >> (i - 1)) & 1 ;
if(ch[u][!now]){
ans |= (1 << (i - 1)) ;
u = ch[u][!now];
}
else {
// ans |= (now) << (i - 1) ;
u = ch[u][now] ;
}
}
return ans ;
}
kkk(){
freopen("c.in" , "r" , stdin) ;
freopen("c.out" , "w" , stdout) ;
n = read() , m = read() ;
for(re i = 1 ; i <= n ; ++i)
a[i] = read() ;
for(re i = 1 ; i <= m ; ++i)
b[i] = read() ;
int ans = 0 ;
for(int len = 1 ; len <= n ; len++){
for(int i = 1 ; i + len -1 <= n ; i++){
int temp = 0 ;
for(int j = i ; j <= i + len - 1 ; j++){
temp ^= a[j] ;
}
if(len % 2 && len != 1)ans = max(ans , temp) ;
if(len % 2)isert(temp) ;
}
}
for(int len = 1 ; len <= m ; len++){
for(int i = 1 ; i + len -1 <= m ; i++){
int temp = 0 ;
for(int j = i ; j <= i + len - 1 ; j++){
temp ^= b[j] ;
}
if(len % 2 && len != 1)ans = max(ans , temp) ;
if(len % 2)ans = max(query(temp) , ans) ;
}
}
printf("%lld\n" , ans) ;
}
DAY2
你是一位精明的数学家,擅长用计算机来解决各类小学奥数题。
这一天,你想起来了著名的 “开关灯问题”,它曾经经常被拿来为难小朋
友们。于是你决定完成一个程序来彻底解决这个问题。
“开关灯问题” 在小学奥数书上的描述是这样的:
一个走廊里有 100 盏按顺序排好的灯,它们分别拥有 1 ∼ 100 的编号,
最开始时都是关上的。小明同学非常无聊,他首先按下了所有编号为 2 的倍
数的灯的开关,接着他又按下了所有编号为 3 的倍数的灯的开关,问最后有
多少灯是打开的。
因为你希望最终完全解决这类问题,因此你将题目一般化:
现在有两种灯的开关:第一种开关是熔断型的,即只要第一次使用开关
后,开关将保持打开状态不变;第二种开关是普通型的,即使用奇数次开关
时,灯会打开,使用偶数次时,灯又会关闭。
一个走廊里有 n 盏按顺序排好的灯,它们使用的都是第 type 种开关
(type = 1 或 2)。最开始时,1 ∼ n 号灯都是关上的,开关也从未使用过。小
明同学非常无聊,他进行了 d 次操作,每一次操作形如:“将编号为 ki 的倍
数的灯的开关按下”。问经过这 d 次操作,最终有多少盏灯是打开的。
你完成的程序需要实现 Q 次任务,每一次任务会给出 n,type,d,{ki}。
【输入格式】
第一行为一个正整数 Q,表示有 Q 次任务。
对于每一次任务,第一行为三个正整数 n, type, d,第二行为 d 个正整数,
表示 ki。
【样例输入 1】 2
6 1 2
2 3
6 2 2
2 3
【样例输出 1】 4
3
T <= 1000 n <= 10^18 d <= 12
对于type == 1 的情况经典容斥原理 直接套结论
首先逐一枚举子集,得到子集的最小公倍数,那么它的倍数个
数就确定 ,如果子集中有奇数个元素对应加上,偶数个元素对应减去
那么对于 type == 2 的 情况则需要推式子了
经过手动模拟, 可以得出这时候的容斥系数为(-2)^(i - 1)
如果真的推的话要二项式反演,蒟蒻不会就只能模拟了
所以主要就是把容斥系数改下,和之前没有区别
本蒟蒻没想到则是因为根本就没用过容斥原理。。。。。。
#include<bits/stdc++.h>
using namespace std ;
//#define maxn
#define int long long
#define kkk signed main
#define re register int
inline int read(){
int ans = 0 , f = 1 ; char ch = getchar() ;
while ( ch < '0' || ch > '9') { if( ch == '-' ) f = -1 ; ch = getchar() ; }
while ( ch >= '0' && ch <= '9' ) ans = ( ans << 3 ) + ( ans << 1 ) + ch - '0' , ch = getchar() ;
return ans * f ;
}
inline int gcd(int a , int b){
return b ? gcd(b , a % b) : a ;
}
inline int lcm(int a ,int b){
return a * b / gcd(a , b) ;
}
int q , n ,type , d ;
int k[13] ;
int temp ,sum , ans;
int ksm(int a , int b){
int ss = 1 ;
while(b > 0){
if(b & 1) ss *= a ;
a = a * a ;
b >>= 1 ;
}
return ss ;
}
int l[1 << 13] , size[1 << 13];
kkk(){
freopen("c.in" , "r" , stdin) ;
freopen("c.out" , "w" , stdout) ;
q = read() ;
for(int i = 1 ; i < (1 << 12) ; i++){
re now = i , cntt = 0 ;
while(now > 0){
if(now & 1) cntt++ ;
now >>= 1 ;
}
size[i] = cntt ;
}
while(q--){
n = read() , type = read() , d = read() ; ans = 0 ;
memset(k , 0 , sizeof(k)) ; memset(l , 0 , sizeof(l)) ;
for(re i = 1 ; i <= d ; ++i)
k[i] = read() ;
int ii ;
l[0] = 1 ;
for(int s = 1 ; s < (1 << d) ; ++s){
for( ii = 1; ii <= d ; ii++)
if((s >> (ii - 1) & 1)) break ;
re aaa = s ^ (1 << (ii - 1)) ;
l[s] = l[aaa] * k[ii] / gcd(l[aaa] , k[ii]) ;
}
if(type == 1){
for(re i = 1 ; i < (1 << d) ; ++i){
temp = l[i] ; sum = size[i] ;
if(sum % 2) ans += n / temp ;
else ans -= n / temp ;
}
}
else {
for(re i = 1 ; i < (1 << d) ; ++i){
sum = size[i] ; temp = l[i] ;
if((sum - 1 )% 2)
ans -= (ksm(2 , sum -1 ) * (n / temp) );
else ans += (ksm(2 , sum -1 ) * (n / temp) );
}
}
printf("%lld\n" , ans) ;
}
}
DAY4
换教练了。。。。。。
难度突增。。。。。
问题描述
小 K 出了 m 道题,并且找了 n 个选手来给题目打分。每个选手可以给每道
题评分为”Y” 或”N”,同时小 K 认为一道题是优秀的当且仅当有人给这道题评分
为”Y”,同时有人给这道题评分为”N”。
现在所有选手都完成了评分,但是小 K 不认为每个选手的评分都应该采用。于
是小 K 决定对于每一个选手的评分,都有 0.5 的概率采用,0.5 的概率不采用。(注
意可能会导致无人参与评分)
现在,小 K 想知道,对于任意一种题目的集合(例如有两道题,则有三个集合
{1},{2},{1,2}),集合中所有题都为优秀的概率是多少?
为了方便计算,对于总共 2m 1 个集合,假设第 i 个中题目全为优秀的概率
为 Pi,请输出
(P1^2n mod (109+7))xor(P2^2n mod (109+7))xor . . . xor(P(2m-1)^2n mod (109+7))
样例输入
2
2 2
NY YN
4 2
NN NY YN YY
输出
1
7
看到 n 很大,m 却很小的时候就该往状压和容斥上面想了。实际上这
道题正是一个基础的容斥。怎么又是容斥
首先我们可以发现,直接计算一个集合中的题目是否全为优秀不好计
算,但是计算集合中题目全不优秀很好计算。(正难则反的思想)
假设题目集合为全集,此时我们不能同时选择任意两个评分不同的选
手。因此假设每种评分有 x 个选手,方案数则加上 2^x - 1。不要忘记最后
加上空集的一种方案。
具体的,我们把每一个选手的评分看成一个二进制数,使用一个数组
统计每一个数的出现次数;为我们计算 Si 的时候,枚举 Si 的所有子集 s,
计算 s 中题目评分为’Y’,Si - s 中评分为’N’,其余题目评分随意的方案数。
如此我们得到了对于任意一个题目集合全不优秀的方案数,但我们要
求的是全部优秀的方案数。
实际上我们可以通过全不优秀的情况求出不全优秀的情况,再用全集
一减即可得到答案。这就是一个经典容斥了
下面给出巧妙的代码实现
#include<bits/stdc++.h>
using namespace std ;
#define maxn 1000010
#define int long long
#define kkk signed main
inline int read(){
int ans = 0 , f = 1 ; char ch = getchar() ;
while ( ch < '0' || ch > '9') { if( ch == '-' ) f = -1 ; ch = getchar() ; }
while ( ch >= '0' && ch <= '9' ) ans = ( ans << 3 ) + ( ans << 1 ) + ch - '0' , ch = getchar() ;
return ans * f ;
}
#define mod 1000000007
int cnt[maxn] ;
int a[maxn] ; // a :
int mi[maxn] , bc[maxn] ;
int T , n , m , ans , temp;
char in[maxn] , s[maxn];
bool vis[maxn] ;
int lowbit(int x){
return x & (-x) ;
}
inline void dfs(int x){
vis[x] = 1 ;
a[x] = mi[cnt[0]] ;
for(int u = x ; u ; u = (u - 1) & x)
a[x] += mi[cnt[u]] - 1 , a[x] %= mod ;
for(int i = 1 ; i <= m ; i++)
if(((1 << (i - 1)) & x) && !vis[x ^ (1 << (i - 1))]){
// printf("x : %lld\n" , x) ;
for(int u = x ; u ; u = (u - 1) & x){
if(u & (1 << (i - 1))) cnt[u ^ (1 << (i - 1))] += cnt[u] ;
}
dfs(x ^ (1 << (i - 1))) ;
for(int u = x ; u ; u = (u - 1) & x){
if(u & (1 << (i - 1))) cnt[u ^ (1 << (i - 1))] -= cnt[u] ;
}
}
}
#define File(s) freopen(s".in","r",stdin),freopen(s".out","w",stdout)
kkk(){
File("survey");
T = read() ;
// printf("T : %lld\n" , T) ;
mi[0] = 1 ;
bc[0] = 0 ;
for(int i = 1 ; i < maxn ;i++) mi[i] = (mi[i - 1] << 1) % mod ;
for(int i = 1 ; i < (1 << 16) ;i++) bc[i] = bc[i ^ lowbit(i)] + 1 ;
while(T--){
// printf("T : %lld\n" , T) ;
n = read() , m = read() ;
for(int i = 1 ; i <= n ; i++){
scanf("%s" , in + 1) ;
temp = 0 ;
for(int j = 1 ; j <= m ; j++)
temp |= (in[j] == 'Y' ? 1 : 0) << (j - 1) ;
cnt[temp]++ ;
}
vis[0] = 1 ; ans = 0 ;
dfs((1 << m) - 1) ;
for(int i = 1 ; i < (1 << m) ; i++){
int now = 0 ;
for(int u = i ; u ; u = (u - 1) & i)
now += bc[u] & 1 ? a[u] : -a[u] , now %= mod ;
now = (mi[n] - now) % mod ;
now = (now + mod) % mod ;
ans ^= now ;
}
// cout << "YeS" << endl ;
printf("%lld\n" , ans);
for(int i = 0 ; i < (1 << m ) ; i++)
cnt[i] = a[i] = vis[i] = 0;
}
}
注意这里枚举子集的子集的复杂度是n^3的
至于为什么呢 emmmmmm 蒟蒻不知道诶。。。
DAY5几乎每道题都难。。。 等我写完正解再补吧。。。
感觉就是noipday2的难度了我还是太弱了
如果对于解析有不懂请留言代码有点丑因为都是我写的
虽然只是我个人模拟赛的解析 但我觉得这些思路是可以借鉴的 代码实现也很巧妙
upd2020.8.16 0:10