BZOJ2728 HNOI2012与非(并查集+数位dp)
容易发现x nand x=not x。并且使用这个性质有x and y=not(x nand y)=(x nand y)nand(x nand y)。也就是说nand运算可以作为not和and运算使用。并且显然not和and运算可以表示nand运算,那么两者等价。事实上这就可以表示所有位运算了。
那么考虑位运算有什么事干不了。注意到如果每个数的第i位都和第j位相同, 那么无论怎么操作这两位都是相同的。大胆猜想这也是充分的,即除了这件事其他都能干。
这样位就被分成了很多类,每一类的取值要求相同。类似数位dp搞一发考虑第一个未达限制的是哪一位就行了。
各种细节,调到吐血,拿过7种分数,连过了都不知道是不是数据水了,没救。
#include<iostream> #include<cstdio> #include<cmath> #include<cstdlib> #include<cstring> #include<algorithm> using namespace std; #define N 1010 #define K 60 #define ll long long ll read() { ll x=0,f=1;char c=getchar(); while (c<'0'||c>'9') {if (c=='-') f=-1;c=getchar();} while (c>='0'&&c<='9') x=(x<<1)+(x<<3)+(c^48),c=getchar(); return x*f; } int n,k,d[K][K],fa[K]; bool flag[K]; ll l,r,num[K]; int find(int x){return fa[x]==x?x:fa[x]=find(fa[x]);} ll calc(ll n) { ll s=0,m=0,cnt=0; for (int i=0;i<k;i++) if (find(i)==i) cnt++; memset(flag,0,sizeof(flag)); for (int i=k-1;~i;i--) { if (find(i)==i) cnt--; if ((n&(1ll<<i))&&!flag[find(i)]) { s+=1ll<<cnt; flag[find(i)]=1; if ((m|=num[find(i)])>n) break; } } for (int i=0;i<k;i++) for (int j=0;j<k;j++) if (find(i)==find(j)&&((n&(1ll<<i))>0)!=((n&(1ll<<j))>0)) return s; return s+1; } int main() { #ifndef ONLINE_JUDGE freopen("bzoj2728.in","r",stdin); freopen("bzoj2728.out","w",stdout); const char LL[]="%I64d\n"; #else const char LL[]="%lld\n"; #endif n=read(),k=read(),l=read(),r=read(); if (l>=(1ll<<k)) {cout<<0;return 0;} r=min(r,(1ll<<k)-1); for (int i=0;i<k;i++) for (int j=0;j<k;j++) d[i][j]=1; for (int i=1;i<=n;i++) { ll x=read(); for (int p=0;p<k;p++) for (int q=0;q<k;q++) if (((x&(1ll<<p))>0)!=((x&(1ll<<q))>0)) d[p][q]=0; } for (int i=0;i<k;i++) fa[i]=i; for (int i=0;i<k;i++) for (int j=0;j<k;j++) if (d[i][j]) fa[find(i)]=find(j); for (int i=0;i<k;i++) num[find(i)]|=1ll<<i; cout<<calc(r)-(l?calc(l-1):0); return 0; }