10月8日考试 题解 (贪心+模拟+树链剖分+搜索)
T1 小Z搭积木
题目大意:小Z有$n$块积木。每个积木上面最多搭$a_i$块积木,积木可以摆很多列。问最少的列数。$n\leq 5000$
先把$a$排序,然后从上往下搭积木,看哪个积木没被用且$a$尽可能小。时间复杂度$O(n^2)$。
代码:
#include<iostream> #include<algorithm> #include<cstdio> using namespace std; int n,a[5005],v[5005],ans,m; int main(){ cin>>n; for (int i=1;i<=n;i++) scanf("%d",&a[i]); sort(a+1,a+n+1); for (int i=1;i<=n;i++) if(!v[i]){ ans++; m=1; for (int j=i+1;j<=n;j++) if (!v[j]&&a[j]>=m){ v[j]=1; m++; } } cout<<ans; return 0; }
T2 动态仙人掌
题目大意:数轴上有$n$个位置在$p_i$,高度为$h_i$的障碍。人可以向与正方向夹角45度的方向起跳,沿着与负方向夹角45度降落。一个障碍被越过当人在此位置时的高度大于等于障碍的高度。人可以在地面任意时刻起跳,在空中任意时刻降落,但是不能在空中起跳。问越过所有障碍最少跳多高。$n\leq 3 \times 10^5$
一开始想假了,距离正解只差一步之遥……
显然对于每个障碍都有一个最晚的起跳点和最早的降落点。我们称其为一段区间。当两个区间有交集时,这段是不能降落的(因为再跳起达不到后一个障碍的高度)。所以我们不妨考虑区间合并,以$l$为关键字排序。当当前区间的$l$严格小于前面区间的$r$时合并区间,否则更新答案。
时间复杂度$O(n\log n)$。
代码:
#include<cstdio> #include<iostream> #include<algorithm> using namespace std; const int N=300005; int n; struct node { int l,r; }a[N]; inline int read() { int x=0,f=1;char ch=getchar(); while(!isdigit(ch)){if (ch=='-') f=-1;ch=getchar();} while(isdigit(ch)){x=x*10+ch-'0';ch=getchar();} return x*f; } bool cmp(node x,node y) { return x.l<y.l; } int main() { n=read(); for (int i=1;i<=n;i++) { int p=read(),h=read(); a[i].l=p-h,a[i].r=p+h; } sort(a+1,a+n+1,cmp); if (a[1].l<0){ cout<<-1; return 0; } int nxt=a[1].r,pre=a[1].l; double ans=0; for (int i=2;i<=n;i++) { if (a[i].l<nxt){ nxt=max(nxt,a[i].r); }else{ ans=max(ans,(nxt-pre)*1.0/2); pre=a[i].l,nxt=a[i].r; } } ans=max(ans,(nxt-pre)*1.0/2); printf("%.1lf",ans); return 0; }
T3 相交
题目大意:有一个含有$n$个结点的树,$q$次询问。每次询问形如$(a,b,c,d)$,表示给$(a,b)$路径所有点打上标记,询问$(c,d)$路径上有没有被标记的点。询问完后$(a,b)$路径上的标记会消失。$n,q\leq 10^5$
傻逼题。直接上树剖就行了。
代码:
#include<cstdio> #include<iostream> using namespace std; const int N=100005; int size[N],son[N],dep[N],fa[N],n,q; int dfn[N],top[N],tot; int head[N],cnt; int sum[N*4],lazy[N*4]; struct node { int next,to; }edge[N*2]; inline int read() { int x=0,f=1;char ch=getchar(); while(!isdigit(ch)){if (ch=='-') f=-1;ch=getchar();} while(isdigit(ch)){x=x*10+ch-'0';ch=getchar();} return x*f; } inline void add(int from,int to) { edge[++cnt]=(node){head[from],to}; head[from]=cnt; } inline void dfs_son(int now,int f) { size[now]=1; dep[now]=dep[f]+1;fa[now]=f; for (int i=head[now];i;i=edge[i].next) { int to=edge[i].to; if (to==f) continue; dfs_son(to,now); size[now]+=size[to]; if (size[to]>size[son[now]]) son[now]=to; } } inline void dfs_chain(int now,int topf) { dfn[now]=++tot; top[now]=topf; if (!son[now]) return; dfs_chain(son[now],topf); for (int i=head[now];i;i=edge[i].next) { int to=edge[i].to; if(dfn[to]) continue; dfs_chain(to,to); } } inline void pushdown(int index,int l,int r) { int mid=(l+r)>>1,k=lazy[index]; lazy[index]=0; sum[index*2]+=(mid-l+1)*k; sum[index*2+1]+=(r-mid)*k; lazy[index*2]+=k;lazy[index*2+1]+=k; } inline void update(int index,int l,int r,int ql,int qr,int k) { if (ql<=l&&r<=qr) { sum[index]+=(r-l+1)*k; lazy[index]+=k; return; } pushdown(index,l,r); int mid=(l+r)>>1; if(ql<=mid) update(index*2,l,mid,ql,qr,k); if(qr>mid) update(index*2+1,mid+1,r,ql,qr,k); sum[index]=sum[index*2]+sum[index*2+1]; } inline int query(int index,int l,int r,int ql,int qr) { if (ql<=l&&r<=qr) return sum[index]; pushdown(index,l,r); int mid=(l+r)>>1,res=0; if (ql<=mid) res+=query(index*2,l,mid,ql,qr); if (qr>mid) res+=query(index*2+1,mid+1,r,ql,qr); return res; } inline void updrange(int x,int y,int k) { while(top[x]!=top[y]) { if (dep[top[x]]<dep[top[y]]) swap(x,y); update(1,1,n,dfn[top[x]],dfn[x],k); x=fa[top[x]]; } if (dep[x]>dep[y]) swap(x,y); update(1,1,n,dfn[x],dfn[y],k); } inline int qrange(int x,int y) { int res=0; while(top[x]!=top[y]) { if (dep[top[x]]<dep[top[y]]) swap(x,y); res=(res+query(1,1,n,dfn[top[x]],dfn[x])); x=fa[top[x]]; } if (dep[x]>dep[y]) swap(x,y); res=(res+query(1,1,n,dfn[x],dfn[y])); return res; } int main() { n=read(); for (int i=1;i<n;i++) { int x=read(),y=read(); add(x,y);add(y,x); } dfs_son(1,0); dfs_chain(1,1); q=read(); while(q--) { int a=read(),b=read(),c=read(),d=read(); updrange(a,b,1); int tmp=qrange(c,d); if (tmp>0) printf("YES\n"); else printf("NO\n"); updrange(a,b,-1); } return 0; }
T4 聪明格
题目大意:给定一个$n \times n$的棋盘,现要求往里面填$1-n$的数,使得每一行每一列没有重复的数字。同时给定一个$n \times n$的图,相同的数字构成一个联通块,表示棋盘内对应连通块内数字的积要等于图中的数。问有几种方案,并输出字典序最小的方案数。
爆搜+剪枝最多70pts.正解的搜索策略很妙。
考虑到对于连通块的限制比对于行和列的限制要严格的多,所以我们不妨考虑这样一种策略:先把连通块填满,然后判断是否合法。接下来是剪枝:我们将连通块按照大小排序,如果大小相同按照因数个数排序(这样使选择的余地尽可能小),然后将每个连通块填满即可。
时间复杂度$O(metaphysics)$。
代码:
#include<cstdio> #include<iostream> #include<algorithm> #include<vector> using namespace std; const int N=11,M=505; const int dx[]={0,1,-1,0,0}; const int dy[]={0,0,0,1,-1}; struct Node{ int c[N][N]; }ans[M]; struct node{ int x,y; }; inline void debug(){ puts("fuck!"); } bool operator < (const Node x,const Node y) { for (int i=1;i<N;i++) for (int j=1;j<N;j++) if (x.c[i][j]!=y.c[i][j]) return x.c[i][j]<y.c[i][j]; return 0; } vector<node> v[M]; vector<int> p[M]; int a[N][N],l[N][N],h[N][N],vis[N][N],c[N][N],n,cnt,tot; inline int read() { int x=0,f=1;char ch=getchar(); while(!isdigit(ch)){if (ch=='-') f=-1;ch=getchar();} while(isdigit(ch)){x=x*10+ch-'0';ch=getchar();} return x*f; } inline int factor(int x) { int t=0; for (int i=1;i*i<=x;i++) t+=(int)(x%i==0); return t; } bool cmp(vector<node> x,vector<node> y) { if (x.size()==y.size()) return factor(x[0].x)<factor(y[0].x); return x.size()<y.size(); } inline void split(int t,int x) { for (int i=1;i<=n;i++) if (x%i==0) p[t].push_back(i); } inline bool judge(int x,int y) { if (x<1||x>n||y<1||y>n||vis[x][y]) return 0; return 1; } inline void dfs(int x,int y) { vis[x][y]=1; v[cnt].push_back((node){x,y}); for (int i=1;i<=4;i++) { int xx=x+dx[i],yy=y+dy[i]; if (judge(xx,yy)&&a[xx][yy]==a[x][y]) dfs(xx,yy); } } void work(int dep); inline void fill(int t,int dep,int s,int tot) { if (dep>tot) work(t+1); else { int x=v[t][dep].x,y=v[t][dep].y; for (int i=0;i<p[t].size();i++) { int u=p[t][i]; if ((s==u||dep<tot)&&s%u==0&&!h[x][u]&&!l[y][u]) { h[x][u]=l[y][u]=1; c[x][y]=u; fill(t,dep+1,s/u,tot); c[x][y]=0; h[x][u]=l[y][u]=0; } } } } inline void work(int dep) { if (dep>cnt){ tot++; for (int i=1;i<=n;i++) for (int j=1;j<=n;j++) ans[tot].c[i][j]=c[i][j]; } else{ int num=v[dep][0].x,tot=v[dep].size()-1; fill(dep,1,num,tot); } } int main() { n=read(); for (int i=1;i<=n;i++) for (int j=1;j<=n;j++) a[i][j]=read(); for (int i=1;i<=n;i++) for (int j=1;j<=n;j++) { if (vis[i][j]) continue; cnt++;v[cnt].push_back((node){a[i][j],0}); dfs(i,j); } sort(v+1,v+cnt+1,cmp); for (int i=1;i<=cnt;i++) split(i,v[i][0].x); work(1); printf("%d\n",tot); Node t=ans[1]; for (int i=2;i<=tot;i++) t=min(t,ans[i]); for (int i=1;i<=n;i++) { for (int j=1;j<=n;j++) printf("%d ",t.c[i][j]); printf("\n"); } return 0; }