2017 清北济南考前刷题Day 3 morning
实际得分:100+0+0=100
T1
右上角是必败态,然后推下去
发现同行全是必胜态或全是必败态,不同行必胜必败交叉
列同行
所以n,m 只要有一个是偶数,先手必胜
#include<cstdio> using namespace std; int main() { freopen("star.in","r",stdin); freopen("star.out","w",stdout); int n,m; while(scanf("%d%d",&n,&m)!=EOF) { if(!n) return 0; if(!(n&1) || !(m&1)) puts("Yuri"); else puts("Chito"); } }
T2
k=1 暴力:
可持久化trie树 求 异或最大值
#include<cstdio> #include<iostream> #include<algorithm> #define N 50001 const int mod=1e9+7; using namespace std; int bit[31]; int a[N]; int root[N],ch[N*31][2],cnt[N*31],tot; void read(int &x) { x=0; char c=getchar(); while(!isdigit(c)) c=getchar(); while(isdigit(c)) { x=x*10+c-'0'; c=getchar(); } } void insert(int pre,int &now ,int dep,int x) { if(!now) now=++tot; cnt[now]=cnt[pre]+1; if(dep<0) return; int p= (x&bit[dep])>0; ch[now][p^1]=ch[pre][p^1]; insert(ch[pre][p],ch[now][p],dep-1,x); } int query(int pre,int k,int dep,int x) { if(dep<0) return 0; int p= (x&bit[dep])>0; if(cnt[ch[k][p^1]]-cnt[ch[pre][p^1]]) return bit[dep]+query(ch[pre][p^1],ch[k][p^1],dep-1,x); return query(ch[pre][p],ch[k][p],dep-1,x); } int main() { freopen("war.in","r",stdin); freopen("war.out","w",stdout); bit[0]=1; for(int i=1;i<=30;++i) bit[i]=bit[i-1]<<1; int n,k; read(n); read(k); for(int i=1;i<=n;++i) { read(a[i]); insert(root[i-1],root[i],31,a[i]); } int ans=0; for(int i=1;i<=n;++i) ans=max(ans,query(root[0],root[n],31,a[i])); cout<<ans%mod; }
所有数不超过1023暴力:
预处理所有 i^j的结果,cnt[i]表示第i个数的个数
这样每一种 异或 值出现的次数=cnt[i]*cnt[j]
从大到小枚举,直至k个即可
#include<cstdio> #include<iostream> #include<algorithm> #define N 50001 const int mod=1e9+7; using namespace std; int cnt[1026]; struct node { int i,j,y; }e[1024*1024+5]; void read(int &x) { x=0; char c=getchar(); while(!isdigit(c)) c=getchar(); while(isdigit(c)) { x=x*10+c-'0'; c=getchar(); } } bool cmp(node p,node q) { return p.y>q.y; } int main() { freopen("war.in","r",stdin); freopen("war.out","w",stdout); int n,x; long long k; read(n); scanf("%I64d",&k); for(int i=1;i<=n;++i) read(x),cnt[x]++; int tot=0; for(int i=0;i<1024;++i) for(int j=i+1;j<1024;++j) e[++tot].i=i,e[tot].j=j,e[tot].y=i^j; sort(e+1,e+tot+1,cmp); int ans=0; for(int i=1;i<=tot;++i) { ans=(ans+min(k,1ll*cnt[e[i].i]*cnt[e[i].j])*e[i].y%mod)%mod; k-=min(k,1ll*cnt[e[i].i]*cnt[e[i].j]); if(!k) break; } cout<<ans; }
满分做法:
二分出一个最大的t,t满>t的数至少有k个
然后查询所有异或值 >t 的数的和,最后在减去 属于前k大的数的和
二分检验与查询均在trie树上进行
查询>t的数的个数:
枚举n个数a[i],累积 与每一个a[i] 异或后 >t 的个数
对于每一个a[i],设现在是第j位
如果t的第j位是0,那么累加第j位与a[i] 不同 的数的个数,trie树上的当前位置转到 第j位与a[i] 的第j位相同的位置
因为所以在第j位就分出大小的数都以累加,继续找第j位分不出大小的数
如果t的第j位是1,什么都不累加,trie树上的当前位置 转到 第j位与a[i] 不同的 位置
因为如果这一位与t的第j位相同,异或得0,一定<t,如果不同,第j位 分不出大小,继续往后走
累积所有>t的数的和:
还是枚举n个数a[i],累积 与每一个 a[i] 异或后>t 的数的和
累积方法思路与上面差不多
如果t的第j位是0,那么就累计 当前位置 所有的与a[i] 异或 为1的数的和,当前位置转到 与a[i]的第j位相同的位置
如果t的第j为是1,那么当前位置 转到与a[i]的第j位不同的位置
注意每一对合法的数会使用两次,所以累计个数 和 总和 时 注意 除以2
#include<cstdio> #include<iostream> using namespace std; #define N 50001 const int mod=1e9+7; const int inv=5e8+4; long long k; int n; int a[N],trie[N*31][2],all[N*31][31],tot=1; int cnt[N*31]; int bit[31]; void read(int &x) { x=0; char c=getchar(); while(!isdigit(c)) c=getchar(); while(isdigit(c)) { x=x*10+c-'0'; c=getchar(); } } void insert(int x) { int now=1,d; for(int i=30;i>=0;--i) { d=(x&bit[i])>0; if(!trie[now][d]) trie[now][d]=++tot; now=trie[now][d]; cnt[now]++; for(int j=30;j>=0;--j) if(x&bit[j]) all[now][j]++; } } int upper(int x,int t) { int pos=1,d,sum=0; for(int i=30;i>=0;--i) { d=(x&bit[i])>0; if(!(t&bit[i])) sum+=cnt[trie[pos][d^1]],pos=trie[pos][d]; else pos=trie[pos][d^1]; // if(t==754974719 && x==962029906) cout<<i<<' '<<sum<<' '<<trie[pos][d^1]<<'\n'; } return sum; } int query(int x,int t) { int pos=1,d,sum=0; for(int i=30;i>=0;--i) { d=(x&bit[i])>0; if(!(t&bit[i])) { for(int j=30;j>=0;--j) if(x&bit[j]) sum=(sum+1LL*(cnt[trie[pos][d^1]]-all[trie[pos][d^1]][j])*bit[j]%mod)%mod; else sum=(sum+1LL*all[trie[pos][d^1]][j]*bit[j]%mod)%mod; pos=trie[pos][d]; } else pos=trie[pos][d^1]; } //cout<<sum<<'\n'; return sum; } long long check(int x) { long long sum=0; for(int i=1;i<=n;i++) { sum+=upper(a[i],x); //if(x==754974719) cout<<i<<' '<<sum<<'\n'; } return sum/2; } int main() { freopen("war.in","r",stdin); freopen("war.out","w",stdout); read(n); scanf("%I64d",&k); bit[0]=1; for(int i=1;i<=30;++i) bit[i]=bit[i-1]<<1; for(int i=1;i<=n;++i) { read(a[i]); insert(a[i]); // cout<<i<<' '<<tot<<'\n'; } int l=0,r=2147483647,mid,tmp=-1; while(l<=r) { mid=l+r>>1; if(check(mid)>=k) l=mid+1,tmp=mid; else r=mid-1; // cout<<mid<<' '<<check(mid)<<'\n'; } int ans=0; if(tmp!=-1) { long long res=k-check(tmp); for(int i=1;i<=n;i++) { ans+=query(a[i],tmp),ans%=mod; // cout<<ans<<'\n'; } ans=1LL*ans*inv%mod; ans=(ans+1LL*res*(tmp+1)%mod)%mod; } else { for(int i=1;i<=n;i++) ans+=query(a[i],0),ans%=mod; ans=1LL*ans*inv%mod; } cout<<ans; //for(int i=1;i<=tot;i++) cout<<i<<' '<<cnt[i]<<'\n'; }
T3
因为k<=10 所以线段树维护区间前10大
合并的时候,用了类似于归并排序时用的 两个指针,扫一遍即可
#include<cstdio> #include<cstring> #include<iostream> using namespace std; #define N 100001 int f[N<<2]; struct node { int mx[11]; node() { memset(mx,0,sizeof(mx));} node operator + (node p) { node t; int a=1,b=1,c=1; while(c<=10) t.mx[c++]=mx[a]>p.mx[b] ? mx[a++] : p.mx[b++]; return t; } }tr[N<<2]; void read(int &x) { x=0; char c=getchar(); while(!isdigit(c)) c=getchar(); while(isdigit(c)) { x=x*10+c-'0'; c=getchar(); } } void build(int k,int l,int r) { if(l==r) { read(tr[k].mx[1]); return; } int mid=l+r>>1; build(k<<1,l,mid); build(k<<1|1,mid+1,r); tr[k]=tr[k<<1]+tr[k<<1|1]; } void down(int k) { f[k<<1]+=f[k]; f[k<<1|1]+=f[k]; for(int i=1;i<=10;++i) { if(tr[k<<1].mx[i]) tr[k<<1].mx[i]+=f[k]; if(tr[k<<1|1].mx[i]) tr[k<<1|1].mx[i]+=f[k]; } f[k]=0; } node query(int k,int l,int r,int opl,int opr) { if(l>=opl && r<=opr) return tr[k]; if(f[k]) down(k); int mid=l+r>>1; if(opr<=mid) return query(k<<1,l,mid,opl,opr); if(opl>mid) return query(k<<1|1,mid+1,r,opl,opr); return query(k<<1,l,mid,opl,opr)+query(k<<1|1,mid+1,r,opl,opr); } void change(int k,int l,int r,int opl,int opr,int w) { if(l>=opl && r<=opr) { for(int i=1;i<=10;i++) if(tr[k].mx[i]) tr[k].mx[i]+=w; f[k]+=w; return; } if(f[k]) down(k); int mid=l+r>>1; if(opl<=mid) change(k<<1,l,mid,opl,opr,w); if(opr>mid) change(k<<1|1,mid+1,r,opl,opr,w); tr[k]=tr[k<<1]+tr[k<<1|1]; } int main() { freopen("noname.in","r",stdin); freopen("noname.out","w",stdout); int n,m; read(n); read(m); build(1,1,n); int op,l,r,w; while(m--) { read(op); read(l); read(r); read(w); if(!op) { if(r-l+1<w) cout<<-1<<'\n'; else cout<<query(1,1,n,l,r).mx[w]<<'\n'; } else change(1,1,n,l,r,w); } }
错误:
将前10大全部放到一个结构体里,query时直接返回结构体
合并的时候 重载的 加号 运算符
所以 标记 不能放到 结构体里
下传标记的时候,只传前10大,但应先判断是否具有第i大