图论训练记录 2025.1
牛场围栏
https://www.gxyzoj.com/d/hzoj/p/LG2662
如果暴力dp,有一定的概率会T,如何降低时间复杂度?
前置知识:同余最短路
例题:P3403 跳楼机
假设
则通过操作,可以变为
所以,可以将能相互转移的点连边,边权即加上的数
此时,从起点到这个点的最短路即为对x取模余i的最小可用x,y,z凑出的数
答案为:
注意,因为下标从1开始,要统一先-1
点击查看代码
#include<cstdio>
#include<queue>
#define ll long long
using namespace std;
ll h,inf=(1ll<<62)+((1ll<<62)-1),d[100005];
int a,b,c,head[100005],edgenum;
struct edge{
int to,nxt,val;
}e[400005];
void add_edge(int u,int v,int w)
{
e[++edgenum].nxt=head[u];
e[edgenum].val=w;
e[edgenum].to=v;
head[u]=edgenum;
}
queue<int> q;
bool inq[100005];
void spfa(int x)
{
q.push(x);
d[x]=0;
while(!q.empty())
{
int u=q.front();
q.pop();
inq[u]=0;
for(int i=head[u];i;i=e[i].nxt)
{
int v=e[i].to;
if(d[v]>d[u]+e[i].val)
{
d[v]=d[u]+e[i].val;
if(!inq[v])
{
inq[v]=1;
q.push(v);
}
}
}
}
}
int main()
{
scanf("%lld%d%d%d",&h,&a,&b,&c);
if(a==1||b==1||c==1)
{
printf("%lld",h);
return 0;
}
h--;
for(int i=0;i<a;i++)
{
add_edge(i,(i+b)%a,b);
add_edge(i,(i+c)%a,c);
d[i]=inf;
}
spfa(0);
ll ans=0;
for(int i=0;i<a;i++)
{
if(h>=d[i]) ans+=(h-d[i])/a+1;
}
printf("%lld",ans);
return 0;
}
而对于这个题,数字变多了,用同样的思路,先连边,跑最短路
此时,得到的是最少的可行解,减去模数就是答案,注意跑不到直接输-1
点击查看代码
#include<cstdio>
#include<algorithm>
#include<queue>
using namespace std;
int n,m,minx=1e9,d[3005],head[3005],edgenum;
bool vis[3005];
struct edge{
int to,nxt,val;
}e[9000001];
void add_edge(int u,int v,int w)
{
// printf("%d %d %d\n",u,v,w);
e[++edgenum].nxt=head[u];
e[edgenum].val=w;
e[edgenum].to=v;
head[u]=edgenum;
}
queue<int> q;
bool inq[3005];
void spfa(int x)
{
q.push(x);
inq[x]=1;
d[x]=0;
while(!q.empty())
{
int u=q.front();
q.pop();
inq[u]=0;
for(int i=head[u];i;i=e[i].nxt)
{
int v=e[i].to;
if(d[v]>d[u]+e[i].val)
{
d[v]=d[u]+e[i].val;
if(!inq[v])
{
inq[v]=1;
q.push(v);
}
}
}
}
}
int main()
{
scanf("%d%d",&n,&m);
for(int i=1;i<=n;i++)
{
int x;
scanf("%d",&x);
for(int j=0;j<=min(x,m);j++)
{
vis[x-j]=1;
}
minx=min(minx,x-m);
}
minx=max(minx,0);
if(vis[1])
{
printf("-1");
return 0;
}
for(int i=0;i<minx;i++)
{
for(int j=minx+1;j<=3000;j++)
{
if(vis[j])
{
// if(i==1) printf("%d ",j);
add_edge(i,(i+j)%minx,j);
}
}
d[i]=1e9;
}
spfa(0);
int ans=0;
for(int i=0;i<minx;i++)
{
// printf("%d ",d[i]);
ans=max(ans,d[i]-minx);
}
ans=max(ans,0);
if(ans==1e9) printf("-1");
else printf("%d",ans);
return 0;
}
「LNOI2014」LCA
https://www.gxyzoj.com/d/hzoj/p/P1241
因为一个点的lca一定在它到根的路径上,而且是求深度
所以可以将每个要求的点到根的路径直接+1,树剖实现即可
对于区间,其实可以拆成
点击查看代码
#include<cstdio>
#include<vector>
#include<algorithm>
#define lid id<<1
#define rid id<<1|1
using namespace std;
const int mod=201314;
int n,m,edgenum,head[50005];
struct edge{
int to,nxt;
}e[100005];
void add_edge(int u,int v)
{
e[++edgenum].nxt=head[u];
e[edgenum].to=v;
head[u]=edgenum;
}
int son[50005],id[50005],siz[50005],top[50005],f[50005];
int dep[50005],cnt,rnk[50005];
void dfs(int u,int fa)
{
f[u]=fa,siz[u]=1,dep[u]=dep[fa]+1;
int maxn=-1;
for(int i=head[u];i;i=e[i].nxt)
{
int v=e[i].to;
dfs(v,u);
siz[u]+=siz[v];
if(maxn<siz[v])
{
maxn=siz[v],son[u]=v;
}
}
}
void dfs1(int u,int t)
{
top[u]=t,id[u]=++cnt;
rnk[cnt]=id[u];
if(son[u])
{
dfs1(son[u],t);
}
for(int i=head[u];i;i=e[i].nxt)
{
int v=e[i].to;
if(v==f[u]||v==son[u]) continue;
dfs1(v,v);
}
}
struct seg_tr{
int l,r,sum,lazy;
}tr[800005];
void build(int id,int l,int r)
{
tr[id].l=l,tr[id].r=r;
if(l==r)
{
return;
}
int mid=(l+r)>>1;
build(lid,l,mid);
build(rid,mid+1,r);
}
void pushdown(int id)
{
if(tr[id].lazy)
{
tr[lid].sum+=(tr[lid].r-tr[lid].l+1)*tr[id].lazy%mod;
tr[lid].lazy+=tr[id].lazy;
tr[rid].sum+=(tr[rid].r-tr[rid].l+1)*tr[id].lazy%mod;
tr[rid].lazy+=tr[id].lazy;
tr[lid].sum%=mod,tr[rid].sum%=mod;
tr[id].lazy=0;
}
}
void update(int id,int l,int r)
{
if(tr[id].l==l&&tr[id].r==r)
{
tr[id].sum+=r-l+1;
tr[id].sum%=mod;
tr[id].lazy++;
return;
}
pushdown(id);
int mid=(tr[id].l+tr[id].r)>>1;
if(r<=mid) update(lid,l,r);
else if(l>mid) update(rid,l,r);
else update(lid,l,mid),update(rid,mid+1,r);
tr[id].sum=(tr[lid].sum+tr[rid].sum)%mod;
}
void add(int u,int v)
{
while(top[u]!=top[v])
{
if(dep[top[u]]<dep[top[v]]) swap(u,v);
update(1,id[top[u]],id[u]);
u=f[top[u]];
}
if(dep[u]<dep[v]) swap(u,v);
update(1,id[v],id[u]);
}
int query(int id,int l,int r)
{
// printf("%d %d %d %d %d\n",id,tr[id].l,tr[id].r,l,r);
if(tr[id].l==l&&tr[id].r==r)
{
return tr[id].sum;
}
pushdown(id);
int mid=(tr[id].l+tr[id].r)>>1;
if(r<=mid) return query(lid,l,r);
else if(l>mid) return query(rid,l,r);
else return (query(lid,l,mid)+query(rid,mid+1,r))%mod;
}
int getsum(int u,int v)
{
int res=0;
while(top[u]!=top[v])
{
if(dep[top[u]]<dep[top[v]]) swap(u,v);
res=(res+query(1,id[top[u]],id[u]))%mod;
u=f[top[u]];
}
if(dep[u]<dep[v]) swap(u,v);
res=(res+query(1,id[v],id[u]))%mod;
return res;
}
struct node{
int id,x,t;
};
vector<node> v[50005];
int ans[50005];
int main()
{
// freopen("1.txt","r",stdin);
scanf("%d%d",&n,&m);
for(int i=2;i<=n;i++)
{
int x;
scanf("%d",&x);
x++;
add_edge(x,i);
}
dfs(1,0);
dfs1(1,1);
build(1,1,n);
// printf("1");
for(int i=1;i<=m;i++)
{
int l,r,z;
scanf("%d%d%d",&l,&r,&z);
v[l].push_back((node){i,z+1,-1});
v[r+1].push_back((node){i,z+1,1});
}
for(int i=1;i<=n;i++)
{
add(i,1);
// printf("%d\n",i);
for(int j=0;j<v[i].size();j++)
{
int x=v[i][j].x,id1=v[i][j].id;
ans[id1]=(ans[id1]+getsum(1,x)*v[i][j].t)%mod;
}
}
for(int i=1;i<=m;i++)
{
printf("%d\n",(ans[i]+mod)%mod);
}
return 0;
}
跳跳棋
神秘构造题
可以发现,假设当前位置为
可以发现,其中的两个对总距离是缩小,而另外两个是增加
先考虑缩小,因为
但是因为
因为操作可逆,所以可以将初状态和末状态化简到
因为接下来只存在两种情况,即中间移到左边或右边,可以按照转移构建二叉树
此时,初状态和末状态必然为其上两个节点,而他们到lca的距离之和就是答案
但因为状态很多,无法建树,可以现将多出的运算推平后,二分次数
点击查看代码
#include<cstdio>
#include<algorithm>
using namespace std;
struct node{
int x,y,z,tot;
}t1,t2;
int cnt;
void get(int x,int y,int z)
{
cnt=0;
while(1)
{
int d1=y-x,d2=z-y,d3;
if(d1==d2) break;
if(d1>d2)
d3=(d1-1)/d2,cnt+=d3,y-=d3*d2,z-=d3*d2;
else
d3=(d2-1)/d1,cnt+=d3,x+=d3*d1,y+=d3*d1;
}
if(t1.x||t1.y) t2=(node){x,y,z,cnt};
else t1=(node){x,y,z,cnt};
}
bool check(int x,int ax,int ay,int az,int bx,int by,int bz)
{
int y=x;
while(x)
{
int d1=ay-ax,d2=az-ay,d3;
if(d1==d2) break;
if(d1>d2)
d3=min(x,(d1-1)/d2),x-=d3,ay-=d3*d2,az-=d3*d2;
else
d3=min(x,(d2-1)/d1),x-=d3,ax+=d3*d1,ay+=d3*d1;
}
while(y)
{
int d1=by-bx,d2=bz-by,d3;
if(d1==d2) break;
if(d1>d2)
d3=min(y,(d1-1)/d2),y-=d3,by-=d3*d2,bz-=d3*d2;
else
d3=min(y,(d2-1)/d1),y-=d3,bx+=d3*d1,by+=d3*d1;
}
if(ax==bx&&ay==by&&az==bz)
{
return 1;
}
else return 0;
}
int a[10],x,y,z,ax,ay,az;
int main()
{
for(int i=1;i<=3;i++) scanf("%d",&a[i]);
sort(a+1,a+4);
x=a[1],y=a[2],z=a[3];
for(int i=1;i<=3;i++) scanf("%d",&a[i]);
sort(a+1,a+4);
ax=a[1],ay=a[2],az=a[3];
get(x,y,z);
get(ax,ay,az);
if(t1.x!=t2.x||t1.y!=t2.y||t1.z!=t2.z)
{
// printf("%d %d %d %d %d %d\n",t1.x,t1.y,t1.z,t2.x,t2.y,t2.z);
printf("NO");
return 0;
}
if(t1.tot<t2.tot)
{
swap(t1,t2),swap(x,ax),swap(y,ay),swap(z,az);
}
int ans=t1.tot-t2.tot;
cnt=ans;
while(cnt)
{
int d1=y-x,d2=z-y,d3;
if(d1==d2) break;
if(d1>d2)
d3=min(cnt,(d1-1)/d2),cnt-=d3,y-=d3*d2,z-=d3*d2;
else
d3=min(cnt,(d2-1)/d1),cnt-=d3,x+=d3*d1,y+=d3*d1;
}
int l=0,r=t2.tot;
while(l<r)
{
int mid=(l+r)>>1;
if(check(mid,x,y,z,ax,ay,az))
{
r=mid;
}
else l=mid+1;
}
printf("YES\n%d",ans+2*l);
return 0;
}
Berland and the Shortest Paths
https://www.gxyzoj.com/d/hzoj/p/CF1005F
原,dij跑出最短路然后倒推统计情况即可
点击查看代码
#include<cstdio>
#include<queue>
#define ll long long
using namespace std;
int n,m,k,head[200005],edgenum;
struct edge{
int to,nxt,id;
}e[400005];
void add_edge(int u,int v,int id)
{
e[++edgenum].nxt=head[u];
e[edgenum].id=id;
e[edgenum].to=v;
head[u]=edgenum;
}
struct node{
int x,y,val;
bool operator < (const node &tmp)const{
return val>tmp.val;
}
};
priority_queue<node> q;
int d[200005];
bool vis[200004];
void dijkstra(int x)
{
q.push((node){x,x,0});
while(!q.empty())
{
int u=q.top().y,val=q.top().val;
q.pop();
if(!vis[u])
{
vis[u]=1;
d[u]=val;
for(int i=head[u];i;i=e[i].nxt)
{
int v=e[i].to;
if(!vis[v])
{
q.push((node){u,v,val+1});
}
}
}
}
}
bool v1[200005];
int cnt;
ll sum;
void dfs(int u)
{
if(u==n+1)
{
for(int i=1;i<=m;i++)
{
if(v1[i]) printf("1");
else printf("0");
}
printf("\n");
cnt++;
return;
}
for(int i=head[u];i;i=e[i].nxt)
{
int v=e[i].to;
if(d[u]==d[v]+1)
{
v1[e[i].id]=1;
dfs(u+1);
v1[e[i].id]=0;
if(cnt==sum) return;
}
}
}
int main()
{
scanf("%d%d%d",&n,&m,&k);//
for(int i=1;i<=m;i++)
{
int u,v;
scanf("%d%d",&u,&v);
add_edge(u,v,i);
add_edge(v,u,i);
}
dijkstra(1);
sum=1;
for(int i=2;i<=n;i++)
{
cnt=0;
for(int j=head[i];j;j=e[j].nxt)
{
int v=e[j].to;
if(d[i]==d[v]+1) cnt++;
}
sum=min(1ll*k,1ll*sum*cnt);
}
cnt=0;
printf("%lld\n",sum);
dfs(2);
return 0;
}
Breaking Good
https://www.gxyzoj.com/d/hzoj/p/4469
和上一题一样,另外加一个统计路径上1的个数,并使在满足距离最短的情况下让它尽量大
此时,和上一题一样找来源即可
点击查看代码
#include<cstdio>
#include<queue>
using namespace std;
int n,m,head[100005],edgenum=1,cnt;
struct edge{
int nxt,to,val,from;
}e[200005];
void add_edge(int u,int v,int w)
{
e[++edgenum].nxt=head[u];
e[edgenum].from=u;
e[edgenum].to=v;
e[edgenum].val=w;
head[u]=edgenum;
}
queue<int> q;
int d[100005],d1[100005];
bool inq[100004];
void spfa(int x)
{
q.push(x);
d[x]=0;
while(!q.empty())
{
int u=q.front();
q.pop();
inq[u]=0;
for(int i=head[u];i;i=e[i].nxt)
{
int v=e[i].to;
if(d[v]>d[u]+1||(d[v]==d[u]+1&&d1[v]<d1[u]+e[i].val))
{
d[v]=d[u]+1;
d1[v]=d1[u]+e[i].val;
if(!inq[v])
{
inq[v]=1;
q.push(v);
}
}
}
}
}
int vis[200005];
void dfs(int u)
{
if(u==1) return;
for(int i=head[u];i;i=e[i].nxt)
{
int v=e[i].to,w=e[i].val;
if(vis[i]) continue;
if(d[v]+1==d[u]&&d1[v]+w==d1[u])
{
if(w) vis[i]=vis[i^1]=1;
else vis[i]=vis[i^1]=2;
dfs(v);
break;
}
}
}
int main()
{
scanf("%d%d",&n,&m);
for(int i=1;i<=m;i++)
{
int u,v,w;
scanf("%d%d%d",&u,&v,&w);
add_edge(u,v,w);
add_edge(v,u,w);
cnt+=w;
}
for(int i=1;i<=n;i++)
{
d[i]=1e9;
}
spfa(1);
printf("%d\n",d[n]-d1[n]+cnt-d1[n]);
dfs(n);
for(int i=2;i<=edgenum;i++)
{
if(!vis[i])
{
if(e[i].val) vis[i]=vis[i^1]=3;
}
}
for(int i=2;i<=edgenum;i+=2)
{
if(vis[i]>1)
printf("%d %d %d\n",e[i].from,e[i].to,!e[i].val);
}
return 0;
}
Turtle and Intersected Segments
https://www.gxyzoj.com/d/hzoj/p/4470
如果暴力判断,边会很多,考虑什么样的边是必要的
首先,假设三个相互重叠的区间,权值
更多同理,所以,当一些区间相互重叠时,新加入的点只需要向这些权值中比它笑的点中最大的和比它大的点中最小的连边
因为两个相交的区间必然有一个端点在另一个区间上
所以直接将端点取出,然后依次处理
因为是要找前驱后继,考虑使用set,而区间在l时加入对应的a,在r时删除即可
最后跑kruskal,注意统计边数判-1
点击查看代码
#include<cstdio>
#include<algorithm>
#include<set>
#include<vector>
#define ll long long
#define it set<int>::iterator
using namespace std;
int n,T,b[1000005],c[500005],cnt[500005],mp[500005],ecnt;
struct node{
int l,r,val,id;
}a[500005];
vector<node> v[1000005];
set<int> st;
struct edge{
int u,v;
ll val;
}e[2000005];
int f[500005];
bool cmp(edge x,edge y)
{
return x.val<y.val;
}
int find(int x)
{
if(f[x]!=x) f[x]=find(f[x]);
return f[x];
}
int main()
{
scanf("%d",&T);
while(T--)
{
scanf("%d",&n);
ecnt=0;
for(int i=1;i<=n;i++)
{
int l,r,val;
scanf("%d%d%d",&l,&r,&val);
a[i]=(node){l,r,val,i};
b[i]=l,b[i+n]=r;
c[i]=val;
f[i]=i,mp[i]=cnt[i]=0;
}
sort(c+1,c+n+1);
for(int i=1;i<=n;i++)
{
a[i].val=lower_bound(c+1,c+n+1,a[i].val)-c;
int t=a[i].val;
a[i].val+=cnt[t];
mp[a[i].val]=i;
cnt[t]++;
}
sort(b+1,b+n*2+1);
int m=unique(b+1,b+2*n+1)-1-b;
for(int i=1;i<=m;i++) v[i].clear();
for(int i=1;i<=n;i++)
{
a[i].l=lower_bound(b+1,b+m+1,a[i].l)-b;
a[i].r=lower_bound(b+1,b+m+1,a[i].r)-b;
v[a[i].l].push_back((node){1,0,a[i].val,i});
v[a[i].r].push_back((node){-1,0,a[i].val,i});
}
for(int i=1;i<=m;i++)
{
for(int j=0;j<v[i].size();j++)
{
if(v[i][j].l>0)
{
st.insert(v[i][j].val);
}
}
for(int j=0;j<v[i].size();j++)
{
int x=v[i][j].val;
it p=st.upper_bound(x);
if(p!=st.end())
{
e[++ecnt]=(edge){v[i][j].id,mp[*p],c[*p]-c[x]};
}
p=st.lower_bound(x);
if(p!=st.begin())
{
p--;
// if(p!=st.begin())
e[++ecnt]=(edge){v[i][j].id,mp[*p],c[x]-c[*p]};
}
}
for(int j=0;j<v[i].size();j++)
{
if(v[i][j].l<0)
{
st.erase(st.find(v[i][j].val));
}
}
}
sort(e+1,e+ecnt+1,cmp);
ll ans=0;
int sum=0;
for(int i=1;i<=ecnt;i++)
{
if(find(e[i].u)!=find(e[i].v))
{
f[find(e[i].u)]=find(e[i].v);
ans+=e[i].val;
sum++;
}
}
if(sum!=n-1) printf("-1\n");
else printf("%lld\n",ans);
}
return 0;
}
DFS Trees
https://www.gxyzoj.com/d/hzoj/p/4385
有一个性质,边权两两不同的图最小生成树唯一
证明:假设有两棵不同的最小生成树T1,T2,假设边集中,第一个不同的边记为k,假设
此时,如果在T2中加入边权为的边,必然成环
因为如果这环上的所有边权都满足小于,则T1就不是树
如果不满足,则必然可以删去一条边权更大的边,与假设矛盾
此时,走到一条非树边,就不满足条件,有两种情况:
-
横插边,u,v子树外的点必然先到它们的lca,此时,因为要走到无法行动才回退,所以走完u或v的子树后,必然经过横插边
-
返祖边,不在(u,v)路径上的点必然会先走到其中一个点,因为是最小生成树,该边权值大,不会被选择,但路径上的点因为没有路,必然经过
此时,差分即可
对于横插边,根处+1,u,v处-1,对于返祖边,v处+1,是u儿子且是v祖先的点处-1
点击查看代码
#include<cstdio>
#include<algorithm>
using namespace std;
int n,m,head[200005],edgenum;
struct edge{
int nxt,to;
}e[400005];
void add_edge(int u,int v)
{
e[++edgenum].nxt=head[u];
e[edgenum].to=v;
head[u]=edgenum;
}
struct node{
int u,v,id;
}a[200005];
int f[200005];
bool vis[200005];
int find(int x)
{
if(x!=f[x]) f[x]=find(f[x]);
return f[x];
}
int dep[200005],f1[200005][20];
void dfs(int u,int fa)
{
dep[u]=dep[fa]+1;
f1[u][0]=fa;
for(int i=1;i<=19;i++)
{
f1[u][i]=f1[f1[u][i-1]][i-1];
}
for(int i=head[u];i;i=e[i].nxt)
{
int v=e[i].to;
if(v==fa) continue;
dfs(v,u);
}
}
int lca(int x,int y)
{
if(dep[x]<dep[y]) swap(x,y);
for(int i=19;i>=0;i--)
{
if(dep[f1[x][i]]>=dep[y])
{
x=f1[x][i];
}
if(x==y) return x;
}
for(int i=19;i>=0;i--)
{
if(f1[x][i]!=f1[y][i])
{
x=f1[x][i];
y=f1[y][i];
}
}
return f1[x][0];
}
int p[200005],ans[200005];
void dfs1(int u,int fa)
{
for(int i=head[u];i;i=e[i].nxt)
{
int v=e[i].to;
if(v==fa) continue;
p[v]+=p[u];
dfs1(v,u);
}
if(!p[u]) ans[u]=1;
}
int main()
{
scanf("%d%d",&n,&m);
for(int i=1;i<=m;i++)
{
int u,v;
scanf("%d%d",&u,&v);
a[i]=(node){u,v,i};
}
for(int i=1;i<=n;i++)
{
f[i]=i;
}
for(int i=1;i<=m;i++)
{
if(find(a[i].u)!=find(a[i].v))
{
f[find(a[i].u)]=find(a[i].v);
add_edge(a[i].u,a[i].v);
add_edge(a[i].v,a[i].u);
vis[i]=1;
}
}
dfs(1,0);
for(int i=1;i<=m;i++)
{
// printf("%d ",vis[i]);
if(!vis[i])
{
int x=a[i].u,y=a[i].v;
if(dep[x]>dep[y]) swap(x,y);
int lc=lca(x,y);
// printf("%d %d %d\n",x,y,lc);
if(lc==x)
{
p[y]--;
for(int j=19;j>=0;j--)
{
if(dep[x]<dep[f1[y][j]])
y=f1[y][j];
}
p[y]++;
}
else
{
p[1]++,p[x]--,p[y]--;
}
}
}
dfs1(1,0);
for(int i=1;i<=n;i++)
{
printf("%d",ans[i]);
}
return 0;
}
MST Company
https://www.gxyzoj.com/d/hzoj/p/4471
可以先将不与1相连的边加入,此时,会形成一个最小生成森林
接下来加入与1相邻的边,此时,如果加入超过k条或加入后图不相连,显然无解
接下来考虑加入与1相连的边,考虑到,必然加入后会形成环,找到环上最大值断掉即可
点击查看代码
#include<cstdio>
#include<algorithm>
#include<vector>
using namespace std;
int n,m,k,mx[5005];
struct node{
int u,v,w,id;
}a[100005];
int f[5005],edgenum=1,head[5005];
bool vis[200005],del[100005],vis1[100005];
int find(int x)
{
if(f[x]!=x) f[x]=find(f[x]);
return f[x];
}
bool cmp(node x,node y)
{
return x.w<y.w;
}
vector<int> v1,v2;
struct edge{
int to,val,nxt,id;
}e[200005];
void add_edge(int u,int v,int w,int id)
{
e[++edgenum].nxt=head[u];
e[edgenum].to=v;
e[edgenum].val=w;
e[edgenum].id=id;
head[u]=edgenum;
}
void dfs(int u,int fa)
{
for(int i=head[u];i;i=e[i].nxt)
{
int v=e[i].to;
if(v==fa||vis[i]||v==1) continue;
if(e[i].val>=e[mx[u]].val) mx[v]=i;
else mx[v]=mx[u];
dfs(v,u);
}
}
int main()
{
scanf("%d%d%d",&n,&m,&k);
for(int i=1;i<=m;i++)
{
int u,v,w;
scanf("%d%d%d",&u,&v,&w);
a[i]=(node){u,v,w,i};
}
for(int i=1;i<=n;i++)
{
f[i]=i;
}
sort(a+1,a+m+1,cmp);
for(int i=1;i<=m;i++)
{
int u=a[i].u,v=a[i].v;
if(u==1||v==1) continue;
if(find(u)!=find(v))
{
f[find(u)]=find(v);
v1.push_back(a[i].id);
add_edge(u,v,a[i].w,a[i].id);
add_edge(v,u,a[i].w,a[i].id);
}
}
for(int i=1;i<=m;i++)
{
int u=a[i].u,v=a[i].v;
if(u!=1&&v!=1) continue;
if(find(u)!=find(v))
{
f[find(u)]=find(v),k--;
v1.push_back(a[i].id);
add_edge(u,v,a[i].w,a[i].id);
add_edge(v,u,a[i].w,a[i].id);
}
else v2.push_back(i);
}
for(int i=2;i<=n;i++)
{
if(find(i)!=find(i-1))
{
printf("-1");
return 0;
}
}
if(k<0)
{
printf("-1");
return 0;
}
// for(int i=0;i<v1.size();i++)
// {
// if(!del[v1[i]])
// {
// printf("%d ",v1[i]);
// }
// }
while(k--)
{
if(!v2.size())
{
printf("-1");
return 0;
}
for(int i=1;i<=n;i++) mx[i]=0;
for(int i=head[1];i;i=e[i].nxt) dfs(e[i].to,1);
int now=-1,ans=2e9;
for(int i=0;i<v2.size();i++)
{
int x=v2[i],tmp=max(a[x].u,a[x].v);
if(a[x].w-e[mx[tmp]].val<ans)
{
ans=a[x].w-e[mx[tmp]].val,now=i;
}
}
int x=v2[now],tmp=max(a[x].u,a[x].v);
add_edge(a[x].u,a[x].v,a[x].w,a[x].id);
add_edge(a[x].v,a[x].u,a[x].w,a[x].id);
vis[mx[tmp]]=vis[mx[tmp]^1]=1;
del[e[mx[tmp]].id]=1;
v1.push_back(a[x].id);
v2.erase(v2.begin()+now);
}
printf("%d\n",n-1);
for(int i=0;i<v1.size();i++)
{
if(!del[v1[i]])
{
printf("%d ",v1[i]);
}
}
return 0;
}
The Shortest Statement
https://www.gxyzoj.com/d/hzoj/p/4472
可以先在图中找一些边,是这些边形成一棵树
此时,如果u,v最短路在树边上,lca求解即可
如果经过非树边,因为
此时,求出这些点到所有点的最短路,每次询问将到u和到v的距离相加再取min即可
点击查看代码
#include<cstdio>
#include<queue>
#include<algorithm>
#define ll long long
using namespace std;
int n,m,f[100005],edgenum,head[100005],id;
struct edge{
int to,nxt,tag;
ll val;
}e[200005];
void add_edge(int u,int v,int w,int tag)
{
e[++edgenum].nxt=head[u];
e[edgenum].tag=tag;
e[edgenum].val=w;
e[edgenum].to=v;
head[u]=edgenum;
}
int find(int x)
{
if(f[x]!=x) f[x]=find(f[x]);
return f[x];
}
queue<int> q;
ll dis[43][100005];
bool vis[100005],inq[100005];
void spfa(int s,int x)
{
q.push(s);
dis[x][s]=0;
while(!q.empty())
{
int u=q.front();
// printf("%d\n",u);
q.pop();
inq[u]=0;
for(int i=head[u];i;i=e[i].nxt)
{
int v=e[i].to;
// printf("%d %d %d %d\n",u,v,dis[x][v],dis[x][u]);
if(dis[x][v]>dis[x][u]+e[i].val)
{
dis[x][v]=dis[x][u]+e[i].val;
if(!inq[v])
{
q.push(v);
inq[v]=1;
}
}
}
}
}
int f1[100005][20],dep[100005];
ll d[100005][20];
void dfs(int u,int fa)
{
dep[u]=dep[fa]+1,f1[u][0]=fa;
for(int i=1;i<=17;i++)
{
f1[u][i]=f1[f1[u][i-1]][i-1];
d[u][i]=d[u][i-1]+d[f1[u][i-1]][i-1];
}
for(int i=head[u];i;i=e[i].nxt)
{
int v=e[i].to;
if(e[i].tag||fa==v) continue;
d[v][0]=e[i].val;
dfs(v,u);
}
}
ll lca(int x,int y)
{
if(dep[x]<dep[y]) swap(x,y);
ll res=0;
for(int i=17;i>=0;i--)
{
if(dep[f1[x][i]]>=dep[y])
{
res+=d[x][i];
x=f1[x][i];
}
if(x==y) return res;
}
for(int i=17;i>=0;i--)
{
if(f1[x][i]!=f1[y][i])
{
res+=d[x][i]+d[y][i];
x=f1[x][i],y=f1[y][i];
}
}
res+=d[x][0]+d[y][0];
return res;
}
int main()
{
scanf("%d%d",&n,&m);
for(int i=1;i<=n;i++)
{
f[i]=i;
}
for(int i=1;i<=m;i++)
{
int u,v,fl=1,w;
scanf("%d%d%d",&u,&v,&w);
if(find(u)!=find(v))
{
f[find(u)]=find(v);
fl=0;
}
else vis[u]=vis[v]=1;
add_edge(u,v,w,fl);
add_edge(v,u,w,fl);
}
// printf("1");
for(int i=1;i<=n;i++)
{
if(vis[i])
{
// printf("%d ",i);
id++;
for(int j=1;j<=n;j++)
{
dis[id][j]=1e18;
}
spfa(i,id);
}
}
dfs(1,0);
int q;
scanf("%d",&q);
while(q--)
{
int x,y;
scanf("%d%d",&x,&y);
ll ans=lca(x,y);
for(int i=1;i<=id;i++)
{
ans=min(ans,dis[i][x]+dis[i][y]);
}
printf("%lld\n",ans);
}
return 0;
}
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 震惊!C++程序真的从main开始吗?99%的程序员都答错了
· 【硬核科普】Trae如何「偷看」你的代码?零基础破解AI编程运行原理
· 单元测试从入门到精通
· 上周热点回顾(3.3-3.9)
· winform 绘制太阳,地球,月球 运作规律