T1 氨基酸序列 amino
题目描述
在S星球,生物体需要的氨基酸只有两种 : 得氨酸和便氨酸。
因此,S星球的蛋白质仅由这两种氨基酸构成肽链构成。我们用0和1分别表示得氨酸和便氨酸,那么生物体的蛋白质可以表示成一段由0和1构成的氨基酸序列。
Θ国珂学院在研究完该冠状病毒的RNA序列后,转而开始研究更简单的氨基酸序列。
研究表明,该冠状病毒的氨基酸序列的长度为L。也就是说,如果以氨基酸序列为标准,则一共有 2 L 2^L 2L种不同的冠状病毒。
8224年4月1日,作为 S 星球疫情的一个转折点,她们已经对 SARS-CoV-233 病毒的氨基酸序列有了初步的了解,并打算在培养皿中进一步观察。
具体地,将在接下来的q天,依次发生如下事件,其中每天发生下面两种事件之一:
- 培养皿中,氨基酸序列为 s e q seq seq的病毒的数量增加了 v a l val val,其中 v a l > 0 val>0 val>0表示增加了 v a l val val个个体, v a l < 0 val<0 val<0表示减少了 v a l val val个个体, v a l = 0 val=0 val=0表示没有发生变化。
- 研究表明,给定一个只包含 0 / 1 / ∗ 0/1/* 0/1/∗的序列 m a s k mask mask(称为致死掩码)如果氨基酸序列 s e q seq seq满足,对于 m a s k mask mask中每个非 ∗ * ∗的位置, s e q seq seq中对应的氨基酸和 m a s k mask mask相同,则称该氨基酸序列是致死的。珂学家想知道现在的培养皿中有多少个病毒的氨基酸序列是致死的。
由于生物的进化,不同时间下致死掩码 m a s k mask mask是不相同的,你需要对这些不同的 m a s k mask mask分别作出回答。
输入格式
第一行包含两个正整数 L , q L,q L,q,分别表示氨基酸序列的长度和事件的个数。
接下来 q q q行,每行描述一个事件,格式如下:
- I seq val 表示氨基酸序列为 s e q seq seq的病毒数量增加了 v a l val val,其中 s e q seq seq为长度为 L L L的 0 / 1 0/1 0/1串。
- Q mask 表示对于致死掩码 m a s k mask mask,询问培养皿中有多少个病毒的氨基酸序列是致死的,其中 m a s k mask mask为长度为的 0 / 1 / ∗ 0/1/* 0/1/∗串。
输出格式
对于每次
Q
Q
Q事件,输出一行一个整数,表示氨基酸序列是致死的病毒数量模
2
32
2^{32}
232的结果。
样例1
4 8
Q ****
I 0101 5
I 1110 3
Q ****
Q *0**
Q ***0
I 0101 -3
Q *1**
0
8
0
3
5
数据范围
1 ≤ L ≤ 18 ; 1 ≤ q ≤ 5 ∗ 1 0 5 ; − 1 0 9 ≤ v a l ≤ 1 0 9 ; ∣ s e q ∣ = ∣ m a s k ∣ = L 1\leq L \leq 18 ; 1\leq q \leq 5*10^5 ; -10^9\leq val \leq 10^9 ; |seq|=|mask|=L 1≤L≤18;1≤q≤5∗105;−109≤val≤109;∣seq∣=∣mask∣=L
算法一 3pts
这个部分分根本没有询问操作。随便写一个没输出的程序就行。
但是这个部分分在之后的测试点有可能会TLE掉 不要问我怎么知道的。
写完之后最好判一手吧,反正也费不了多少事。
算法二 3+6+12+6+7pts
暴力维护,写一个Trie树或这枚举子集就好了。
算法三 58pts
对于只有mask中不含1的部分,相当于动态维护子集和,可以在 O ( q ∗ 2 L 2 ) O(q*2^{\frac{L}{2}}) O(q∗22L)的时间内完成,具体做法如下:
具体的,我们将 S S S分块,分成大小为 L 2 \frac{L}{2} 2L的两部分 S 1 , S 2 S_1,S_2 S1,S2。然后维护子集和时只对 S 2 S_2 S2部分求子集和。
这样修改的时候只需要对 S 2 S_2 S2部分进行枚举,查询时对 S 1 S_1 S1求和即可,时间复杂度均为 O ( 2 L 2 ) O(2^\frac{L}{2}) O(22L)。
(如果对这里有疑问可以直接看算法八和代码)
接下来的算法四五都是STD的做法,算法六是STD优化,算法七是STD自称尚未实现的做法,而算法八是我自己写(shui)的做法,如果不想看题解做法可以看看。
算法四


链接:毒蛇越狱
算法五

算法六 (std 优化)

算法七 (尚未实现)

算法八
让我们回顾算法三:
具体的,我们将 S S S分块,分成大小为 L 2 \frac{L}{2} 2L的两部分 S 1 , S 2 S_1,S_2 S1,S2。然后维护子集和时只对 S 2 S_2 S2部分求子集和。
因为 1 ≤ n ≤ 18 1\leq n\leq 18 1≤n≤18,所以在 n = 18 n=18 n=18时两块的长度都为9。
考虑这个算法的瓶颈在哪里:
- 运行时间方面:每次都是 O ( q ∗ 2 9 ) O(q*2^9) O(q∗29),时间极为极限,在这个辣鸡评测机上几乎不可能过不去。
- 内存方面:因为子集处理时相当于把询问的串选定某些 0 , 1 0,1 0,1位置换成 ? ? ?,一共有三种状态, 3 9 3^9 39因为有三没法位运算所以会非常慢,而如果开到 4 9 4^9 49, 2 9 ∗ 4 9 2^9*4^9 29∗49的unsigned int数组正正好好512M,稍微开一点就会炸,所以间接地导致时间慢。
再想想算法三实际上的时间复杂度:
- 修改操作:因为要枚举子集,就是满满的 2 9 2^9 29没法优化,而且三次方运算还拖慢时间。
- 查询操作:与前面9位的 ? ? ?个数s有关,时间复杂度是 2 s 2^s 2s。
于是我就想着优化掉修改操作的常数。
-
因为 2 9 2^9 29太极限了,考虑将 S S S分块大小设成 S 1 = 10 , S 2 = 8 S_1=10,S_2=8 S1=10,S2=8
-
这样的话查询操作就是 2 8 2^8 28了。
然后回想之前的话:
3 9 3^9 39因为有三没法位运算所以会非常慢,而如果开到 4 9 4^9 49, 2 9 ∗ 4 9 2^9*4^9 29∗49的unsigned int数组正正好好512M
- 2 9 2^9 29换成 2 8 2^8 28后,如果四为压位基底,大小是 2 10 ∗ 4 8 2^{10}*4^8 210∗48,256M!然后就可以将这堆压位操作换成更快的位运算。
但是这样有什么麻烦呢?很显然,查询的最坏复杂度变成了 2 10 2^{10} 210。
考虑什么时候会达到这个复杂度:
- 当询问串前10位全是?,一次复杂度 2 10 2^{10} 210
- 询问串前10位只有一个数字,一次复杂度 2 9 2^{9} 29
我们发现,这几种情况是有限的:
- 前十位全问号只有一种情况
- 前十位有九个问号只有20种情况
所以,可以对这21种情况统计末尾,记下每种末尾串的数量,暴力枚举子集统计。
for(i=0;i<10;i++)
f1[i][((x>>8)>>i)&1][x&255]+=y;
c[x&255]+=y;
tmp2=(xx&255);
for(i=tmp2;i;i=tmp2&(i-1))
ans+=c[(xs&255)|i];
ans+=c[xs&255];
最后贴上我的代码:
#include <bits/stdc++.h>
using namespace std;
int n,q;
unsigned int s[1<<10][1<<16];
// unsigned int f2[10][10][2][2][1<<8];
unsigned int f1[10][2][1<<8],c[1<<8];
inline int t10(register int x){return x?1:0;}
void add(register int x,register unsigned int y){
register int i,j;
for(i=0;i<(1<<8);i++)
s[x>>8][(i<<8)|(x&(i^255))]+=y;
for(i=0;i<10;i++)
f1[i][((x>>8)>>i)&1][x&255]+=y;
c[x&255]+=y;
}
inline int popc(int x){
int ret=0;
for(;x;x-=x&-x)
ret++;
return ret;
}
unsigned int query(register int xx,register int xs){
register int tmp1,tmp2,tmp3,tmp4,i,j;
register unsigned int ans=0;
tmp1=popc(xx>>8);
tmp2=(xx>>8);
if(tmp1<=8){
for(i=tmp2;i;i=tmp2&(i-1))
ans+=s[(xs>>8)|i][((xx&255)<<8)|(xs&255)];
ans+=s[(xs>>8)][((xx&255)<<8)|(xs&255)];
} else if(tmp1==10){
tmp2=(xx&255);
for(i=tmp2;i;i=tmp2&(i-1))
ans+=c[(xs&255)|i];
ans+=c[xs&255];
} else {
for(i=0;i<10;i++)
if(!(xx&(1<<(i+8)))){
tmp3=i,tmp4=(xs>>(i+8))&1;
break;
}
tmp2=(xx&255);
for(i=tmp2;i;i=tmp2&(i-1))
ans+=f1[tmp3][tmp4][(xs&255)|i];
ans+=f1[tmp3][tmp4][(xs&255)];
}
return ans;
}
inline int read(){
register int x=0,f=1;
register char c;
while(!isdigit(c=getchar()))
if(c=='-') f=-1;
do x=(x<<3)+(x<<1)+(c^48);
while(isdigit(c=getchar()));
return x*f;
}
struct query{
bool opt;
int a,b;
}Q[500010];
int main(){
#ifdef ONLINE_JUDGE
freopen("amino.in","r",stdin);
freopen("amino.out","w",stdout);
#endif
scanf("%d%d",&n,&q);
char ch;
unsigned int utmp;
int xx,xs;
bool flag=true;
for(int i=1,x,y;i<=q;i++){
do ch=getchar();
while(ch!='Q'&&ch!='I');
if(ch=='I') {
Q[i].opt=false;
x=0;
ch=getchar();
for(int j=1;j<=n;j++){
ch=getchar();
x=(x<<1)+(ch=='1');
}
Q[i].a=x,Q[i].b=read();
// add(x,read());
} else {
Q[i].opt=true;
flag=false;
xx=xs=0;
ch=getchar();
for(int j=1;j<=n;j++){
ch=getchar();
xx<<=1,xs<<=1;
if(ch=='*') xx|=1;
else if(ch=='1') xs|=1;
}
Q[i].a=xx,Q[i].b=xs;
// printf("%u\n",query());
}
}
if(flag) return 0;
for(int i=1;i<=q;i++)
if(!Q[i].opt) add(Q[i].a,Q[i].b);
else printf("%u\n",query(Q[i].a,Q[i].b));
return 0;
}
T2
T3

这篇博客介绍了S星球生物的氨基酸序列构成,特别关注于冠状病毒的氨基酸序列研究。文章讨论了一种算法,该算法通过分块处理优化了对氨基酸序列的维护和查询操作,以应对不同致死掩码的查询,同时处理了病毒数量的增减。内容涵盖了算法设计、时间复杂度分析以及实际实现,展示了在限制时间内解决复杂问题的方法。
1327





