【比赛】百度之星2017 初赛Round A
第一题
题意:给定多组数据P,每次询问P进制下,有多少数字B满足条件:只要数位之和是B的倍数,该数字就是B的倍数。
题解:此题是参考10进制下3和9倍数的特殊性质。
对于10进制,ab=10*a+b=9*a+(a+b),所以9的约数都有此性质。
对于P进制,ab=p*a+b=(p-1)a+(a+b),所以p-1的约数都有此性质。
对于P,计算P-1的约数个数即为答案。
★第二题
题意:给定L的关系(L<=10^5),关系表示为xi=xj或xi≠xj,将数据按顺序分为若干组(相邻在同一组),每组不满足所有条件,去掉最后一个条件后满足所有条件,求组数。
题解:
一种方法是每次倍增一组个数,对于组内跑bfs或者并查集,最后对组内不等关系判互恰,倍增结合二分。
正解O(n log n)。
相等关系具有传递性,可以用并查集在线维护。
不等关系不具有传递性,用平衡树维护每个点的不等关系。
对于相等关系,若在同一集合跳过,若在对方平衡树内则清空退出,否则合并双方集合。
合并使用启发式合并,就是大的并入小的,并修改对应不等关系平衡树处的表示。
对于不等关系,若在同一集合则清空退出,否则加入对方平衡树。
清空退出:用到的点加入栈,清空时只清栈内平衡树和栈内父亲,保证复杂度。(vis时间戳也可以优化常数)
记得开双倍数组。
#include<cstdio> #include<cstring> #include<set> #include<stack> #include<algorithm> using namespace std; const int maxn=200010; int ans,anss[maxn],pre,n,fa[maxn]; stack<int>s; set<int>q[maxn]; set<int>::iterator it; void getans(int x){ anss[++ans]=x-pre;pre=x; while(!s.empty()){ q[s.top()].clear(); fa[s.top()]=s.top(); s.pop(); } } int find(int x){return x==fa[x]?x:fa[x]=find(fa[x]);} int main(){ scanf("%d",&n); int u,v,w; pre=0; for(int i=1;i<=n;i++)fa[i]=i;//初始化 for(int i=1;i<=n;i++){ scanf("%d%d%d",&u,&v,&w); s.push(u);s.push(v); u=find(u);v=find(v); if(w==1){ if(u==v)continue; if(q[u].find(v)!=q[u].end())getans(i); else{ if(q[u].size()>q[v].size())swap(u,v); for(it=q[u].begin();it!=q[u].end();it++){ q[*it].erase(u); q[*it].insert(v); q[v].insert(*it); } fa[u]=v; } }else{ if(u==v)getans(i); else{ q[u].insert(v); q[v].insert(u); } } } printf("%d\n",ans); for(int i=1;i<=ans;i++)printf("%d\n",anss[i]); return 0; }
另一种解法:如果只有一组,显然全局二分后找最长长度。
因为答案就有明显的递进性质,所以考虑使用倍增来扩展,失败后在使用区间二分,保证复杂度。
复杂度……是啥?代码未AC。
#include<cstdio> #include<cstring> #include<stack> #include<algorithm> using namespace std; const int maxn=200010; int ans,anss[maxn],pre,n,fa[maxn]; int a[maxn],b[maxn],c[maxn]; int find(int x){return x==fa[x]?x:fa[x]=find(fa[x]);} bool found(int l,int r){ bool ok=1; for(int i=l;i<=r;i++)if(c[i]&&find(a[i])!=find(b[i]))fa[fa[a[i]]]=fa[b[i]]; for(int i=l;i<=r;i++)if((!c[i])&&find(a[i])==find(b[i])){ok=0;break;} for(int i=l;i<=r;i++)fa[i]=i; return ok; } int half(int x,int l,int r){ int mid; while(l<r){ mid=(l+r)>>1; if(found(x,mid))l=mid+1; else r=mid; } return l; } int main(){ scanf("%d",&n); int now=1; for(int i=1;i<=n;i++)fa[i]=i;//初始化 for(int i=1;i<=n;i++)scanf("%d%d%d",&a[i],&b[i],&c[i]); while(now<=n){ int mx=0; while(now+(1<<mx)<=n&&found(now,now+(1<<mx)))mx++; if(mx==0){ans++;now++;anss[ans]=now;} else{ now=half(now,now+(1<<(mx-1)),min(n,now+(1<<mx))); ans++;anss[ans]=now; now++; } } printf("%d\n",ans); for(int i=1;i<=ans;i++)printf("%d\n",anss[i]-anss[i-1]); return 0; }
★第三题
题意:给定n个点的树,m条路径(u到v)构成序列,q次询问序列L到R路径的交集。
题解:套路化地化树上询问为链上询问,处理一堆细节。
http://paste.ubuntu.com/25312404/
#include <cstdio> #include <algorithm> #include <cstring> #include <set> #define rep(i, l, r) for(int i=l; i<=r; i++) #define dow(i, l, r) for(int i=l; i>=r; i--) #define clr(x, c) memset(x, c, sizeof(x)) #define travel(x) for(edge *p=fir[x]; p; p=p->n) #define all(x) (x).begin,(x).end #define pb push_back #define fi first #define se second #define l(x) Left[x] #define r(x) Right[x] #define lowbit(x) (x&-x) using namespace std; typedef long long ll; typedef pair<int,int> Pii; typedef long double ld; typedef unsigned long long ull; inline int read() { int x=0; bool f=0; char ch=getchar(); while (ch<'0' || '9'<ch) f|=ch=='-', ch=getchar(); while ('0'<=ch && ch<='9') x=x*10+ch-'0', ch=getchar(); return f?-x:x; } #define maxn 500009 #define maxc 1000009 int n, m; struct edge{int y,z; edge *n;} e[maxn*2], *fir[maxn], *pt=e; void AddE(int x, int y, int z) { pt->y=y, pt->z=z, pt->n=fir[x], fir[x]=pt++; pt->y=x, pt->z=z, pt->n=fir[y], fir[y]=pt++; } int h[21][maxn], dep[maxn]; ll d[maxn]; inline int lca(int a, int b) { if (dep[a]<dep[b]) swap(a,b); for(int i=0, tmp=dep[a]-dep[b]; tmp; tmp>>=1,i++) if (tmp&1) a=h[i][a]; if (a==b) return a; dow(i,20,0) if (h[i][a] && h[i][a]!=h[i][b]) a=h[i][a], b=h[i][b]; return h[0][a]; } int cn, rt, Left[maxc], Right[maxc]; Pii g[maxc]; inline Pii Union(Pii p0, Pii p1) { if (p0==Pii(1,1) || p1==Pii(1,1)) return Pii(1,1); int x1=p0.fi, y1=p0.se; int x2=p1.fi, y2=p1.se, a, b, c, d; if ((a=lca(x1,y1))==(b=lca(x2,y2))) { if (lca(x1,x2)==lca(x1,y1)) swap(x2,y2); Pii tmp=Pii(lca(x1,x2),lca(y1,y2)); if (tmp.se==tmp.fi) return Pii(1,1); else return tmp; } else { if (dep[a]<dep[b]) swap(x1,x2), swap(y1,y2), swap(a,b); c=lca(x1,x2), d=lca(y1,x2); if ((c==a) || (d==a)) return Pii(c,d); c=lca(x1,y2), d=lca(y1,y2); if ((c==a) || (d==a)) return Pii(c,d); else return Pii(1,1); } return Pii(1,1); } void Build(int l, int r, int &t) { t=++cn; if (l==r) {g[t]=Pii(read(),read()); return;} int mid=(l+r)>>1; Build(l,mid,l(t)); Build(mid+1,r,r(t)); g[t]=Union(g[l(t)],g[r(t)]); } int L, R; Pii G; void Que(int l, int r, int &t) { if (L<=l && r<=R) { if (G.fi==0) G=g[t]; else G=Union(G,g[t]); return; } int mid=(l+r)>>1; if (L<=mid) Que(l,mid,l(t)); if (mid<R) Que(mid+1,r,r(t)); } int q[maxn], tot; int main() { n=read(); rep(i, 1, n-1) {int x=read(), y=read(), z=read(); AddE(x,y,z);} q[tot=1]=1; while (tot) { int x=q[tot--]; for(int i=1;h[i-1][h[i-1][x]];i++) h[i][x]=h[i-1][h[i-1][x]]; travel(x) if (p->y!=h[0][x]) q[++tot]=p->y, h[0][p->y]=x, d[p->y]=d[x]+p->z, dep[p->y]=dep[x]+1; } Build(1,m=read(),rt); int Q=read(); while (Q--) { L=read(), R=read(); G=Pii(0,0); Que(1,m,rt); printf("%I64d\n", d[G.fi]+d[G.se]-d[lca(G.fi,G.se)]*2); } return 0; }
第四题
第五题
题意:多组数据,给定年月日,求下一次同月同日为同星期几的年份。
题解:365%7=1,过一年星期加一天。
对于2.29前的日子,闰年在本年多+1。对于2.29后的日子,闰年在本年多+1。
对于2.29,四年四年跳特殊处理,还要处理跳到的年份是否闰年。
第六题
题意:给定一个n*m的01矩阵。
图像0的定义:存在1字符且1字符只能是由一个连通块组成,存在且仅存在一个由0字符组成的连通块完全被1所包围。
图像1的定义:存在1字符且1字符只能是由一个连通块组成,不存在任何0字符组成的连通块被1所完全包围。
连通的含义是,只要连续两个方块有公共边,就看做是连通。
完全包围的意思是,该连通块不与边界相接触。
题解:
要用巧妙的方法判断,否则会极其复杂。
在外圈加0后,从外圈到内圈消除,转化为:
图像0:一圈0连通块,一圈1联通块,一圈0联通块。
图像1:一圈0连通块,一圈1联通块。
注意每圈只能有1个联通块。