## $Round1~D1T1$外星千足虫
\(\mathtt{BSOJ2793}\)——高斯消元解异或方程组
有\(n\)个数\(\{a_i\}\)
给出\(m\)个信息,每个信息给出\(\displaystyle{(\sum_{i=1}^m a_{b_i})\bmod 2}\)(\(\{b_i\}\)是\({1,2,\cdots,n}\)的子集)
求最少几次操作即可确定可能取值
给出的信息可以转化为\(\displaystyle{\oplus_{i=1}^m a_{b_i}}\)
因此问题转化为求异或方程组最少几行就可以有解
高斯消元求自由元个数即可
复杂度\(O(n^3)\)
考虑高斯消元集合异或操作太浪费,使用\(bitset\)优化异或的特殊情况
复杂度\(O(\frac{n^3}{64})\)
int n,m,ans;
bitset<N> a[M];
int main(void){
re int i,j;n=read()+1,m=read();
for(i=1;i<=m;++i)
for(j=1;j<=n;++j)a[i][j]=gc();
for(i=1;i<n;++i){
for(j=i;j<=m;++j)if(a[j][i])break;
ans=max(ans,j);
if(j>m)return puts("Cannot Determine"),0;
if(i^j)swap(a[i],a[j]);
for(j=1;j<=m;++j){if(i==j||!a[j][i])continue;a[j]^=a[i];}
}
printf("%d\n",ans);
for(i=1;i<n;++i)puts((a[i][n])?"?y7M#":"Earth");
return 0;
}
把8行的初始设为了i+1
\(\mathtt{BSOJ2794}\)——补集思想简化状态+逆向递推计数dp
求长度为\(n\)的排列满足任意\(i\in[2,n-1]\)有\(a_i>a_{i-1}\&a_i>a_{i+1}\)或\(a_i<a_{i-1}\&a_i<a_{i+1}\)
形象来说本题是要我们求//\和//的数量
首先考虑若排列\(\{a_i\}\)满足要求那么\(\{b_i\}(b_i=n-a_i)\)也满足要求
那么我们单求//就可以了(所以第一个不为\(1\))
紧接着我们会发现这个计数题正着做不好做,但构造任意一个序列是容易的所以我们逆推,用反构造(合法\(\rightarrow\)合法)的过程去实现\(dp\)
已经有\(1\rightarrow i\)首位为\(j\)的序列,设情况有\(dp_{i,j}\)个
注意滚动一下
int main(void){
re int i,j,now=0;
n=read(),mod=read();dp[now][2]=1;
for(i=3;i<=n;++i){
now^=1;
for(j=2;j<=i;++j)dp[now][j]=Mod(dp[now][j-1]+dp[now^1][i-j+1]);
}
for(i=2;i<=n;++i)ans=Mod(ans+dp[now][i]);
printf("%d\n",Mod(ans+ans));
return 0;
}
\(\mathtt{BSOJ2795}\)——最短路+拓扑排序
给出一个有向图,有一些点必须在限制他的点之后到达他,求\(1\rightarrow n\)的最短路
考虑到限制实际上是让我们满足限制关系\(DAG\)拓扑序的去完成最短路
因此我们就魔改最短路,加入\(Topsort\)的过程即可
设\(dis_x\)表示\(1\rightarrow x\)的最短路(中间未\(Topsort\)完不一定满足要求)
\(real_x\)表示满足拓扑序关系下\(1\rightarrow x\)的实际最短路(限制我的都被走完)
注意:每次用\(real\)去更新\(dis\),只有被限制的更新完了才去更新别的,这样才保证拓扑序
核心
inline void Dijkstra(void){
re int i,x,v,d;
for(i=1;i<=n;++i)dis[i]=INF;
q.push((Node){1,dis[1]=real[1]=0});
while(!q.empty()){
x=q.top().x,d=q.top().dis,v=max(dis[x],real[x]),q.pop();
if(v^d)continue;
for(i=h[x];i;i=e[i].next){
re int y=e[i].to;
if(v+e[i].v<dis[y]){dis[y]=v+e[i].v;if(!deg[y])q.push((Node){y,max(dis[y],real[y])});}
}
for(re int y:g[x]){real[y]=max(real[x],v);if(!--deg[y])q.push((Node){y,max(real[y],dis[y])});}
}
}
\(\mathtt{BSOJ3301}\)——Tarjan+二维hash+虚点技巧
\(n*m\)的方格中,有些点很特殊
- 可以到达同行任意点
可以到达同列任意点
可以到达八连通的点
问从任意点出发可以到达最多点数
如果把每个点向其联通的连边,缩点后求最长路就是答案
考虑优化建图,可以发现连边的冗杂在于行列连边,因此我们对每行每列建虚点,边数就与特殊点数有关
n=read(),read(),read();
for(i=1;i<=n;++i){
x[i]=read(),y[i]=read(),opt[i]=read();
hash[Find(x[i],y[i])]=fa[i]=i;v[i]=1;
if(opt[i]==1)Sx[x[i]]?++v[fa[i]=Sx[x[i]]]:Sx[x[i]]=i;
else if(opt[i]^3)Sy[y[i]]?++v[fa[i]=Sy[y[i]]]:Sy[y[i]]=i;
}//Sx,Sy分别指向他所在行列的第一个点
for(i=1;i<=n;++i){
if(opt[i]==3){
for(dir=0;dir<8;++dir)if(hash[res=Find(x[i]+dx[dir],y[i]+dy[dir])])AddEdge(fa[i],fa[hash[res]]);
if(Sx[x[i]])AddEdge(Sx[x[i]],fa[i]);if(Sy[y[i]])AddEdge(Sy[y[i]],fa[i]);
}
else if(opt[i]==1&&Sy[y[i]])AddEdge(Sy[y[i]],fa[i]);
else if(opt[i]==2&&Sx[x[i]])AddEdge(Sx[x[i]],fa[i]);
}
其中\(Find\)是hash表上
inline int Find(re int a,re int b){
re int z=(a*13+b*19)%N;
while(hash[z]&&(x[hash[z]]^a||y[hash[z]]^b))if(++z==N)z=0;
return z;
}
\(\mathtt{BSOJ3301}\)——前缀和+主席树
给出\(n*m\)的矩阵,多次询问指定区间选数大于等于指定值得最少选数个数
对\(50\%\)数据,\(n,m\le 200\)
对另\(50\%\)数据,\(n=1,m\le 5*10^5\)
对前半部分暴力从大到小枚举权值用二维前缀和试减即可
后半部分利用二分最小值+主席树求和,可以用一个\(\log\)的主席树上划分即可
\(\mathtt{BSOJ3303}\)——费用流+拆点
给一张\(DAG\),有边权,每个点有出发点权.求最小代价使得每个点都被经过。
遇到点权就拆入出点
建图:对起点\((S,i',1,a_i)\):表示从\(i\)直接出发
\((S,i,1,0),(i',T,1,0)\)
对每条边\((x',y,1,w_{x,y})\)
跑最小费用流即可
n=read(),m=read(),S=0,T=n<<1|1,P=T+1;
for(i=1;i<=n;++i)a[i]=read(),AddEdge(S,i+n,1,a[i]);
for(i=1;i<=n;++i)AddEdge(S,i,1,0),AddEdge(i+n,T,1,0);
while(m--){
x=read(),y=read(),z=read();
if(x>y)swap(x,y);
if(z<a[y])AddEdge(x,y+n,1,z);
}
\(\mathtt{BSOJ3318}\)——数论综合(卢卡斯+CRT)
求\(G^{\sum\limits_{d|n}\binom{n}{d}}\pmod {999911659}(G,n\le 10^9)\)
首先由扩展欧拉定理\(G^{\sum\limits_{d|n}\binom{n}{d}}\equiv G^{\sum\limits_{d|n}\binom{n}{d}~\bmod{~999911659}}\pmod {999911659}\)
因为\(Lucas\)的复杂度是\(O(p\log_p n)\)考虑缩小模数
\(\because 999911658=2×3×4679×35617\)
所以可以分别求组合数再\(CRT\)一下
有一个多组数据版本\(\mathtt{BSOJ4834}\)需要预处理对每个模数的逆元等
多组询问离每个点曼哈顿距离最大点
这个是裸题不用题
\(\mathtt{BSOJ3320}\)——双向链表+细节+模拟
简述个鬼
我写之前学习了loj这位大佬的提交(我不会说是我写到一半发现无懈可击不怎么会写),然后后来就把其他部分改得跟他一致了(他写得太精简了,下面会说明),希望为他起一个注解的作用
这是一个类似(抄袭)三国杀的游戏
我们一句一句的来看一只猪有那些信息:
主猪(MP):自己存活的情况下消灭所有的反猪。
忠猪(ZP):不惜一切保护主猪,胜利条件与主猪相同。
反猪(AP):杀死主猪。
这是类型
游戏开始时候,每个玩家手里都会有4张牌,且体力上限和初始体力都是4。
这是体力,和牌
1.如果没有猪哥连弩,每个出牌阶段只能使用一次“杀”来攻击;
2.任何牌被使用后被弃置(武器是装备上);
牌分为手牌(消耗品),武装(永久品):只有猪哥连弩
◎献殷勤:使用无懈可击挡下南猪入侵、万箭齐发、决斗;使用无懈可击抵消表敌意;
◎表敌意:对某个角色使用杀、决斗;使用无懈可击抵消献殷勤;
◎跳忠:即通过行动表示自己是忠猪。跳忠行动就是对主猪或对某只已经跳忠的猪献殷勤,或者对某只已经跳反的猪表敌意;
◎跳反:即通过行动表示自己是反猪。跳反行动就是对主猪或对某只已经跳忠的猪表敌意,或者对某只已经跳反的猪献殷勤;
忠猪不会跳反,反猪也不会跳忠;不管是忠猪还是反猪,能够跳必然跳;
除了主猪外只有通过跳身份的办法来显示身份
所以我们定义一只猪
struct Pig{
char type,card[M],weapon,show;//类型,卡,武器有无,我表示(不表示0)我是什么身份(1忠/主猪,2反猪,3类反猪)
int heal,cardnum;//血量,卡数
}a[N];
他的行为有抽牌,用牌,找牌(并用掉)
struct Pig{
char type,card[M],weapon,show;//类型,卡,武器有无,我表示(不表示0)我是什么身份
int heal,cardnum;//血量,卡数
inline void Delete(re int&x){re int i;--cardnum;for(i=x;i<=cardnum;++i)card[i]=card[i+1];--x;}//丢掉第x张牌
inline char Find(re char a){re int i;for(i=1;i<=cardnum;++i)if(card[i]==a)return Delete(i),1;return 0;}//检查手牌有没有a
inline void Get(re int x){while(x--){card[++cardnum]=st[++top];if(top==m)--top;}}//牌堆摸x张
}a[N];
另外我们用一个双向链表来维护猪之间位置关系,这样删除很方便
接下来看手牌
◎基本牌:
『桃(P)』:在自己的回合内,如果自己的体力值不等于体力上限,那么使用一个桃可以为自己补充一点体力,否则不能使用桃;桃只能对自己使用;在自己的回合外,如果自己的血变为0或者更低,那么也可以使用;
『杀(K)』:在自己的回合内,对攻击范围内除自己以外的一名角色使用。如果没有被『闪』抵消,则造成1点伤害。无论有无武器,杀的攻击范围都是1;
『闪(D)』:当你受到杀的攻击时,可以弃置一张闪来抵消杀的效果;
◎锦囊牌:
『决斗(F)』:出牌阶段,对除自己以外任意一名角色使用,由目标角色先开始,自己和目标角色轮流弃置一张杀,首先没有杀可弃的一方受到1点伤害,另一方视为此伤害的来源;
『南猪入侵(N)』:出牌阶段,对除你以外所有角色使用,按逆时针顺序从使用者下家开始依次结算,除非弃置一张杀,否则受到1点伤害;
『万箭齐发(W)』:和南猪入侵类似,不过要弃置的不是杀而是闪;
『无懈可击(J)』:在目标锦囊生效前抵消其效果。每次有一张锦囊即将生效时,从使用这张锦囊的猪开始,按照逆时针顺序,依次得到使用无懈可击的机会;
效果:用于决斗时,决斗无效并弃置;用于南猪入侵或万箭齐发时,当结算到某个角色时才能使用,当前角色不需弃置牌并且不会受到伤害(仅对一个角色产生效果);用于无懈可击时,成为目标的无懈可击被无效。
我们会发现『杀(K)』『南猪入侵(N)』『万箭齐发(W)』攻击力都是1,新建一个功能\(Defend(x,y)\)表示\(x\)成功伤害\(y\),注意到这一次可能触发
如果自己的血变为0或者更低,那么也可以使用桃
还可能杀死它猪
inline void Defend(re int x,re int y){//x砍y一点血
if(!--a[y].heal)a[y].heal+=a[y].Find('P');//能用桃就用
if(!a[y].heal)Kill(x,y);//杀死了
}
对于\(x\)杀死\(y\),先看胜利条件
主猪(MP):自己存活的情况下消灭所有的反猪。
忠猪(ZP):不惜一切保护主猪,胜利条件与主猪相同。
反猪(AP):杀死主猪。
再判一下几种情况
◎玩家死亡:如果该玩家的体力降到0或者更低,并且自己手中没有足够的桃使得自己的体力值回到1,那么就死亡了,死亡后所有的牌(装备区,手牌区)被弃置;
◎奖励与惩罚:反猪死亡时,最后一个伤害来源处(即使是反猪)立即摸三张牌。忠猪死亡时,如果最后一个伤害来源是主猪,那么主猪所有装备牌、手牌被弃置;
◎注意,一旦达成胜利条件,游戏立刻结束,因此即使会摸3张牌或者还有牌可以用也不用执行了。
inline void Kill(re int x,re int y){//x把y杀了的结果
if(a[y].type=='M')Print();//忠猪被杀
if(a[y].type=='F'){
if(!(--enemynum))Print();//敌人杀完
a[x].Get(3);//没结束要摸牌
}
else if(a[x].type=='M'&&a[y].type=='Z')a[x].cardnum=a[x].weapon=0;//我杀我忠猪
r[l[y]]=r[y],l[r[y]]=l[y];//去掉y
}
最困难的判定是无懈可击,我们要看\(x\)可不可以攻击\(y\),首先看\(y\)可不可以被队友(这地方就涉及到显示的身份)无效,然后再看这个无效可不可以被成功无效(成功无效就代表之后再没有一个无效)
我们会发现这是一个递归的过程,重点是理解无效本身是一种会被无效的行为
inline char WXKJ(re int x,re int y){
re int i;re char first;
for(i=x,first=1;(first||(i^x));i=r[i],first=0)
if((!y?CanDefend(i,x):MyPig(i,y))&&a[i].Find('J')){//有y是看友军可不可以无效别人攻击保护你,没y是看敌人可不可以无效你的无效,递归次数%2表示到底实在阻止这场攻击发生
a[i].show=(a[i].type=='F'?2:1);
return !WXKJ(i,0);//你不能无效我才发动行为(包括无效)成功
}
return 0;
}
在这里判定别人是什么猪就要用到别人显示的身份(而不是真实身份)
inline char CanDefend(re int x,re int y){
return ((a[x].type=='M'&&(a[y].show==2||a[y].show==3))||(a[x].type=='Z'&&a[y].show==2)||(a[x].type=='F'&&a[y].show==1));
}
inline char MyPig(re int x,re int y){
return (((a[x].type=='M'||a[x].type=='Z')&&a[y].show==1)||(a[x].type=='F'&&a[y].show==2));
}
然后具体写每个手牌的操作就可以了
\(\mathtt{BSOJ3323}\)——计数dp,巧妙思维
求\(n\)位逐位递增能被\(P\)整除的个数
首先注意到一个性质,合法的数,都可以看做小于等于\(9\)个形如\(000\dots111\)的数累加
设\(cnt_i=\sum[x形如"000\cdots111"][x\equiv i\pmod{p}]\)
考虑做背包,设\(f_{i,j,k}\)表示考虑余数为\(0\cdots i\)的的后缀选\(k\)个和的余数为\(j\)的方案数
枚举每一种余数个数即可转移
\(\displaystyle f_{i,j,k}=\sum_{l=0}^k\binom{cnt_i+l-1}{l}f_{i-1,j−il,k-l}\)注意第二维可能越界
答案很显然
\[Ans=\sum_{i=0}^8f_{p-1,0,i}
\]
for(i=0;i<p;++i){
C[i][0]=1;
if(cnt[i])for(j=1;j<9;++j)C[i][j]=1ll*C[i][j-1]*cnt[i]%mod*inv[j]%mod,cnt[i]=Mod(cnt[i]+1);
}
dp[now][first][0]=1;
for(i=0;i<p;++i){
now^=1;for(j=0;j<p;++j)for(k=0;k<9;++k){
dp[now][j][k]=0;
for(l=0;l<=k;++l)dp[now][j][k]=(dp[now][j][k]+1ll*dp[now^1][(j-(i*l%p)+p)%p][k-l]*C[i][l])%mod;
}
}
for(i=0;i<9;++i)ans=Mod(ans+dp[now][0][i]);printf("%d\n",ans);
手机扫一扫
移动阅读更方便
你可能感兴趣的文章