我需要一个答案,也许美满,也|

Aurora-JC

园龄:3年粉丝:3关注:4

2023-05-21 22:23阅读: 84评论: 0推荐: 0

【比赛】2022.11.23 51nod 图论专场?

A 反转Dag图

题面

给出一个 n 个点 m 条边的有向图,顶点编号 1n ,边的编号为 1m

你可以选择一些边进行反转(即从 uv 的边反转后变为从 vu 的边),将每条边反转都需要一定代价。最终使得整个图变成一个 Dag 图。

总的反转代价是由所有需要反转的边中,代价最高的那一条决定的,求达成目标的最小代价。

对于 100% 的数据,2n105 ,1m105,1c109

样例输入

5 6
2 1 1
5 2 6
2 3 2
3 4 3
4 5 5
1 5 4

样例输出

2

分析

这道题考试时候傻逼了,都已经想到正解了,不知道怎么被自己给否定了。

这道题要使最大边权最小,那么很容易想到二分这个最大边权,然后怎么做呢?我一开始想的是将边权小于这个二分值的边直接反转,这样肯定是错的,我举了个反例就否定了这个做法,现在只想骂自己傻逼。

其实对于边权小于二分值的边,怎么样反转都是可行的,我一直以为只能翻转一次,所以就错了,所以那些小的边绝对不会成为环的边,如果在环上,直接反转就行了,所以只要将大于二分值的边加进去,用tarjan判断是否有环就行了,(其实拓扑也行,只是我没想到)。

可能会有些疑惑,如果那些小边反转后成为另一个环的边怎么办呢?

其实没事,可以看下图,发现如果小边(5,1)翻不翻转都不会有什么影响,因为有一个大的环,所以大的环上也要有边反转。

code

#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 歪脖子树

题面

杭州有一棵 n 个点的歪脖子树(节点编号 1n),每个点有一个丑陋度 a[i]

它的长相过于奇怪以至于人们甚至不知道它的根在哪里,但大家初始时都假定节点 1 为根。

为了验证,植物学家们做了 T 次实验,每次实验形如下列三者之一:

V x y:把点 x 的丑陋度改成 y

E x:将 x 设为新的树根;

Q x:查询 x 的子树中丑陋度的最小值;

对于 100% 的数据, 2n105,1T105,0v104

输入样例

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 答案为 1n 整段区间的答案,其余点的答案为 1n 整段区间 的减去 有 root 的子树的区间,显然 以 root 的子树是一段连续的区间,那么答案就是不带这个区间的两段区间。

code

#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 倒水问题 :

n 个水杯,水杯中分别有 a[i] 升水,你采用以下方式把所有水杯倒空。

设当前所有水杯中水量最多的一杯有 g 升,你可以把所有水量在 g/2 (下取整)以上的水杯都倒空。

然后继续这个操作,直到所有水杯被倒空为止。设总共倒了 h 次。 不过在干这个事情之前,你可以先选出至多 k 个水杯,预先将其中的水都倒空。

你可以通过不同的选择,让最终的 h 尽可能小。问最小的 h 是多少,同时输出预先倒空的水杯数量(有可能小于 k)?

对于 100% 的数据,1n2×105,0kn,1ai109

输入样例

4 1
2 3 4 9

输出样例

2 1

分析

没想到居然是 dp,我还以为是贪心什么之类的,好吧,可能贪心贪不到最优解。

fi,j 表示倒到 第 i 杯水(第 i 杯水也被倒了),倒了 j 组水,用了 fi,j 的事先倒水次数,考虑转移方程:

fi,j={fi1,j+1ft,j1

其中,t 是小于等于 ai2 的数的最大下标,因为值域不超过 1e9 ,所以 j 最多为 32,所以复杂度 O(32n)t 可以预处理出来。最后答案显而易见。

#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个节点的树(节点编号 1n),每条边要么是白色,要么是黑色,一开始所有的边都是白色,需要支持三种操作:

1 u v:将 uv 路径上所有的边的颜色改变,即黑变白,白变黑;

2 u v:将 uv 路径上所有相邻的边的颜色改变,相邻的边表示仅有一个端点在该路径上的边;

3 u v:求出 uv 路径上黑色边的数量。

对于 100% 的数据,1n,q100000

输入样例

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 中国大陆许可协议进行许可。

posted @   Aurora-JC  阅读(84)  评论(0编辑  收藏  举报
点击右上角即可分享
微信分享提示
评论
收藏
关注
推荐
深色
回顶
收起