2017北京国庆刷题Day6 afternoon
期望得分:100+100+40=240
实际得分:100+0+40=140
二进制拆分、二进制前缀和
#include<cstdio> #include<iostream> using namespace std; typedef long long LL; #define N 100001 int a[N],b[N],c[N]; const int mod=1e9+7; int suma[N][32],sumb[N][32]; 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(); } } int finda(int r,int wh) { long long tot=0; for(int i=0;i<=30;i++) if(wh&bit[i]) tot=(tot+1ll*(r-suma[r][i])*bit[i])%mod; else tot=(tot+1ll*suma[r][i]*bit[i])%mod; return int(tot%mod); } int findb(int r,int wh) { long long tot=0; for(int i=0;i<=30;i++) if(wh&bit[i]) tot=(tot+1ll*(r-sumb[r][i])*bit[i])%mod; else tot=(tot+1ll*sumb[r][i]*bit[i])%mod; return int(tot%mod); } int main() { freopen("xorarray.in","r",stdin); freopen("xorarray.out","w",stdout); int n; scanf("%d",&n); int len,x; for(int i=1;i<=n;i++) { read(a[i]); len=0; x=a[i]; while(x) suma[i][len++]=suma[i-1][len]+(x&1),x/=2; for(int j=len;j<=30;j++) suma[i][j]=suma[i-1][j]; } for(int i=1;i<=n;i++) { read(b[i]); len=0; x=b[i]; while(x) sumb[i][len++]=sumb[i-1][len]+(x&1),x/=2; for(int j=len;j<=30;j++) sumb[i][j]=sumb[i-1][j]; } bit[0]=1; for(int i=1;i<=30;i++) bit[i]=bit[i-1]<<1; c[1]=a[1]^b[1]; for(int k=2;k<=n;k++) { c[k]=c[k-1]; c[k]=(c[k]+finda(k-1,b[k]))%mod; c[k]=(c[k]+findb(k,a[k]))%mod; } for(int i=1;i<=n;i++) printf("%d ",c[i]); }
60分做法:
先做一遍最小生成树
枚举两个点i,j,那么替换掉的是最小生成树上i,j路径上权值最大的边
倍增维护
时间复杂度:O(n*n*logn)
100分做法:
换一个角度,考虑被替换掉的边的贡献
边e要想被替换掉,那么点 i,j 要满足两个条件:
① 设e的两端点为u,v,i∈u,j∈v
② e的边权是连通 i,j 必经之路(最短路)上边权最大的边
怎么找这条边?——Kruscal算法
Kruscal算法每次找还没有加进去的权值最小的边,所以满足必经之路
还没有加进去的边权最小的边,是已加进去的边权最大的边,所以满足路径上边权最大
所以,设最小生成树的总权值为sum,设当前边权为w,当前边连接的两点的集合大小分别为 s1、s2
ans=(sum*n*(n-1)- 2*Σ s1*s2*w)/(n*(n-1))
n*(n-1):任意选两个点共有这些选法
Σ 前乘2:u,v 和 v,u 算不同的方案
#include<cstdio> #include<algorithm> #define N 20001 #define M 100001 using namespace std; typedef long long LL; int fa[N],siz[N]; struct node { int u,v,w; }e[M]; bool cmp(node p,node q) { return p.w<q.w; } int find(int i) { return fa[i]==i ? i : fa[i]=find(fa[i]); } int main() { freopen("detective.in","r",stdin); freopen("detective.out","w",stdout); int n,m; scanf("%d%d",&n,&m); for(int i=1;i<=m;i++) scanf("%d%d%d",&e[i].u,&e[i].v,&e[i].w); sort(e+1,e+m+1,cmp); for(int i=1;i<=n;i++) fa[i]=i,siz[i]=1; int u,v; LL sum=0,cnt=0; for(int i=1;i<=m;i++) { u=find(e[i].u); v=find(e[i].v); if(u==v) continue; sum+=e[i].w; cnt+=1ll*siz[u]*siz[v]*e[i].w; fa[v]=u; siz[u]+=siz[v]; } printf("%.2lf",sum-cnt*2.0/(n*(n-1))); }
考场上替换掉的不是 i,j 路径上权值最大的边,替换的是与i,j相连的边权最大的边,0分。
连接i,j 之后,要满足还是一颗树,去掉i,j之间的任意一条边即可
分治+DP+bitset
若点(x1,y1)能访问到点(x2,y2)
假设x1<=x2,y1<=y2
那么(x1,y1)往右下扩展,(x2,y2)往左上扩展,两个点一定在中间某一行相遇
所以可以利用分治的思想枚举相遇的那一行
如果x1和x2都在枚举的这一行的上面,那就扔到左边
如果x1和x2都在枚举的这一行的下面,那就扔到右边
左边和右边的分别继续分治下去
如果x1和x2一个在上面,一个在下面,那就判断这两个点能否在这一行相遇
能在这一行相遇,两个点一定能相遇
反之,一定不能相遇
如何判断 ?
假设当前枚举的相遇行为mid,
f[i][j][k]=0/1表示第i行第j列的点,向右下扩展,能否扩展到第mid行的第k列
g[i][j][k]=0/1表示第i行第j列的点,向左上扩展,能否扩展到第mid行的第k列
若 f[x1][y1][k] 和 g[i][j][k] 有一个k同时为true,那么两个点就可以相遇
第三维可以用bitset简化
f的转移:
预处理:f[mid][j][j]=第mid行第j列是否有障碍
倒叙枚举mid行之上的i,j,f[i][j]|=f[i][j+1],f[i][j]|=f[i+1][j]
g的转移类似,
正序枚举mid行之下的,加号改成减号即可
#include<cstdio> #include<vector> #include<bitset> #define N 501 #define M 1000001 using namespace std; int n,m; bool mp[N][N],ans[M]; char s[N]; bitset<N>f[N][N],g[N][N]; struct node { int sx,sy,tx,ty,id; }; node p; void solve(vector<node>v,int l,int r) { if(l>r) return; int mid=l+r>>1; for(int i=mid;i>=l;i--) for(int j=m;j;j--) { f[i][j]=0; if(!mp[i][j]) continue; if(i==mid) f[i][j].set(j); else f[i][j]|=f[i+1][j]; if(j!=m) f[i][j]|=f[i][j+1]; } for(int i=mid;i<=r;i++) for(int j=1;j<=m;j++) { g[i][j]=0; if(!mp[i][j]) continue; if(i==mid) g[i][j].set(j); else g[i][j]|=g[i-1][j]; if(j!=1) g[i][j]|=g[i][j-1]; } vector<node>vl,vr; for(vector<node>::iterator it=v.begin();it!=v.end();it++) { p=*it; if(p.tx<mid) vl.push_back(p); else if(p.sx>mid) vr.push_back(p); else ans[p.id]=(f[p.sx][p.sy]&g[p.tx][p.ty]).any(); } solve(vl,l,mid-1); solve(vr,mid+1,r); } int main() { freopen("boardgame.in","r",stdin); freopen("boardgame.out","w",stdout); scanf("%d%d",&n,&m); for(int i=1;i<=n;i++) { scanf("%s",s+1); for(int j=1;j<=m;j++) mp[i][j]= s[j]=='.' ? true : false; } int Q; scanf("%d",&Q); vector<node>v; for(int i=1;i<=Q;i++) { scanf("%d%d%d%d",&p.sx,&p.sy,&p.tx,&p.ty); p.id=i; v.push_back(p); } solve(v,1,n); for(int i=1;i<=Q;i++) ans[i] ? puts("Yes") : puts("No"); return 0; }
考场40分双向宽搜,然而跟普通的bfs一个样
#include<cstdio> #include<iostream> using namespace std; #define N 501 bool mp[N][N]; char s[N]; int n,m,rd[N][N][2]; int sx,sy,tx,ty; int vis[N][N]; int q[N*N],h,t,cnt,bl[N][N]; void read(int &x) { x=0; char c=getchar(); while(!isdigit(c)) c=getchar(); while(isdigit(c)) { x=x*10+c-'0'; c=getchar(); } } int turn(int i,int j) { return (i-1)*m+j; } bool bfs() { cnt++; h=0,t=1; q[++h]=turn(sx,sy); int now,x,y; vis[sx][sy]=cnt; vis[tx][ty]=cnt; q[++t]=turn(tx,ty); bl[sx][sy]=1; bl[tx][ty]=2; while(h<=t) { now=q[h++]; x=(now-1)/m+1; y=now-(x-1)*m; if(bl[x][y]==1) { if(mp[x+1][y]) { if(bl[x+1][y]==2 && vis[x+1][y]==cnt) return true; if(vis[x+1][y]!=cnt) vis[x+1][y]=cnt,q[++t]=turn(x+1,y),bl[x+1][y]=1; } if(mp[x][y+1]) { if(bl[x][y+1]==2 && vis[x][y+1]==cnt) return true; if(vis[x][y+1]!=cnt) vis[x][y+1]=cnt,q[++t]=turn(x,y+1),bl[x][y+1]=1; } } else { if(mp[x-1][y]) { if(bl[x-1][y]==1 && vis[x-1][y]==cnt) return true; if(vis[x-1][y]!=cnt) vis[x-1][y]=cnt,q[++t]=turn(x-1,y),bl[x-1][y]=2; } if(mp[x][y-1]) { if(bl[x][y-1]==1 && vis[x][y-1]==cnt) return true; if(vis[x][y-1]!=cnt) vis[x][y-1]=cnt,q[++t]=turn(x,y-1),bl[x][y-1]=2; } } } return false; } int main() { freopen("boardgame.in","r",stdin); freopen("boardgame.out","w",stdout); scanf("%d%d",&n,&m); for(int i=1;i<=n;i++) { scanf("%s",s+1); for(int j=1;j<=m;j++) mp[i][j]=s[j]=='.' ? true : false; } int q; scanf("%d",&q); while(q--) { read(sx); read(sy); read(tx); read(ty); if(!mp[sx][sy] || !mp[tx][ty]) { puts("No"); continue; } if(sx==tx && sy==ty) { puts("Yes"); continue; } if(tx<=sx && ty<=sy) { puts("No"); continue; } if(bfs()) puts("Yes"); else puts("No"); } }