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; structnode { 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; }