第一题 permutation
是一道爱斯基摩人的逆序对题(形容它不裸的程度)
如果从上往下看,会发现横线的作用是交换当前它左右两侧的数。因此这道题的第一部分可以直接从上往下暴力swap。
对于第二部分,把这样一张图倒过来看,可以发现倒过来的图也就是把一个排列经过m次交换恢复顺序。
而最少的横线数,也就是最少的交换次数,就是逆序对数。
第一部分模拟交换+第二部分归并排序统计逆序对数 100分
#include<cstdio> using namespace std; const int N=1000007; int n,m,hx[N],a[N],c[N],cnt; void sort(int l,int r) { if(l==r) return; int mid=l+r>>1;int t=l,q=mid+1,p=l-1; sort(l,mid);sort(mid+1,r); while(q<=r&&t<=mid) { if(a[t]>a[q]) { cnt+=mid-t+1; c[++p]=a[q];q++; } else c[++p]=a[t++]; } while(q<=r) c[++p]=a[q++]; while(t<=mid)c[++p]=a[t++]; for(int i=l;i<=r;i++) a[i]=c[i]; } void swap(int &x,int &y) { int temp=x;x=y;y=temp; } int main() { scanf("%d%d",&n,&m); for(int i=1;i<=m;i++) scanf("%d",&hx[i]); for(int i=1;i<=n;i++) a[i]=i; for(int i=1;i<=m;i++) swap(a[hx[i]],a[hx[i]+1]); for(int i=1;i<=n;i++) printf("%d ",a[i]); sort(1,n); //- for(int i=1;i<=n;i++) printf("%d ",a[i]); printf("\n%d",cnt); }
考试的时候以为是道图论题,在那方面想了半天没想出来就放弃了。还是太年轻。
第二题 mst最小生成树
它真的是个最小生成树!!从来没有见过这么耿直的题目名称-_-
对包含第k条边的最小生成树而言,若k在这张图的最小生成树上,则答案为最小生成树的权值。
若k不在最小生成树上,考虑在最小生成树上加上k这条边,此时我们会发现,原来有n-1条边,现在有n条边,显然出现了一个环。
显然在这个环上删去任意一条边,都不会影响全图的点互相连通。所以在环上选出除边k之外最长的一条边删掉即可。
而这个问题可以转化为在最小生成树上,求两点之间路径上边权最大的边的边权。
考虑倍增。
Kruskal+倍增 100分
注意预处理边权时,为了覆盖到x到f[x][i]的所有路径,需要在e[x][i-1]和e[fa[x][i-1]][i-1]之间取max。因为没有注意到这点而改了一晚上-_-
#include<cstdio> #include<algorithm> #include<queue> using namespace std; const int N=400007; int n,m,fa[N][21],head[N],head2[N],f[N],ed[N][21],dep[N],etot,e2tot; long long anst; bool vis[N]; struct node{ int from,next,to,v,num; }e[N<<1],e2[N<<1]; void adde(int x,int y,int v,int num) { e[++etot].to=y; e[etot].from=x; e[etot].next=head[x]; e[etot].v=v; e[etot].num=num; head[x]=etot; } void adde2(int x,int y,int v) { e2[++e2tot].to=y; e2[e2tot].next=head2[x]; e2[e2tot].v=v; head2[x]=e2tot; } int find(int x) { if(f[x]!=x) f[x]=find(f[x]); return f[x]; } void unionn(int x,int y) { f[find(x)]=y; } void kruskal() { for(int i=1;i<=m;i++) { if(find(e[i].from)==find(e[i].to)) continue; unionn(e[i].from,e[i].to); vis[e[i].num]=1;anst+=e[i].v; adde2(e[i].from,e[i].to,e[i].v); adde2(e[i].to,e[i].from,e[i].v); } } void getf(int x,int f,int v) { fa[x][0]=f;ed[x][0]=v; dep[x]=dep[f]+1; for(int i=1;i<=17;i++) { fa[x][i]=fa[fa[x][i-1]][i-1]; ed[x][i]=max(ed[x][i-1],ed[fa[x][i-1]][i-1]); } for(int i=head2[x];i;i=e2[i].next) { if(e2[i].to==f) continue; getf(e2[i].to,x,e2[i].v); } } int lca(int x,int y) { int s=0; if(dep[y]>dep[x]) swap(x,y); for(int i=20;i>=0;i--) { if(dep[fa[x][i]]>=dep[y]) { s=max(s,ed[x][i]); x=fa[x][i]; } } if(x==y) return s; for(int i=20;i>=0;i--) { if(fa[x][i]!=fa[y][i]) { s=max(s,max(ed[x][i],ed[y][i])); x=fa[x][i];y=fa[y][i]; } } s=max(s,max(ed[x][0],ed[y][0])); return s; } bool comp(node x,node y) { return x.v<y.v; } bool comp2(node x,node y) { return x.num<y.num; } int main() { scanf("%d%d",&n,&m); int x,y,v; for(int i=1;i<=m;i++) { scanf("%d%d%d",&x,&y,&v); adde(x,y,v,i); } for(int i=1;i<=n;i++) f[i]=i; sort(e+1,e+1+m,comp); kruskal(); sort(e+1,e+1+m,comp2); getf(1,0,0); for(int i=1;i<=m;i++) { if(vis[i]) printf("%lld\n",anst); else printf("%lld\n",anst-lca(e[i].from,e[i].to)+e[i].v); } return 0; }
一点闲话:写的50分暴力因为数组开小了只得了20分;考试的时候想到了正解,但是写到一半的时候竟然因为突然觉得很不靠谱而放弃了:)