P7681 题解
题目描述(摘自原题)
一个班有
第一节课上,Goran 向他的每个朋友扔了一个西瓜,随后几节课,孩子们都会按照以下方式采取行动:
如果上节课一个人被奇数个西瓜扔中,那么这节课他会向所有他的朋友各扔一个西瓜。
如果上节课一个人被偶数(包括
现在,给定孩子的个数
数据范围
对于所有数据,
思路
观察到:
考虑使用 bitset,这样每个人每节课只需要一次与操作即可,成功将时间复杂度从
但是不管再怎么削减计算复杂度,
一张丑丑的图
观察到,在一定节课以后,必定会出现一段循环。图中就体现为第三节课以后都受偶数西瓜。这是因为每次的情况只与上一次有关,且情况最多只有
再回头看
计算出第一次循环的开始和结束,给
时间复杂度
下面是代码,如果具体实现有问题可以看注释。
#include<bits/stdc++.h>
using namespace std;
#define int long long //麻麻再也不用担心我忘记开long long了!
#define rep(i,s,t) for(int i=(s);i<=(t);++i)
#define irep(i,t,s) for(int i=(t);i>=(s);--i)
const int N=21;
int n,h,val[1<<N],cnt[N]; //val数组记录每回合答案(的前缀和),cnt数组记录朋友数(用以计算贡献)
bitset<20> gt[1<<N],fnd[N]; //gt(get)数组记录一节课上每个人被丢西瓜的奇偶,fnd(friend)记录一个人的朋友
map<int,int>used; // used数组记录一种排列上次出现的位置
signed main(){
cin>>n>>h;
rep(i,0,n-1){
rep(j,0,n-1){
char c=getchar();
while(c<'0'||c>'1')c=getchar();
fnd[i][j]=c-'0';
}
cnt[i]=fnd[i].count();
}
val[0]=cnt[0]; //初始一节课的情况
gt[0]=1;
int k=0;
for(k=1;k<=(1<<n)+10;++k){
rep(i,0,n-1){
gt[k][i]=(fnd[i]>[k-1]).count()&1;
val[k]+=cnt[i]*(2-gt[k][i]);
}
if(used.count(gt[k].to_ulong()))break;
used[gt[k].to_ulong()]=k; //记录次情况出现的课节
//to_ulong是将bitset转为整型的函数,方便放入map中排序,就不用打比较函数了
}
int ans=0,st=used[gt[k].to_ulong()];
rep(i,1,(1<<n)+10){
val[i]+=val[i-1]; //计算前缀和
}
if(h<=st){ //如果没有进入循环
cout<<val[h-1]<<'\n';
}
else{ //否则
h-=st;
ans+=val[st-1];
ans+=h/(k-st)*(val[k-1]-val[st-1]);
ans+=val[h%(k-st)+st-1]-val[st-1];
cout<<ans<<'\n';
}
}