牛逼!在IDEA裡搞Spring Boot Mybatis反向工程,太爽咯~
「JOI 2018 Final」糰子製作
Algorithm 1: 暴力計算
對於所有\(0,1,?\)組成的\(3^n\)種串處理出答案
具體的,對於當前串包含的最後一個\(?\)位置,列舉它變成0/1的答案,按照一定的順序累和即可
(程式碼可以在Algo2裡面看到)
Algorithm 2 : Meet in the Middle
\(3^{20}\)太大,優化上面的暴力,容易想到把複雜度從預處理分一部分給查詢
取出\(n\)中前\(k\)個位置,這些位置不處理\(3^k\),而是讓每個詢問暴力地去列舉這些位置上的\(?\)變成\(0/1\)
顯然每個詢問有最多\(2^k\)次列舉,即複雜度為\(O(Q\cdot 2^k)\)
對於剩下的\(n-k\)個位置,採取上面的暴力方法預處理,三進位制列舉,預處理複雜度為\(O(2^k3^{n-k})\)
因此複雜度為\(O(Q\cdot 2^k +2^k3^{n-k})\),計算在\(k=6,7\)時複雜度約為\(3.5\cdot 10^8\)
(這是一個非常穩的複雜度)
#include<bits/stdc++.h> using namespace std; typedef long long ll; typedef unsigned long long ull; typedef double db; typedef pair <int,int> Pii; #define reg register #define pb push_back #define mp make_pair #define Mod1(x) ((x>=P)&&(x-=P)) #define Mod2(x) ((x<0)&&(x+=P)) #define rep(i,a,b) for(int i=a,i##end=b;i<=i##end;++i) #define drep(i,a,b) for(int i=a,i##end=b;i>=i##end;--i) template <class T> inline void cmin(T &a,T b){ ((a>b)&&(a=b)); } template <class T> inline void cmax(T &a,T b){ ((a<b)&&(a=b)); } char IO; ll rd(){ ll s=0,f=0; while(!isdigit(IO=getchar())) if(IO=='-') f=1; do s=(s<<1)+(s<<3)+(IO^'0'); while(isdigit(IO=getchar())); return f?-s:s; } const int N=20,M=1<<20,M3=1600000; const int DM=7; int n,m,k; int Pow[N],S[M3],Low[M3],trans[M3]; int QX[M],QY[M],QZ[M],Ans[M],rev[M]; char val[M],q[N]; int main() { rep(i,Pow[0]=1,N-1) Pow[i]=Pow[i-1]*3; n=rd(),m=rd(),k=min(DM,n),scanf("%s",val); rep(i,1,(1<<n)-1) { rev[i]=(rev[i>>1]>>1)|((i&1)<<(n-1)); if(i<rev[i]) swap(val[i],val[rev[i]]); } Low[0]=1e9; rep(i,1,Pow[n-k]-1) { Low[i]=(i%3==2)?0:Low[i/3]+1; if(Low[i]>n) trans[i]=(trans[i/3]<<1)|(i%3); } rep(i,1,m) { scanf("%s",q); rep(j,0,n-1) { if(q[j]=='?') QY[i]|=1<<j,q[j]='2'; else QX[i]|=(q[j]-'0')<<j; } rep(j,k,n-1) QZ[i]+=Pow[j-k]*(q[j]-'0'); } int A=(1<<k)-1; rep(i,0,A) { rep(j,0,Pow[n-k]-1) { if(Low[j]>n) S[j]=val[(trans[j]<<k)|i]-'0'; else S[j]=S[j-Pow[Low[j]]]+S[j-2*Pow[Low[j]]]; } // 暴力預處理字首和 rep(j,1,m) if((QX[j]&A)==(i&~QY[j])) Ans[j]+=S[QZ[j]]; } rep(i,1,m) printf("%d\n",Ans[i]); }
Algorithm 3 : 高位字首和+容斥
起始學過高位字首和/FMT的看到這個題第一反應可能都是這個。。
-> 對於\(?\)的位置,直接賦值成1,然後對於這個數從高位字首和裡查詢
然後你發現不知道怎麼對於1的把0的去掉
顯然這個可以通過一個暴力的容斥來完成,列舉一些1的位置變成0,然後就是容斥的奇數減偶數加
複雜度為\(O(Q\cdot 2^{1的個數})\)
同理,處理高位字尾和,複雜度為\(O(Q\cdot 2^{0的個數})\)
而直接暴力列舉\(?\)變成0/1,複雜度為\(O(Q\cdot 2^{?的個數})\)
綜合這三種演算法,選一個更優的做,就得到一個複雜度為
$ O(Q\cdot 2^{\begin{aligned}\min\lbrace 1的個數,0的個數,?的個數\rbrace\end{aligned}})$
顯然查詢複雜度就是\(O(Q\cdot 2^{\lfloor \frac{n}{3}\rfloor }=Q\cdot 2^6)\)
算上預處理,複雜度為\(O(2^nn+Q\cdot 2^6)\)
#include<bits/stdc++.h>
using namespace std;
enum{N=1<<20};
int n,k,m,A[N],B[N],C[N],P[N];
// A 高位字首和
// B 高位字尾和
// C 點值(打擾了)
// P __builtin_parity
char val[N],q[21];
int main() {
freopen("d.in","r",stdin),freopen("d.out","w",stdout),scanf("%d%d%s",&n,&m,val),k=1<<n;
for(int i=0;i<k;++i) A[i]=B[i]=C[i]=val[i]-'0',P[i]=P[i>>1]^(i&1);
for(int i=1;i<k;i<<=1) for(int l=0;l<k;l+=i*2) for(int j=l;j<l+i;++j) A[j+i]+=A[j],B[j]+=B[j+i];
//預處理高位字首和,高位字尾和
while(m--) {
int x=0,y=0,a=0,b=0,ans=0;
for(int i=scanf("%s",q+1);i<=n;++i) {
if(q[i]=='?') x|=1<<(n-i),a++;
if(q[i]=='1') y|=1<<(n-i),b++;
}
if(a<=n/3) for(int S=x;~S;S=S?(S-1)&x:-1) ans+=C[y|S]; // 列舉?變成0/1
else if(b<=n/3) for(int S=y;~S;S=S?(S-1)&y:-1) ans+=P[S^y]?-A[S|x]:A[S|x]; // 對於高位字首和容斥
else for(int S=x=(k-1)^x^y;~S;S=S?(S-1)&x:-1) ans+=P[S]?-B[S|y]:B[S|y]; // 對於高位字尾和容斥
printf("%d\n",ans);
}
}