A思路:排个序建立最短路树即可,可以双指针实现。
考察:贪心,构造。
#include<bits/stdc++.h> #define rep(i,a,b) for(int i=a;i<=b;i++) using namespace std; const int maxn=2000010; struct in{ int x,id; bool friend operator <(in w,in v){ if(w.x==v.x) return w.id<v.id; return w.x<v.x; } }s[maxn]; int a[maxn],b[maxn],c[maxn],tot; int q[maxn],head,tail; int main() { int N,M; scanf("%d%d",&N,&M); rep(i,1,N) scanf("%d",&s[i].x),s[i].id=i; sort(s+1,s+N+1); q[++head]=1; rep(i,2,N) { while(head>tail&&s[i].x-s[q[tail+1]].x>M) tail++; if(head>tail){ a[i]=s[q[tail+1]].id; b[i]=s[i].id; c[i]=s[i].x-s[q[tail+1]].x; } q[++head]=i; if(c[i]==0) { puts("-1"); return 0; } } printf("%d\n",N-1); rep(i,2,N) printf("%d %d %d\n",a[i],b[i],c[i]); return 0; }
B:显然最大值取决于a[],出现的最高位不同的那一位,假设有最高位的分到A组,不然分到B组,分组后,各取一个x,y,那么ans=min(x^y),这个可以字典树实现。
比较常规。 但是比赛的时候我以为字典树空间不够只能得80分。所以写了分治, 分治复杂度也是O(60*N)的,而且感觉比字典树保险,实现就是每一层,继续分组。
考察:贪心,字典树求最小异或。
#include<bits/stdc++.h> #define ll long long #define rep(i,a,b) for(int i=a;i<=b;i++) using namespace std; const int maxn=2000010; ll a[maxn],b[maxn],c[maxn],ans=0; void solve(int p,int L1,int R1,int L2,int R2,ll sum) {if(p==-1){ ans=min(ans,sum); return ; } int t1=0,t2=0; for(int i=L1;i<=R1;i++){ if(a[i]&(1LL<<p)) b[++t1]=a[i]; else c[++t2]=a[i]; } rep(i,1,t1) a[L1+i-1]=b[i]; rep(i,1,t2) a[L1+t1+i-1]=c[i]; int t3=0,t4=0; for(int i=L2;i<=R2;i++){ if(a[i]&(1LL<<p)) b[++t3]=a[i]; else c[++t4]=a[i]; } rep(i,1,t3) a[L2+i-1]=b[i]; rep(i,1,t4) a[L2+t3+i-1]=c[i]; int F=0; if(t1&&t3) F=1,solve(p-1,L1,L1+t1-1,L2,L2+t3-1,sum); if(t2&&t4) F=1,solve(p-1,L1+t1,R1,L2+t3,R2,sum); if(F) return ; solve(p-1,L1,R1,L2,R2,sum+(1LL<<p)); } int main() { int N; scanf("%d",&N); rep(i,1,N) scanf("%lld",&a[i]); int p=-1; for(int i=60;i>=0;i--){ int tot=0; rep(j,1,N) if(a[j]&(1LL<<i)) tot++; if(tot!=0&&tot!=N) { p=i; break; } } if(p!=-1){ int t1=0,t2=0; ans=1LL<<61; rep(i,1,N) { if(a[i]&(1LL<<p)) b[++t1]=a[i]; else c[++t2]=a[i]; } rep(i,1,t1) a[i]=b[i]; rep(i,1,t2) a[t1+i]=c[i]; solve(p-1,1,t1,t1+1,N,1LL<<p); } printf("%lld\n",ans); return 0; }
C:求最小字典序下的公共路径长度,由于N比较小,显然就是枚举每个点为根,然后最小字典序的 Σ最短路树LCA到根的距离,预料到码量比较长,就写了个floyd骗分,LCA也直接写的倍增(估计写tarjan会快个10倍),60分水过。 字典序那里可以用拓扑排序实现。
考察:最短路,LCA,保证字典序。
#include<bits/stdc++.h> #define pii pair<int,int> #define mp make_pair #define rep(i,a,b) for(int i=a;i<=b;i++) using namespace std; const int inf=1e9; const int maxn=2001; int dis[maxn][maxn]; int Laxt[maxn],Next[6001],To[6001],len[6001],cnt; int a[6001],b[6001],fa[maxn][12],ans[6001],dep[maxn],N,Q; bool vis[maxn]; int ind[maxn]; void add(int u,int v,int w) { Next[++cnt]=Laxt[u]; Laxt[u]=cnt; To[cnt]=v; len[cnt]=w; } int LCA(int u,int v) { if(dep[u]<dep[v]) swap(u,v); for(int i=11;i>=0;i--) { if(dep[fa[u][i]]>=dep[v]) u=fa[u][i]; } if(u==v) return u; for(int i=11;i>=0;i--) if(fa[u][i]!=fa[v][i]) u=fa[u][i],v=fa[v][i]; return fa[u][0]; } set<pii>s; void solve(int p) { rep(i,1,N) vis[i]=ind[i]=0; rep(i,1,N) for(int j=Laxt[i];j;j=Next[j]){ if(dis[p][i]+len[j]==dis[p][To[j]]) ind[To[j]]++; } s.insert(mp(p,0)); while(!s.empty()){ pii T=*s.begin(); s.erase(s.begin()); int u=T.first; fa[u][0]=T.second; dep[u]=dep[T.second]+1; vis[u]=1; for(int i=Laxt[u];i;i=Next[i]) { if(dis[p][u]+len[i]==dis[p][To[i]]) { ind[To[i]]--; if(ind[To[i]]==0) s.insert(mp(To[i],u)); } } } rep(i,1,11) rep(j,1,N) fa[j][i]=fa[fa[j][i-1]][i-1]; rep(i,1,Q){ if(!vis[a[i]]||!vis[b[i]]) continue; ans[i]=max(ans[i],dis[p][LCA(a[i],b[i])]); } } int main() { int M,u,v,w; scanf("%d%d%d",&N,&M,&Q); rep(i,1,Q) ans[i]=-1; rep(i,1,N) rep(j,1,N) dis[i][j]=inf; rep(i,1,N) dis[i][i]=0; rep(i,1,M) { scanf("%d%d%d",&u,&v,&w); add(u,v,w); dis[u][v]=min(dis[u][v],w); } rep(i,1,Q) scanf("%d%d",&a[i],&b[i]); rep(k,1,N) rep(i,1,N) if(dis[i][k]!=inf) rep(j,1,N) dis[i][j]=min(dis[i][j],dis[i][k]+dis[k][j]); rep(i,1,N) solve(i); rep(i,1,Q) printf("%d\n",ans[i]); return 0; }