洛谷2266 爱的距离
经过精心研究,Soha发现关押Ami的地牢需要若干把钥匙才能打开,而钥匙则被埋藏在一系列的法阵中。凭借着自己的身手与魔力,Soha是能够破解法阵、获得钥匙的。法阵是一个M*N大小的矩阵,法阵中的每一格都具有自己的高度。其中,有一部分格中埋藏着钥匙,但Soha法力不足,无法直接挖取。而他发现,只需从埋藏着钥匙的格子出发向四周的格子走,并在不少于T个的独立法阵格子中施法(包括埋藏钥匙的格子本身也要施法),便可挖出钥匙。换句话说,在他每次挖掘钥匙之前,都必须先从埋藏钥匙的格子开始,走过周围的T-1个不重复的格子。
虽然Soha施法不需要耗费体力,但他在移动的过程中,需要耗费一定量的体力(体育不及格233)。从一个格子移动到另一个格子中所耗费的体力值为两个格子的高度值之差的绝对值。
对于每个埋藏钥匙的格子来说,定义其难度值P为在施法过程中,每次在各个格子间移动的所需耗费的体力的最大值。
而Soha则希望让这个难度值越小越好。因为,只有保留足够多的体力,他才能营救Ami,并两人合力打败恶魔猎手的背叛。
所以,他想知道所有埋藏钥匙的点的难度值的和最小值可以是多少?
思路: 题目比较繁琐,但思路简单。将每相邻的两个格连一条边,然后就是最小生成树了,用并查集维护(先把边排升序,保存每个集合中的元素个数和1的个数)。每次合并的时候,判断合并后元素总和是否大于t,对于合并前的两个集合,将原集合没有合并时的元素中和与t比较,若元素总数<t,则ans+=原集合中1的个数*边长。最后输出ans。
#include<iostream> #include<cstdio> #include<cstring> #include<algorithm> using namespace std; struct use{ int st,en; long long va; }a[1000000]; int map[1001][1001],sum[800000]={0},sum1[800000]={0},fa[800001]={0}; int my_comp(const use &x,const use &y) { if (x.va<y.va) return 1; return 0; } int rool(int x) { if (fa[x]!=x) fa[x]=rool(fa[x]); return fa[x]; } int main() { int n,m,t,i,j,p,tot=0,r1,r2; long long ans=0,x; scanf("%d%d%d",&n,&m,&t); for (i=1;i<=n;++i) for (j=1;j<=m;++j) { p=(i-1)*m+j; scanf("%lld",&x); map[i][j]=x; if (j>1) { ++tot; a[tot].st=p-1; a[tot].en=p; a[tot].va=abs(map[i][j]-map[i][j-1]); } if (i>1) { ++tot; a[tot].st=p-m; a[tot].en=p; a[tot].va=abs(map[i-1][j]-map[i][j]); } } sort(a+1,a+tot+1,my_comp); for (i=1;i<=n;++i) for (j=1;j<=m;++j) { scanf("%lld",&x); p=(i-1)*m+j; sum1[p]=x; } for (i=1;i<=n*m;++i) { sum[i]=1; fa[i]=i; } for (i=1;i<=tot;++i) { r1=rool(a[i].st); r2=rool(a[i].en); if (r1!=r2) { if (sum[r2]+sum[r1]>=t) { if (sum[r1]<t) { ans=ans+sum1[r1]*a[i].va; } if (sum[r2]<t) { ans=ans+sum1[r2]*a[i].va; } } sum[r2]=sum[r2]+sum[r1]; sum1[r2]=sum1[r2]+sum1[r1]; fa[r1]=r2; } } printf("%lld ",ans); }
bzoj1016 最小生成树计数
题目大意:现在给出了一个简单无向加权图。你不满足于求出这个图的最小生成树,而希望知道这个图中有多少个不同的最小生成树。(如果两颗最小生成树中至少有一条边不同,则这两个最小生成树就是不同的)。由于不同的最小生成树可能很多,所以你只需要输出方案数对31011的模就可以了。
思路:考虑最小生成树的性质:1)每种边权的边数相同(若不相同,答案就会改变);2)从小到大连晚某种边权后,点的连通状态相同(借助kruskal过程理解)。鉴于题目中相同边权的边不超过10条,我们可以对于每种边权的边dfs一下,判断可行的解,累乘起来就可以了。
#include<iostream> #include<cstdio> #include<algorithm> #define maxedge 1010 #define maxnode 110 #define P 31011 using namespace std; struct use{ int x1,x2,va,en; }edge[maxedge*2]={0}; int fa[maxnode]={0},nefa[maxnode]={0},lafa[maxnode]={0},cc,n; bool visit[maxedge*2]={false}; int my_comp(const use x,const use y) { if (x.va<y.va) return 1; else return 0; } int rool(int x) { if (fa[x]!=x) fa[x]=rool(fa[x]); return fa[x]; } int nerool(int x) { if (nefa[x]!=x) nefa[x]=nerool(nefa[x]); return nefa[x]; } bool judge(int fi,int up) { int i,j,r1,r2; for (i=1;i<=n;++i) nefa[i]=lafa[i]; for (i=fi;i<=up;++i) { if (visit[i]) { r1=nerool(edge[i].x1),r2=nerool(edge[i].x2); if (r1==r2) return false; if (r1<r2) nefa[r2]=r1; else nefa[r1]=r2; } } for (i=1;i<=n;++i) { nerool(i);rool(i); if (fa[i]!=nefa[i]) return false; } return true; } void dfs(int i,int tot,int la,int fi,int up) { int j; if (i>tot) { if (judge(fi,up)) { ++cc;if (cc>=P) cc-=P; } return; } for (j=la+1;j<=up;++j) { visit[j]=true; dfs(i+1,tot,j,fi,up); visit[j]=false; } } int main() { int m,i,j,tot,r1,r2,ans; scanf("%d%d",&n,&m); for (i=1;i<=m;++i) scanf("%d%d%d",&edge[i].x1,&edge[i].x2,&edge[i].va); sort(edge+1,edge+m+1,my_comp); for (i=1;i<=m;i=j+1) { j=i;edge[i].en=i; while(edge[j].va==edge[j+1].va) ++j; edge[i].en=j; } tot=0; for (i=1;i<=n;++i) fa[i]=i; for (i=1;i<=m;++i) { r1=rool(edge[i].x1);r2=rool(edge[i].x2); if (r1!=r2) { if (r1<r2) fa[r2]=r1; else fa[r1]=r2; ++tot; if (tot==n-1) break; } } if (tot<n-1) printf("0 "); else { ans=1; for (i=1;i<=n;++i) fa[i]=i; for (i=1;i<=m;i=j) { tot=0; for (j=1;j<=n;++j) {rool(j);lafa[j]=fa[j];} for (j=i;j<=edge[i].en;++j) { r1=rool(edge[j].x1);r2=rool(edge[j].x2); if (r1!=r2) { if (r1<r2) fa[r2]=r1; else fa[r1]=r2; ++tot; } } cc=0; dfs(1,tot,i-1,i,edge[i].en); ans=(ans*cc)%P; } printf("%d ",ans); } }
从这道题目中,对最小生成树有了新的认识。
bzoj3624 免费道路
题目大意:给定两种路,求一个最小生成树使得其中一种路0有k条。
思路:可以先把1的路都加上,再加0的路,如果0的超过k条就是无解;然后把之前0的路都加上,如果不够在找一些0的补上,最后填1的边,输出就可以了。(一开始做第二次最小生成树的时候没有加之前选过的0的边,而是随便取得,所以出现了一些问题。)
#include<iostream> #include<cstdio> #include<cstring> #include<algorithm> #define maxn 20005 #define maxe 100005 using namespace std; struct use{ int ki,u,v;bool us; }edge[maxe]={0}; int fa[maxn]={0},n,m; void pre(){for (int i=1;i<=n;++i) fa[i]=i;} int root(int x) { if (x!=fa[x]) fa[x]=root(fa[x]); return fa[x]; } void add(int i,int u,int v,int kk){edge[i].u=u;edge[i].v=v;edge[i].ki=kk;} int kru(int kk,int limit) { int i,j,r1,r2,tot=0; if (limit==0) return 0; for (i=1;i<=m;++i){ if (edge[i].ki!=kk) continue; r1=root(edge[i].u);r2=root(edge[i].v); if (r1!=r2){edge[i].us=true;fa[r1]=r2;++tot;} if (tot>=limit) break; } return tot; } int kru2() { int i,j,r1,r2,tot=0; for (i=1;i<=m;++i){ if (edge[i].ki==1){edge[i].us=false;continue;} if (edge[i].us==false) continue; r1=root(edge[i].u);r2=root(edge[i].v); if (r1!=r2){edge[i].us=true;fa[r1]=r2;++tot;} } return tot; } int main() { int k,i,j,u,v,kk,tot1,tot2; scanf("%d%d%d",&n,&m,&k);pre(); for (i=1;i<=m;++i){scanf("%d%d%d",&u,&v,&kk);add(i,u,v,kk);} tot1=kru(1,n);tot2=kru(0,n); if (tot1+tot2<n-1||tot2>k) printf("no solution "); else{ pre();tot2=kru2(); tot1=kru(0,k-tot2);tot2=kru(1,n); for (i=1;i<=m;++i) if (edge[i].us) printf("%d %d %d ",edge[i].u,edge[i].v,edge[i].ki); } }
bzoj2654 tree(!!!)
题目大意:给定一个无向图,有白边、黑边,求一个最小权的有k条白边的最小生成树。
思路:二分+最小生成树。白边加上一个值x(单增)之后,最小生成树中白边个数是单调递减的,所以可以二分(边权都是0~100)。注意可能出现x个的白边个数不固定,所以我们尽量排序的时候将同权值的白边放在前面,保存一下当前最小生成树的权值sum和白边个数,答案就是sum-k*mid。
#include<iostream> #include<cstdio> #include<cstring> #include<algorithm> #define maxm 100005 using namespace std; struct use{ int st,en,va,vv,kk; }edge[maxm]={0},ai[maxm]={0}; int n,m,k,fa[maxm]={0},sum; int cmp(const use&x,const use&y){return x.vv==y.vv?x.kk<y.kk:x.vv<y.vv;} int root(int x){ if (x!=fa[x]) fa[x]=root(fa[x]); return fa[x]; } int work(int mid){ int i,j,r1,r2,tot=0;sum=0; for (i=1;i<=m;++i){ ai[i].st=edge[i].st;ai[i].en=edge[i].en; ai[i].kk=edge[i].kk; if (edge[i].kk) ai[i].vv=edge[i].va; else ai[i].vv=edge[i].va+mid; }for (i=1;i<=n;++i) fa[i]=i; sort(ai+1,ai+m+1,cmp); for (j=0,i=1;j<n-1&&i<=m;++i){ r1=root(ai[i].st);r2=root(ai[i].en); if (r1!=r2){ fa[r1]=r2;++j;sum+=ai[i].vv; if (!ai[i].kk) ++tot; if (j==n-1)break; } }return tot; } int main(){ int i,j,u,v,l,r,mid,ans=0;scanf("%d%d%d",&n,&m,&k); for (i=1;i<=m;++i){ scanf("%d%d%d%d",&edge[i].st,&edge[i].en,&edge[i].va,&edge[i].kk); ++edge[i].st;++edge[i].en; }l=-101;r=101; while(l<=r){ j=work(mid=(l+r)/2); if (j>=k){ans=sum-k*mid;l=mid+1;} else r=mid-1; }printf("%d ",ans); }
bzoj2753 滑雪与时间胶囊
题目大意:给定n个景点及m条边,每次可以从一个高的滑到高度小于等于它的,同时可以自由回到之前到过的点,问最多访问的景点数和总的最小路径和。
思路:连出有向边之后,可以通过很多方式求出第一问(深搜一下联通的点,或者第二问的时候顺带更新一下),然后就是第二问,求最小树形图,有朱刘算法,但是时间复杂度不行,那就得优化了(听ca爷说直接跑裸的最小生成树就行了,然后。被骗了),考虑如果要让这个是最小树形图,那就得能从1号点到所有的点,而这些边都是从高度高的到低的,所以如果我们按照终点高度排序的话(其实一开始做的时候,是本着放弃的态度试出来的。),就能跑出合理的最小树形图了。
#include<iostream> #include<cstdio> #include<cstring> #include<algorithm> #define maxm 2000005 #define len 2000000 #define LL long long using namespace std; struct use{ int st,en,hi;LL va; }edge[maxm]={0}; int point[maxm]={0},next[maxm]={0},en[maxm]={0},hi[maxm]={0},tot=0,n,m,fa[maxm]={0},siz[maxm]={0},sum; LL va[maxm]={0},ans=0; bool visit[maxm]={false}; void add(int u,int v,LL vv){next[++tot]=point[u];point[u]=tot;en[tot]=v;va[tot]=vv;} int cmp(const use&x,const use&y){return (x.hi==y.hi ? x.va<y.va : x.hi>y.hi);} int root(int u){ if (fa[u]!=u) fa[u]=root(fa[u]); return fa[u]; } void dfs(int u){ int i,j,v;visit[u]=true;++sum; for (i=point[u];i;i=next[i]) if (!visit[v=en[i]]) dfs(v); } void work(){ int i,j,u,v,r1,r2; for (i=1;i<=n;++i){ if (!visit[i]) continue; for (j=point[i];j;j=next[j]) edge[++tot]=(use){i,en[j],hi[en[j]],va[j]}; }for (i=1;i<=n;++i) fa[i]=i; sort(edge+1,edge+tot+1,cmp); for (i=1;i<=tot;++i){ r1=root(edge[i].st);r2=root(edge[i].en); if (r1==r2) continue; fa[r2]=r1;ans+=edge[i].va; } } int main(){ int i,j,u,v;LL ki; scanf("%d%d",&n,&m); for (i=1;i<=n;++i) scanf("%d",&hi[i]); for (i=1;i<=m;++i){ scanf("%d%d%lld",&u,&v,&ki); if (hi[u]>=hi[v]) add(u,v,ki); if (hi[v]>=hi[u]) add(v,u,ki); }tot=0;dfs(1);work(); printf("%d %lld ",sum,ans); }
曼哈顿距离最小生成树
给出平面上的n个点,求曼哈顿距离最小生成树。以每个点为原点,用x、y轴和y=x和y=-x把平面分成八个部分,每个部分只需要连最近的那个点,8n条边,在用kruskal求最小生成树。
每个部分只用连最近点是因为:如果原点是A,部分内有B、C,只要证明BC<max(AB,AC),就说明AB和AC中一定有一条边不用连,所以最后只需要连最近的点。证明:在y>x和y轴右边取得部分中:B(x1,y1),C(x2,y2),所以y1>x1,y2>x2,令x1<x2(如果不同可以交换B、C),BC=x2-x1+|y2-y1|,两种情况:BC=x2-x1+y2-y1=(x2+y2)-(x1+y1)<=(x2+y2)=AC;BC=x2-x1+y1-y2<=y2-x1+y1-y2<=y1-x1<=y1+x1=AB。
实现的时候只需要找四部分和两个半坐标轴上的,每个部分可以通过坐标变换变成一样的情况。
求曼哈顿距离最小生成树和树上两点路径的最大值。
#include<iostream> #include<cstdio> #include<cstring> #include<algorithm> #define N 100005 #define M 1000005 #define up 18 #define mid (l+r)/2 #define lson i<<1,l,mid #define rson i<<1|1,mid+1,r #define inf 2100000000 using namespace std; int in(){ char ch=getchar();int x=0; while(ch<'0'||ch>'9') ch=getchar(); while(ch>='0'&&ch<='9'){ x=x*10+ch-'0';ch=getchar(); }return x;} struct use{ int u,v,va; bool operator<(const use&x)const{return va<x.va;} }ed[M]; struct poi{int x,y,po;}ai[N],di[N]; int cmp1(const poi&x,const poi&y){return (x.y==y.y ? x.x<y.x : x.y<y.y);} int cmp2(const poi&x,const poi&y){return (x.x==y.x ? x.y<y.y : x.x<y.x);} int n,point[N],next[M],en[M],va[M],tot=0,fa[N],tt=0,fi[N][up],gi[N][up],dep[N]={0}, bi[N],bz=0; int ab(int x){return x<0 ? -x : x;} int dis(int x1,int y1,int x2,int y2){return ab(x1-x2)+ab(y1-y2);} int find(int x){return x==fa[x] ? x : fa[x]=find(fa[x]);} void add(int u,int v,int w){ next[++tt]=point[u];point[u]=tt;en[tt]=v;va[tt]=w; next[++tt]=point[v];point[v]=tt;en[tt]=u;va[tt]=w; } void pre(){ int i,r1,r2; sort(ed+1,ed+tot+1); for (i=1;i<=tot;++i){ r1=find(ed[i].u);r2=find(ed[i].v); if (r1==r2) continue; add(ed[i].u,ed[i].v,ed[i].va); fa[r1]=r2; } } void dfs(int u,int ff){ int i,v;fi[u][0]=ff; dep[u]=dep[ff]+1; for (i=1;i<up;++i){ fi[u][i]=fi[fi[u][i-1]][i-1]; gi[u][i]=max(gi[u][i-1],gi[fi[u][i-1]][i-1]); }for (i=point[u];i;i=next[i]){ if ((v=en[i])==ff) continue; gi[v][0]=va[i];dfs(v,u); } } int work(int u,int v){ int i,mx=0; if (dep[u]<dep[v]) swap(u,v); for (i=up-1;i>=0;--i) if (dep[fi[u][i]]>=dep[v]){ mx=max(mx,gi[u][i]); u=fi[u][i]; } if (u==v) return mx; for (i=up-1;i>=0;--i) if (fi[u][i]!=fi[v][i]){ mx=max(mx,max(gi[u][i],gi[v][i])); u=fi[u][i];v=fi[v][i]; }mx=max(mx,max(gi[u][0],gi[v][0])); return mx; } struct seg{int v,po;}tr[N<<2],ci; seg updata(seg x,seg y){return (x.v<y.v ? x : y);} void build(int i,int l,int r){ if (l==r){tr[i]=(seg){inf,0};return;} build(lson);build(rson); tr[i]=updata(tr[i<<1],tr[i<<1|1]); } void ask(int i,int l,int r,int ll,int rr){ if (ll<=l&&r<=rr){ if (l==ll) ci=tr[i]; else ci=updata(ci,tr[i]); return; }if (ll<=mid) ask(lson,ll,rr); if (rr>mid) ask(rson,ll,rr); } void tch(int i,int l,int r,int x,seg y){ if (l==r){tr[i]=updata(tr[i],y);return;} if (x<=mid) tch(lson,x,y); else tch(rson,x,y); tr[i]=updata(tr[i<<1],tr[i<<1|1]); } void gete(){ int i,j,k,a;bz=0; for (i=1;i<=n;++i) bi[++bz]=ai[i].x+ai[i].y; sort(bi+1,bi+bz+1); bz=unique(bi+1,bi+bz+1)-bi-1; sort(ai+1,ai+n+1,cmp1); build(1,1,bz); for (i=n;i;i=j-1){ for (j=i;j>1&&ai[j].y==ai[j-1].y;--j); for (k=j;k<=i;++k){ a=upper_bound(bi+1,bi+bz+1,ai[k].x+ai[k].y)-bi-1; ask(1,1,bz,1,a); if (ci.po) ed[++tot]=(use){ai[ci.po].po,ai[k].po, dis(ai[ci.po].x,ai[ci.po].y,ai[k].x,ai[k].y)}; }for (k=j;k<=i;++k){ a=upper_bound(bi+1,bi+bz+1,ai[k].x+ai[k].y)-bi-1; tch(1,1,bz,a,(seg){ai[k].y-ai[k].x,k}); } } } int main(){ freopen("plan.in","r",stdin); freopen("plan.out","w",stdout); int i,j,m,u,v;n=in(); for (i=1;i<=n;++i){ di[i]=ai[i]=(poi){in(),in(),i};fa[i]=i; }sort(ai+1,ai+n+1,cmp1); for (i=1;i<=n;i=j+1) for (j=i;j<n&&ai[j].y==ai[j+1].y;++j) ed[++tot]=(use){ai[j].po,ai[j+1].po,dis(ai[j].x,ai[j].y,ai[j+1].x,ai[j+1].y)}; sort(ai+1,ai+n+1,cmp2); for (i=1;i<=n;i=j+1) for (j=i;j<n&&ai[j].x==ai[j+1].x;++j) ed[++tot]=(use){ai[j].po,ai[j+1].po,dis(ai[j].x,ai[j].y,ai[j+1].x,ai[j+1].y)}; gete(); for (i=1;i<=n;++i) ai[i]=(poi){-di[i].x,di[i].y,di[i].po}; gete(); for (i=1;i<=n;++i) ai[i]=(poi){-di[i].y,-di[i].x,di[i].po}; gete(); for (i=1;i<=n;++i) ai[i]=(poi){-di[i].y,di[i].x,di[i].po}; gete(); pre();dfs(1,0);m=in(); while(m--){ u=in();v=in(); printf("%d ",work(u,v)); } }