[BZOJ5285][HNOI2018]寻宝游戏
先贴一段出题人的话:
当时出题的时候很紧张,是真的紧张,因为觉得实在是太水了。
...
我当时很怕结果出来十几个AC,全场70。然后就再也没人找我出题了。
当时就不停的想HNOI有什么水题,是不是比我这个更水来安慰自己。
首先考虑DP,发现如果按照每位依次DP的话不满足无后效性,所以我们从后往前做。
相当于BFS,列出一个真值表,把8种情况都考虑进去即可。
这样我们就可以得到。。10分了。
https://www.cnblogs.com/lzdhydzzh/p/8850109.html
发现如果全都不强制或者有一位存在不合法情况,都可以直接判掉并剪枝,这样每层最多状态数最多为2。
手写bitset压64位跑一下可以过。$O(\frac{nmq}{64})$
有点麻烦,看注释吧。
1 #include<cstdio> 2 #include<cstring> 3 #include<algorithm> 4 #include<iostream> 5 #define rep(i,l,r) for (int i=l; i<=r; i++) 6 using namespace std; 7 8 typedef unsigned long long ull; 9 const int N=1010,mod=1e9+7; 10 int bl,n,m,Q,res,dt,cur,tot; 11 ull ans,mi[N]; 12 13 struct bitset{ 14 ull p[80]; 15 void clear(){ rep(i,0,bl) p[i]=0; } 16 void read(){ 17 memset(p,0,sizeof(p)); char ch=getchar(); 18 while (!isdigit(ch)) ch=getchar(); 19 for (int i=0; i<m; i++,ch=getchar()) p[i>>6]|=(((ull)(ch-'0'))<<(i&63));//压64位 20 } 21 }s[N],f[N],nt,t1,t2,v1,v2,g,c1,c2,que[2][4]; 22 23 void init(){ 24 bl=(m-1)>>6; mi[0]=1; 25 rep(i,1,n) mi[i]=(mi[i-1]<<1)%mod; 26 memset(nt.p,0,sizeof(nt.p)); 27 for (int i=0; i<m; i++) nt.p[i>>6]|=(1llu<<(i&63)); 28 rep(i,1,n){ 29 s[i].read(); 30 rep(j,0,bl) f[i].p[j]=s[i].p[j]^nt.p[j]; 31 } 32 } 33 34 int main(){ 35 freopen("bzoj5285.in","r",stdin); 36 freopen("bzoj5285.out","w",stdout); 37 scanf("%d%d%d",&n,&m,&Q); init(); 38 while (Q--){ 39 g.read(); dt=ans=cur=0; que[cur][++dt].clear();//0表示必须和询问串一样,1表示不强制 40 for (int i=n; i; i--){ 41 tot=dt; cur^=1; dt=0; 42 rep(j,1,tot){ 43 bool f1=1,f2=1,l1=1,l2=1; 44 rep(k,0,bl){ 45 if (!f1 && !f2) break; 46 if (que[cur^1][j].p[k]==nt.p[k]){//如果都不强制的话就不需要具体考虑了 47 if (f1) t1.p[k]=nt.p[k]; 48 if (f2) t2.p[k]=nt.p[k]; 49 continue; 50 } 51 //v1为或,v2为且 52 if (f1) v1.p[k]=s[i].p[k]&(s[i].p[k]^g.p[k]);//询问串这一位为0,当前串这一位为1 53 if (f2) v2.p[k]=g.p[k]&(s[i].p[k]^g.p[k]);//询问串这一位为1,当前串这一位为0 54 if (f1 && (v1.p[k]&que[cur^1][j].p[k])!=v1.p[k]) f1=0;//判掉-1 55 if (f2 && (v2.p[k]&que[cur^1][j].p[k])!=v2.p[k]) f2=0;//判掉-1 56 if (f1) t1.p[k]=que[cur^1][j].p[k]|(s[i].p[k]^v1.p[k]);//生成新的要求,如果本来就不强制则必定不强制,具体根据真值表 57 if (f2) t2.p[k]=que[cur^1][j].p[k]|(f[i].p[k]^v2.p[k]);//生成新的要求,如果本来就不强制则必定不强制,具体根据真值表 58 if (f1 && t1.p[k]!=nt.p[k]) l1=0;//判断是否全部不强制 59 if (f2 && t2.p[k]!=nt.p[k]) l2=0;//判断是否全部不强制 60 } 61 if (f1){ if (l1) ans=(ans+mi[i-1])%mod; else que[cur][++dt]=t1; } 62 if (f2){ if (l2) ans=(ans+mi[i-1])%mod; else que[cur][++dt]=t2; } 63 } 64 } 65 rep(j,1,dt){ 66 bool f1=1; 67 rep(k,0,bl) if ((que[cur][j].p[k]^nt.p[k])&g.p[k]){ f1=0; break; }//最后对 与 判断一遍是否合法 68 ans+=f1; 69 } 70 printf("%llu\n",ans); 71 } 72 return 0; 73 }
上面好像是出题人并不喜欢的暴力解法,很不优美,我们把它扔进垃圾桶。
正解是把或看成1,与看成0,通过总结规律发现题目可以变成比大小问题。
https://kelin.blog.luogu.org/solution-p4424
非常神的想法,代码也很简单,将操作数排序然后看哪个区间是合法的即可。
1 #include<cstdio> 2 #include<algorithm> 3 #define rg register int 4 #define rep(i,l,r) for (int i=(l); i<=(r); i++) 5 using namespace std; 6 7 const int N=5010,mod=1000000007; 8 int n,m,q,c[2],a[N],b[N],s[N],t[N],mi[N]; 9 char p[N]; 10 11 int up(int x,int y){ x+=y; return (x>=mod)?x-mod:x; } 12 int dn(int x,int y){ x-=y; return (x<0)?x+mod:x; } 13 14 int main(){ 15 freopen("bzoj5285.in","r",stdin); 16 freopen("bzoj5285.out","w",stdout); 17 scanf("%d%d%d",&n,&m,&q); 18 mi[1]=1; rep(i,2,n+1) mi[i]=(mi[i-1]<<1)%mod; 19 rep(i,1,m) a[i]=i; 20 rep(i,1,n){ 21 scanf("%s",p+1); c[0]=0; c[1]=m; 22 rep(j,1,m) if (p[j]=='1') s[j]=up(s[j],mi[i]); else c[0]++; 23 for (int j=m; j; j--) b[c[p[a[j]]-48]--]=a[j]; 24 swap(a,b); 25 } 26 rep(i,1,m) t[i]=s[a[i]]; t[m+1]=mi[n+1]; 27 while (q--){ 28 scanf("%s",p+1); int x=m+1,y=0; 29 for (int i=m; i; i--) if (p[a[i]]=='0') { y=i; break; } 30 rep(i,1,m) if (p[a[i]]=='1') { x=i; break; } 31 printf("%d\n",(x<y)?0:dn(t[x],t[y])); 32 } 33 return 0; 34 }