【比赛】2022.11.23 51nod 图论专场?
A 反转Dag图 :
题面
给出一个
你可以选择一些边进行反转(即从
总的反转代价是由所有需要反转的边中,代价最高的那一条决定的,求达成目标的最小代价。
对于
样例输入
5 6 2 1 1 5 2 6 2 3 2 3 4 3 4 5 5 1 5 4
样例输出
2
分析
这道题考试时候傻逼了,都已经想到正解了,不知道怎么被自己给否定了。
这道题要使最大边权最小,那么很容易想到二分这个最大边权,然后怎么做呢?我一开始想的是将边权小于这个二分值的边直接反转,这样肯定是错的,我举了个反例就否定了这个做法,现在只想骂自己傻逼。
其实对于边权小于二分值的边,怎么样反转都是可行的,我一直以为只能翻转一次,所以就错了,所以那些小的边绝对不会成为环的边,如果在环上,直接反转就行了,所以只要将大于二分值的边加进去,用tarjan判断是否有环就行了,(其实拓扑也行,只是我没想到)。
可能会有些疑惑,如果那些小边反转后成为另一个环的边怎么办呢?
其实没事,可以看下图,发现如果小边(5,1)翻不翻转都不会有什么影响,因为有一个大的环,所以大的环上也要有边反转。
#include<bits/stdc++.h> #define N 1000005 #define INF 0x3f3f3f3f using namespace std; int read(){ int x=0,f=1;char ch=getchar(); while(ch<'0'||ch>'9'){if(ch=='-')f=-f;ch=getchar();} while(ch>='0'&&ch<='9'){x=(x<<1)+(x<<3)+(ch^48);ch=getchar();} return x*f; } int n,m,tot,t,cnt,top; int Head[N],to[N],Next[N]; int s[N],vis[N],dfn[N],low[N]; void add(int u,int v){ to[++tot]=v,Next[tot]=Head[u],Head[u]=tot; } struct EDGE{ int u,v,w; }a[N]; void tarjan(int x){ dfn[x]=low[x]=++t,s[++top]=x,vis[x]=1; for(int i=Head[x];i;i=Next[i]){ int y=to[i]; if(!dfn[y]) tarjan(y),low[x]=min(low[x],low[y]); else if(vis[y]) low[x]=min(low[x],dfn[y]); } if(dfn[x]==low[x]){ cnt++;int k=-1; while(k!=x){ k=s[top--];vis[k]=0; } } } void init(){ tot=0,cnt=0,t=0,top=0; memset(Head,0,sizeof(Head)); memset(dfn,0,sizeof(dfn)),memset(low,0,sizeof(low)); } bool check(int x){ init(); for(int i=1;i<=m;++i) if(a[i].w>x) add(a[i].u,a[i].v); for(int i=1;i<=n;++i) if(!dfn[i]) tarjan(i); return cnt==n; } int main(){ n=read(),m=read(); for(int i=1;i<=m;++i) a[i].u=read(),a[i].v=read(),a[i].w=read(); int l=0,r=1e9,ans=0; while(l<=r){ int mid=(l+r)>>1; if(check(mid)) ans=mid,r=mid-1; else l=mid+1; } printf("%d\n",ans); return 0; }
B 歪脖子树 :
题面
杭州有一棵
它的长相过于奇怪以至于人们甚至不知道它的根在哪里,但大家初始时都假定节点
为了验证,植物学家们做了
V x y:把点
E x:将
Q x:查询
对于
输入样例
3 7 0 1 1 2 1 3 Q 1 V 1 6 Q 1 V 2 5 Q 1 V 3 4 Q 1
输出样例
1 2 3 4
分析
一道关于 dfs 序的题,其实不用去考虑换根的事情,在仍旧以 1 为根的情况下也能做到。
如果不会换根,显然可以用 dfs 序 + 线段树来求解,因为子树的dfs 必定是连续,可以转化为线段树单点修改,区间查询,那会修改根呢?。
一个显然的性质:如果换了根,那么只有 1 到 root 这条路径上的点的答案会改变,所以我们可以特判一下这种情况,如果一个点不在 1 到 root 的路径上,就仍旧以 以 1 为根的答案为答案。
那如果在这条路径上,显然 root 答案为
#include<bits/stdc++.h> #define N 100005 #define ls u<<1 #define rs u<<1|1 using namespace std; int read(){ int x=0,f=1;char ch=getchar(); while(ch<'0'||ch>'9'){if(ch=='-')f=-f;ch=getchar();} while(ch>='0'&&ch<='9'){x=(x<<1)+(x<<3)+(ch^48);ch=getchar();} return x*f; } int n,T,tot,t,root=1; int a[N],dfn[N],d[N],gp[N],f[N][25]; int Head[N],to[N<<1],Next[N<<1]; int minn[N<<2],siz[N],val[N]; void add(int u,int v){ to[++tot]=v,Next[tot]=Head[u],Head[u]=tot; } void PushUp(int u){ minn[u]=min(minn[ls],minn[rs]); } void dfs(int x,int fa){ d[x]=d[fa]+1,f[x][0]=fa; dfn[x]=++t;siz[x]=1,val[t]=a[x]; for(int i=Head[x];i;i=Next[i]){ int y=to[i];if(y==fa) continue; dfs(y,x);siz[x]+=siz[y]; } } void Build(int u,int l,int r){ if(l>=r){minn[u]=val[l];return ;} int mid=(l+r)>>1; Build(ls,l,mid); Build(rs,mid+1,r); PushUp(u); } void UpDate(int u,int l,int r,int x,int k){ if(l==r){minn[u]=k;return ;} int mid=(l+r)>>1; if(x<=mid) UpDate(ls,l,mid,x,k); else UpDate(rs,mid+1,r,x,k); PushUp(u); } int Query(int u,int l,int r,int L,int R){ if(R<L) return 1e9; //边界处理 if(L<=l&&r<=R) return minn[u]; int mid=(l+r)>>1,ans=1e9; if(L<=mid) ans=min(ans,Query(ls,l,mid,L,R)); if(R>mid) ans=min(ans,Query(rs,mid+1,r,L,R)); return ans; } int lca(int x,int y){ if(d[x]>d[y]) swap(x,y); for(int j=20;j>=0;--j) if(d[f[y][j]]>d[x]) y=f[y][j]; if(f[y][0]==x) return y; return 0; } int main(){ n=read(),T=read(); for(int i=1;i<=n;++i){ int x=read();a[i]=read(); if(x!=0) add(x,i),add(i,x); } dfs(1,0); for(int j=1;j<=20;++j) for(int i=1;i<=n;++i) f[i][j]=f[f[i][j-1]][j-1]; Build(1,1,n); while(T--){ char opt[2]; int x,y; scanf("%s",opt+1);x=read(); if(opt[1]=='V'){ y=read();a[x]=y; UpDate(1,1,n,dfn[x],y); }else if(opt[1]=='E'){ root=x; }else{ if(root==x){printf("%d\n",minn[1]);continue;} int tt=lca(x,root); if(d[x]<d[root]&&tt) printf("%d\n",min(Query(1,1,n,1,dfn[tt]-1),Query(1,1,n,dfn[tt]+siz[tt],n))); else printf("%d\n",Query(1,1,n,dfn[x],dfn[x]+siz[x]-1)); } } return 0; }
C 倒水问题 :
有
设当前所有水杯中水量最多的一杯有
然后继续这个操作,直到所有水杯被倒空为止。设总共倒了
你可以通过不同的选择,让最终的
对于
输入样例
4 1 2 3 4 9
输出样例
2 1
分析
没想到居然是 dp,我还以为是贪心什么之类的,好吧,可能贪心贪不到最优解。
设
其中,
#include<bits/stdc++.h> #define N 200005 using namespace std; int read(){ int x=0,f=1;char ch=getchar(); while(ch<'0'||ch>'9'){if(ch=='-')f=-f;ch=getchar();} while(ch>='0'&&ch<='9'){x=(x<<1)+(x<<3)+(ch^48);ch=getchar();} return x*f; } int n,k; int a[N],f[N][35],b[N]; int main(){ n=read(),k=read(); for(int i=1;i<=n;++i) a[i]=read(); if(n==k) return printf("0 %d\n",k),0; sort(a+1,a+1+n); memset(f,0x3f,sizeof(f)); for(int i=0;i<=32;++i) f[0][i]=0; for(int i=1;i<=k;++i) f[i][0]=i; for(int i=1;i<=n;++i){ int x=upper_bound(a+1,a+1+n,a[i]/2)-a; b[i]=x-1; } for(int i=1;i<=n;++i) for(int j=1;j<=32;++j) f[i][j]=min(f[i-1][j]+1,f[b[i]][j-1]); for(int i=0;i<=32;++i) if(f[n][i]<=k) return printf("%d %d\n",i,f[n][i]),0; return 0; }
D 树的颜色 :
题面
我们有一棵有n个节点的树(节点编号
1 u v:将
2 u v:将
3 u v:求出
对于
输入样例
10 5 9 7 5 6 5 5 8 10 5 3 5 2 9 7 4 6 1 10 2 10 5 1 4 9 3 1 1 3 7 8 3 9 10 1 7 9 3 4 8 1 4 1 2 10 2 2 1 1
输出样例
0 1 0 3
分析
本文作者:南风未起
本文链接:https://www.cnblogs.com/jiangchen4122/p/17419342.html
版权声明:本作品采用知识共享署名-非商业性使用-禁止演绎 2.5 中国大陆许可协议进行许可。
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步