Codeforce 1700Difficulty Graphs 20 questions

1.CF427C Checkposts

题意:建立一些站点,如果有一个检查站在i路口,保护j的条件是:i==j或者警察巡逻车可以从i走到j,并且能回到i。求最小花费和用最少点的方案数

显然是强连通分量

在跑tarjan的过程中记录每个强连通分量中cost的最小值,在输出时遍历累乘即可

要开longlong,wa了两发

#include<bits/stdc++.h>
#define int long long
using namespace std;
constexpr int maxn=3e5+5;
constexpr int mod=1e9+7;
int n,m;
int cost[maxn],dfn[maxn],low[maxn],z[maxn],tot,k,vis[maxn];
int t,scc[maxn],cnt[maxn],top,res[maxn];
vector<int>e[maxn];
vector<int>jl[maxn];
void tarjan(int u){
	dfn[u]=low[u]=++tot;
	z[++top]=u;//入栈 
	vis[u]=1;
	for(auto v:e[u]){
		if(!dfn[v]){
			tarjan(v);
			low[u]=min(low[u],low[v]);
		}
		else if(vis[v]){
			low[u]=min(low[u],dfn[v]);
		}
	}
	if(low[u]==dfn[u]){
		t++;//连通分量的标号 
		do{
			scc[z[top]]=t;    //属于这个连通分量 
			cnt[t]++;         //记录这个环中有多少个点
			res[t]=min(res[t],cost[z[top]]);
			jl[t].push_back(z[top]);
			vis[z[top]]=0; 
			top--;              //出栈 
		}while(u!=z[top+1]);
	}
}

signed main(){
	cin>>n;
	memset(res,0x3f,sizeof res);
	for(int i=1;i<=n;i++){
		cin>>cost[i];
	}
	cin>>m;
	for(int i=1;i<=m;i++){
		int u,v;
		cin>>u>>v;
		e[u].push_back(v);
	}
	for(int i=1;i<=n;i++){
		if(!dfn[i])tarjan(i);
	}
	int ans=0,w=1;
	for(int i=1;i<=t;i++){
		ans+=res[i];
		int ww=0;
		for(auto v:jl[i]){
			if(cost[v]==res[i])ww++;
		}
		w=((w*ww)%mod);
	//	printf("cnt = %d\n",cnt[i]);
	}
	printf("%lld %lld",ans,w);
	return 0;
}

2.CF1406C Link Cut Centroids

题意:给定一棵节点数为 n 的树 , 删一条边然后加上一条边 , 使得该树的重心唯一 。(删掉的边和加上的边可以是同一条)

重心最多只能有两个

如果只有一个重心,输出唯一重心上的任意一条边两次即可

如果有两个重心,把b重心的上任意一条非跟a重心相连的边连到a重心上即可

#include<bits/stdc++.h>
using namespace std;
vector<int>e[200005];
int n,m,t,maxn1,maxn2,zx1,zx2;
int d[200005];
void dfs(int s,int f){
	d[s]=1;
	int res=0;
	for(auto v:e[s]){
		if(v==f)continue;
		dfs(v,s);
		d[s]+=d[v];
		res=max(res,d[v]);
	}
	res=max(res,n-d[s]);
	if(res<maxn1){
		maxn1=res;
		zx1=s;
	}
	if(res==maxn1&&s!=zx1){
		maxn2=maxn1;
		zx2=s;
	}
}
int main(){
	cin>>t;
	while(t--){
		cin>>n;
		maxn1=0x3f3f3f3f,maxn2=0x3f3f3f3f+1;
		memset(d,0,sizeof d);
		for(int i=1;i<=n;i++){
			e[i].clear();
		}
		for(int i=1;i<=n-1;i++){
			int x,y;
			cin>>x>>y;
			e[x].push_back(y);
			e[y].push_back(x);
		}
		dfs(1,0);
		int f=0;
		if(maxn1!=maxn2){
			for(auto v:e[zx1]){
				printf("%d %d\n",zx1,v);
				printf("%d %d\n",zx1,v);
				f=1;
				break;
			}
			if(f)continue;
		}
		else if(maxn1==maxn2){
			for(auto v:e[zx2]){
				if(v!=zx1){
					printf("%d %d\n",zx2,v);
					printf("%d %d\n",v,zx1);
					f=1;
					break;
				}
			}
		}
	}
	return 0;
}

3.CF1209D Cow and Snacks

n 种花,k 个客人,每个人喜欢两种编号不同的花。但是每种花在花店里只有一束。

客人按顺序进入花店,会买走所有她喜欢且仍在店铺里的花。如果一个客人买不到任何一束花,那么她就会十分沮丧导致变成肥宅。现在你可以自己安排这 n 个人的顺序,使得肥宅的数量最小。

并查集,

#include<bits/stdc++.h>
using namespace std;
vector<int>e;
int n,m;
const int maxn=2e5+5;
int f[maxn],vis[maxn];
int find(int x){
	if(f[x]==x)return x;
	else return f[x]=find(f[x]);
}
inline void hb(int x,int y){
	int fx=find(x);
	int fy=find(y);
	f[fx]=fy;
}
int main(){
	cin>>n>>m;
	for(int i=1;i<=n;i++){
		f[i]=i;
	}
	for(int i=1;i<=m;i++){
		int x,y;
		cin>>x>>y;
		hb(x,y);
	}
	int cnt=0;
	for(int i=1;i<=n;i++){
		if(f[i]==i)cnt++;
	}
	cout<<m-n+cnt;
} 

4.CF489D Unbearable Controversy of Being

题意:在一个有向图中,求类似于上图的菱形,即4个点 a , b , c , d a,b,c,d a,b,c,d 同时存在 ( a , b ) , ( a , d ) , ( b , c ) , ( d , c ) (a,b),(a,d),(b,c),(d,c) (a,b),(a,d),(b,c),(d,c)四条边组成的菱形的个数

思路:遍历求出每个点距离为2的路径数,再用组合数 n选2

可以表示成 a ( a − 1 ) 2 \dfrac {a(a-1)}2 2a(a1)

#include<bits/stdc++.h>
#define int long long
using namespace std;
constexpr int maxn=3005,inf=0x3f3f3f3f;
constexpr int dx[]={1,-1,0,0};
constexpr int dy[]={0,0,1,-1};
int n,m,t,k;
vector<int>e[maxn];
int cnt[maxn][maxn];
signed main(){
	cin>>n>>m;
	for(int i=1;i<=m;i++){
		int u,v;
		cin>>u>>v;
		e[u].push_back(v);
	}
	for(int i=1;i<=n;i++){
		for(auto u:e[i]){
			for(auto v:e[u]){
				if(i==v)continue;
				cnt[i][v]++;
			}
		}
	}
	int ans=0;
	for(int i=1;i<=n;i++){
		for(int j=1;j<=n;j++){
			ans+=(cnt[i][j]-1)*cnt[i][j]/2;
		}
	}
	cout<<ans;
	return 0;
}

5.CF1176E Cover it!

题意:给你一张 n n n个点 m m m条边的无向连通图(没有边权)。保证图中没有重边和自环

你的任务是选择至多 ⌊ n 2 ⌋ \lfloor\frac{n}{2}\rfloor 2n个点使得对于任意一个没有被选择的点都和至少一个选中的点相邻(换言之,通过一条边连接)

保证答案存在,如果有多个答案,输出任意一个

你将要回答多个独立的询问

思路:二分图染色
题目保证有解并且图连通,不用判断无解情况

#include<bits/stdc++.h>
using namespace std;
constexpr int maxn=2e5+5,inf=0x3f3f3f3f;
constexpr int dx[]={1,-1,0,0};
constexpr int dy[]={0,0,1,-1};
int n,m,t,k;
vector<int>e[maxn];
int cnt,color[maxn];
inline void init(){
	cnt=0;
	for(int i=1;i<=n;i++){
		e[i].clear();
		color[i]=-1;
	}
}
void dfs(int u){
	color[u]=0;
	for(auto v:e[u]){
		if(color[v]!=-1)continue;
		dfs(v);
		if(!color[v])color[u]=1;	
	}
}
signed main(){
	cin>>t;
	while(t--){
		cin>>n>>m;
		init();
		int mm=0,nb=0;
		for(int i=1;i<=m;i++){
			int u,v;
			cin>>u>>v;
			e[u].push_back(v);
			e[v].push_back(u);
		}
		dfs(1);
		for(int i=1;i<=n;i++){
			cnt+=color[i];
		}
		cout<<cnt<<endl;
		for(int i=1;i<=n;i++){
			if(color[i]){
				cout<<i<<" ";
			}
		}
		cout<<endl;
	}
	return 0;
}

6.CF1144F Graph Without Long Directed Paths

题意:给你一个无向图,把所有边标记方向,并使整张图中没有距离 >=2 的路径,问你是否存在并输出方案。

第一行输出“YES”或“NO”,若存在,第二行按照读入顺序输出连边方向

思路:二分图染色

既然距离不能达到2,那就白点向黑点连,判断有解即可

#include<bits/stdc++.h>
using namespace std;
constexpr int maxn=2e5+5,inf=0x3f3f3f3f;
constexpr int dx[]={1,-1,0,0};
constexpr int dy[]={0,0,1,-1};
int n,m,t,k;
vector<int>e[maxn];
int cnt,color[maxn],vis[maxn]; 
int a[maxn],b[maxn];
void dfs(int u){
	vis[u]=1;
	for(auto v:e[u]){
		if(!vis[v]){
			color[v]=color[u]^1;
			dfs(v);
		}	
		else if(color[v]==color[u]){
			cout<<"NO";
			exit(0);
		}
	}
}
signed main(){
	cin>>n>>m;
	for(int i=1;i<=m;i++){
		int u,v;
		cin>>u>>v;
		a[i]=u;
		b[i]=v;
		e[u].push_back(v);
		e[v].push_back(u);
	}
	for(int i=1;i<=n;i++){
		if(!vis[i])dfs(i);
	}
	cout<<"YES"<<endl;
	for(int i=1;i<=m;i++){
		cout<<color[a[i]];
	}
	return 0;
}

7.CF1093D Beautiful Graph

题意:给你一个 n n n 个点 m m m 条边的无向图。

你需要给每个点一个点权,使得每条边连接的两个点点权奇偶不同。点权的值域为 { 1 , 2 , 3 } \{1,2,3\} {1,2,3}

请求出方案数对 998244353 998244353 998244353 取模的结果。

二分图染色,用上简单的组合,开longlong,处处求mod,一个地方不注意就炸哭了

#include<bits/stdc++.h>
using namespace std;
#define int long long
constexpr int maxn=3e5+5,inf=0x3f3f3f3f,mod=998244353;
int n,m,t;
vector<int>e[maxn];
int ans1=0,ans2=0;
int cnt,color[maxn],vis[maxn];
int flag=0;
int Pow(int x, int k){ int ans=1;for(;k;k>>=1,x=(int)x*x%mod) if(k&1) ans=(int)ans*x%mod;return ans;}
void dfs(int u,int tag){
	if(flag)return;
	if(color[u])ans1++;
	else ans2++;
	vis[u]=tag;
	for(auto v:e[u]){
		if(!vis[v]){
			color[v]=color[u]^1;
	    	dfs(v,tag);
		}
		else if(color[v]==color[u]){
	    	flag=1;
	    	return;
    	}
	}
}
signed main(){
	cin>>t;
	while(t--){
		cin>>n>>m;
		for(int i=1;i<=n;i++){
			e[i].clear();
			vis[i]=0;
			color[i]=0;
		}
		flag=0;
		cnt=0;
		for(int i=1;i<=m;i++){
			int u,v;
			cin>>u>>v;
			e[u].push_back(v);
			e[v].push_back(u);
		}
		int ans=1;
		for(int i=1;i<=n;i++){
			if(!vis[i]){
				++cnt;
				ans1=0,ans2=0;
				dfs(i,cnt);
		    	ans=(int)ans*(Pow(2,ans1)%mod+Pow(2,ans2)%mod)%mod;
			}
		}
		if(flag){
			cout<<"0"<<endl;
			continue;
		}
		cout<<ans<<endl;
	}
	return 0;
}

8.CF1027D Mouse Hunt

伯兰州立大学的医学部刚刚结束了招生活动。和以往一样,约80%的申请人都是女生并且她们中的大多数人将在未来4年(真希望如此)住在大学宿舍里。

宿舍楼里有 n n n个房间和一只老鼠!女孩们决定在一些房间里设置捕鼠器来除掉这只可怕的怪物。在 i i i号房间设置陷阱要花费 c i c_i ci伯兰币。房间编号从 1 1 1 n n n

要知道老鼠不是一直原地不动的,它不停地跑来跑去。如果 t t t秒时它在 i i i号房间,那么它将在 t + 1 t+1 t+1秒时跑到 a i a_i ai号房间,但这期间不会跑到别的任何房间里( i = a i i=a_i i=ai表示老鼠没有离开原来的房间)。时间从 0 0 0秒开始,一旦老鼠窜到了有捕鼠器的房间里,这只老鼠就会被抓住。

如果女孩们知道老鼠一开始在哪里不就很容易吗?不幸的是,情况不是这样,老鼠在第 0 0 0秒时可能会在从 1 1 1 n n n的任何一个房间内。

那么女孩们最少要花多少钱设置捕鼠器,才能保证老鼠无论从哪个房间开始流窜最终都会被抓到?

思路:因为有n个点n条边,所以一定会存在环,每个点只有一条出边,所以这个环一定是在链的末尾,先用拓扑排序把环外的点消去,再在森林里找到各个环内cost最小的点,相加即可

#include<bits/stdc++.h>
using namespace std;
constexpr int maxn=2e5+5,inf=0x3f3f3f3f;
int n,m,cnt;
int du[maxn],c[maxn],vis[maxn],ans[maxn];
vector<int>e[maxn];
void topo(){
	queue<int>q;
	for(int i=1;i<=n;i++){
		if(du[i]==0){
			q.push(i);
		}
	}
	while(!q.empty()){
		int u=q.front();
		q.pop();
		for(auto v:e[u]){
			du[v]--;
			if(du[v]==0)q.push(v);
		}
	}
}
void dfs(int u,int tag){
	if(vis[u]||!du[u])return;
	vis[u]=1;
	ans[tag]=min(ans[tag],c[u]);
	for(auto v:e[u]){
		dfs(v,tag);
	}
}
int main(){
	cin>>n;
	for(int i=1;i<=n;i++){
		cin>>c[i];
	}
	for(int i=1;i<=n;i++){
		int v;
		cin>>v;
		du[v]++;
		e[i].push_back(v);
	}
	topo();
	memset(ans,inf,sizeof ans);
	for(int i=1;i<=n;i++){
		if(!vis[i]&&du[i])dfs(i,++cnt);
	}
	int res=0;
	for(int i=1;i<=cnt;i++){
		res+=ans[i];
	}
	cout<<res;
}

9.CF1320B Navigation System

给出 2 ≤ n ≤ 2 ⋅ 1 0 5 2\le n\le2\cdot 10^5 2n2105 个节点, 2 ≤ m ≤ 2 ⋅ 1 0 5 2\le m\le2\cdot 10^5 2m2105 条边的有向图,路径 p 1 , ⋯   , p k p_1,\cdots,p_k p1,,pk,路径中没有重复元素,边 ( p i , p i + 1 ) (p_i,p_{i+1}) (pi,pi+1) 总是存在。

定义 s = p 1 , t = p k s=p_1,t=p_k s=p1,t=pk

有一个导航系统,若当前在节点 u u u,会构造一条从 u u u t t t 的最短路径(这种路径可能不止一条,但导航系统只会选其中一条),设导航系统规划的下一个节点为 w w w,实际行走的下一个节点为 v v v

  • w = v w=v w=v 不会触发重构。
  • w ≠ v w\neq v w=v 会触发重构。

实际行走路线为 p p p,求可能的最少重构次数和最多重构次数。

思路:若当前点在最短路上,该点连边的点中,有其他点也为最短路上的点,可以选择重构,最大重构++

若当前点不在最短路上,因为必须重构,所以最大最小都++

建一个反图跑bfs,求出每一点到终点的最短路

#include<bits/stdc++.h>
using namespace std;
constexpr int maxn=2e5+5,inf=0x3f3f3f3f;
int n,m,k,cnt;
int dis[maxn],a[maxn],vis[maxn],s,t;
vector<int>e[maxn],r[maxn];
void bfs(){
	dis[s]=0;
	queue<int>q;
	q.push(s);
	while(!q.empty()){
		int u=q.front();
		q.pop();
		for(auto v:r[u]){
			if(!dis[v]){
				dis[v]=dis[u]+1;
				q.push(v);
			}
		}
	}
}
int main(){
	cin>>n>>m;
	for(int i=1;i<=m;i++){
		int u,v;
		cin>>u>>v;
		e[u].push_back(v);
		r[v].push_back(u);
	}
	cin>>k;
	for(int i=1;i<=k;i++){
		cin>>a[i];
	}
	s=a[k],t=a[1];
	bfs();
	int ans1=0,ans2=0;
	for(int i=1;i<k;i++){
		if(dis[a[i]]==dis[a[i+1]]+1){
			for(auto v:e[a[i]]){
				if(v!=a[i+1]&&dis[v]==dis[a[i+1]]){
					ans2++;
				//	cout<<"!"<<endl;
					break;
				}
			}
		}
		else{
			ans1++,ans2++;
		}
	}
	cout<<ans1-1<<" "<<ans2-1;
}

10.CF698B Fix a Tree

题意:给出n个点的父亲,(若父亲为自己则为根节点)求出最少改掉多少个点的父亲可以使其成为一颗有根树

思路:用并查集判断树or环的个数,分两种情况:

若森林中有符合条件的树,选一个树根作为总根,将其他树or环的代表点连到根上即可输出,cnt-1

若森林中只有环,选一个环的代表点作为根,将其他的环的代表点连到根上即可,输出cnt(因为作为根的环把父节点改成了自己)

#include<bits/stdc++.h>
using namespace std;
constexpr int maxn=2e5+5,inf=0x3f3f3f3f;
int n,m,k,cnt,root;
int f[maxn],a[maxn],vis[maxn];
vector<int>e[maxn];
int find(int x){
	if(f[x]==x)return x;
	else return f[x]=find(f[x]);
}
inline void hb(int x,int y){
	int fx=find(x);
	int fy=find(y);
	f[fx]=fy;
}
int main(){
	cin>>n;
	for(int i=1;i<=n;i++){
		f[i]=i;
	}
	for(int i=1;i<=n;i++){
		cin>>a[i];
		hb(i,a[i]);
	}
	for(int i=1;i<=n;i++){
		if(f[i]==i&&a[i]==i){
			root=i;
		}
	}
	if(root){
		for(int i=1;i<=n;i++){
			if(f[i]!=i)continue;
			cnt++;
			if(a[i]==i&&i!=root){
				a[i]=root;
			}
			else a[i]=root;
		}
		cout<<cnt-1<<endl;
	}
	else{
		for(int i=1;i<=n;i++){
			if(f[i]!=i)continue;
			cnt++;
			if(!root)root=i;
			a[i]=root;
		}
		cout<<cnt<<endl;
	}
	for(int i=1;i<=n;i++){
		printf("%d ",a[i]);
	}
	return 0;
}

11.CF813C The Tag Game

题意:有一颗以1为根,节点数为n的树,A在1号点,B在x号点,A,B交替行动,A的目标是行动次数尽量少,B的目标是行动次数尽量多,问在这种情况下,行动的次数.

思路:求出两个点到所有的点的最短时间,a先动,所以只要a到的时间比b要早就追不上,遍历所有点,更新最大时间即可

#include<bits/stdc++.h>
using namespace std;
constexpr int maxn=2e5+5;
int n,m,t,k,x,cnt,nb,mm;
int dis1[maxn],dis2[maxn];
vector<int>e[maxn];
void dfs1(int u,int fa){
	for(auto v:e[u]){
		if(v==fa)continue;
		dis1[v]=dis1[u]+1;
		dfs1(v,u);
	}
}
void dfs2(int u,int fa){
	for(auto v:e[u]){
		if(v==fa)continue;
		dis2[v]=dis2[u]+1;
		dfs2(v,u);
	}
}
int main(){
	cin>>n>>x;
	for(int i=1;i<=n-1;i++){
		int u,v;
		cin>>u>>v;
		e[u].push_back(v);
		e[v].push_back(u);
	}
	dfs1(x,x);
	dfs2(1,1);
	int ans=0;
	for(int i=2;i<=n;i++){
		if(dis1[i]<dis2[i]){
			ans=max(ans,dis2[i]*2);
		}
	}
	cout<<ans;
}

12.CF229B Planets

题意:在宇宙里有 n n n 个星球,分别编号为 1 , 2 , . . . , n 1,2,...,n 1,2,...,n 。Jack现在在 1 1 1 号星球上,他要去 n n n 号星球。已知一些星球之间有双向的传送通道,Jack可以通过这些传送通道移动。每次传送需要一些时间,在不同的星球之间传送也可能需要不同时间。

当有其他人在使用这个星球的传送通道时,Jack无法离开这个星球。比如,如果有人在 t t t 时刻使用通道,那Jack只能在 t + 1 t+1 t+1 时刻离开(如果t+1时刻没有人在使用通道)。

现在,Jack想请你计算他最早可以在哪个时刻到达 n n n 号星球。Jack在0时刻出发。

思路:dij,到达每个点要停留一定的时间,用map保存每个点的哪个时间需要停留

#include<bits/stdc++.h>
#define int long long
using namespace std;
typedef pair<int,int>pii;
constexpr int maxn=1e5+5,inf=0x3f3f3f3f;
struct edge{
	int v,w;
};
vector<edge>e[maxn];
map<pair<int,int>,int>wait;
int n,m,k,t,cnt,mm,dis[maxn],vis[maxn];
bool judge(){
	priority_queue<pii,vector<pii>,greater<pii>>q;
	memset(dis,inf,sizeof dis);
	memset(vis,0,sizeof vis);
	dis[1]=0;
	q.push({0,1});
	while(!q.empty()){
		pii x=q.top();
		q.pop();
		int u=x.second;
		int w=x.first;
		if(vis[u])continue;
		vis[u]=1;
		while(wait[{u,w}]){
			w++;
		}
		for(auto ss:e[u]){
			int v=ss.v;
			if(dis[v]>w+ss.w){
				dis[v]=w+ss.w;
				q.push({dis[v],v});
			}
		}
	}
	return true;
}
signed main(){
	cin>>n>>m;
	for(int i=1;i<=m;i++){
		int u,v,w;
		scanf("%lld%lld%lld",&u,&v,&w);
		e[u].push_back((edge){v,w});
		e[v].push_back((edge){u,w});
    }
    for(int i=1;i<=n;i++){
    	cin>>k;
    	for(int j=1;j<=k;j++){
    		int res;
    		scanf("%lld",&res);
    		mm=max(mm,res);
    		wait[{i,res}]=1;
		}
	}
	int ans=0;
	bool sb=judge();
	ans=dis[n];
	if(ans<inf)cout<<ans;
	else cout<<-1;
	return 0;
}

13.CF1037D Valid BFS?

给定一个 n ( 1 ≤ n ≤ 2 ⋅ 1 0 5 ) n(1\leq n \leq 2\cdot10^5) n(1n2105)个节点的树的 n − 1 n-1 n1条边和这棵树的一个 B F S BFS BFS a 1 , a 2 , … , a n a_1,a_2,\dots,a_n a1,a2,,an,判断这个 B F S BFS BFS序是否是一个从节点 1 1 1开始的合法 B F S BFS BFS序,若合法则输出 Y e s Yes Yes,否则输出 N o No No

思路:把题目要求的bfs序列保存,对树的边进行排序,bfs序列中出现早的点排前面,再进行bfs,如果有出现点不符合该bfs序则直接跳出,否则合法

#include<bits/stdc++.h>
using namespace std;
constexpr int maxn=2e5+5;
int n,m,k,t,cnt;
int a[maxn],b[maxn],vis[maxn];
vector<int>e[maxn];
inline void bfs(){
	queue<int>q;
	q.push(1);
	vis[1]=1;
	while(!q.empty()){
		int u=q.front();
		q.pop();
		if(u!=a[++cnt]){
			printf("No\n");
			exit(0);
		}
		for(auto v:e[u]){
			if(!vis[v]){
				vis[v]=1;
				q.push(v);
			}
		}
	}
	printf("Yes\n");
}
int main(){
	cin>>n;
	for(int i=1;i<n;i++){
		int u,v;
		cin>>u>>v;
		e[u].push_back(v);
		e[v].push_back(u);
	}
	for(int i=1;i<=n;i++){
		cin>>a[i];
		b[a[i]]=i;
	}
	for(int i=1;i<=n;i++){
		sort(e[i].begin(),e[i].end(),[](int x,int y){return b[x]<b[y];});
	}
	bfs();
}

14.CF1383A String Transformation 1

题意:有字符串 A A A B B B,每次在 A A A中选取若干个相同的字母(设为 x x x),改成另一个字母(设为 y y y),需要满足 x < y x<y x<y,问将A改成B的最少操作

思路:一开始看错题目了,以为要连续的…结果是任意相同的子序列就可以,直接并查集即可,相当于不同字母间连了多少次边就需要改变多少次,a->b->c->e->k,并查集合并次数即为答案

#include<bits/stdc++.h>
using namespace std;
constexpr int maxn=2e5+5,inf=0x3f3f3f3f;
int n,m,t,k,cnt;
int f[30];
int find(int x){
	if(f[x]==x)return x;
	else return f[x]=find(f[x]);
}
void hb(int x,int y){
	int fx=find(x);
	int fy=find(y);
	if(fx!=fy){
		cnt++;
		f[fx]=fy;
	}
}
int main(){
	cin>>t;
	while(t--){
		cin>>n;
		string a1,a2;
		cin>>a1>>a2;
		for(int i=0;i<30;i++){
			f[i]=i;
		}
		int flag=0;
		for(int i=0;i<n;i++){
			if(a1[i]>a2[i]){
				flag=1;
				break;
			}
			else if(a1[i]!=a2[i]){
				hb(a1[i]-'a'+1,a2[i]-'a'+1);
			}
		}
		if(flag)cout<<"-1"<<endl;
		else cout<<cnt<<endl;
		cnt=0;
	}
}

15.CF295B Greg and Graph

Greg有一个有边权的有向图,包含 n n n 个点。这个图的每两个点之间都有两个方向的边。Greg喜欢用他的图玩游戏,现在他发明了一种新游戏:

  • 游戏包含 n n n 步。
  • i i i 步Greg从图中删掉编号为 x i x_i xi 的点。当删除一个点时,这个点的出边和入边都会被删除。
  • 在执行每一步之前,Greg想知道所有点对间最短路长度的和。最短路可以经过任何没删掉的点。换句话说,如果我们假设 d ( i , v , u ) d(i, v, u) d(i,v,u) 是在删掉 x i x_i xi v v v u u u 间的最短路长度,那么Greg想知道下面这个求和的值: ∑ v , u , v ≠ u d ( i , v , u ) \sum_{v, u, v \neq u} d(i, v, u) v,u,v=ud(i,v,u)

帮帮Greg,输出每一步之前要求的值。

思路:明示要用Floyd,可是又不可能每次删掉一个点再求一次,怎么办呢?这里要用逆向思维(之前kb专题有一题也是同样的思路),从后删除的边往前加边,这样就避免了每删除一次要重新计算整张图的麻烦,而且Floyd的过程跟松弛顺序是无关的,依次加入这些点,并标记为可行点,对整张图求最短路和,如果两点都为可行点则加上这两点的最短路

(题目提示了要开longlong)

#include<bits/stdc++.h>
#define int long long
using namespace std;
constexpr int maxn=2e5+5,inf=0x3f3f3f3f;
int n,m,t,k,cnt,ans[maxn];
int dis[505][505],vis[505],now[505];
signed main(){
	cin>>n;
	for(int i=1;i<=n;i++){
		for(int j=1;j<=n;j++){
			cin>>dis[i][j];
		}
	}
	for(int i=1;i<=n;i++){
		cin>>vis[i];
	}
	for(int u=n;u>=1;u--){
		int k=vis[u];
		now[k]=1;
		for(int i=1;i<=n;i++){
			for(int j=1;j<=n;j++){
				if(i==j)continue;
				dis[i][j]=min(dis[i][j],dis[i][k]+dis[k][j]);
			}
		}
		for(int i=1;i<=n;i++){
			for(int j=1;j<=n;j++){
				if(i==j)continue;
				if(now[i]&&now[j])ans[u]+=dis[i][j];
			}
		}
	}
	for(int i=1;i<=n;i++){
		cout<<ans[i]<<" ";
	}
	return 0;
}

16.CF1253D Harmonious Graph

定义一张无向图是和谐的当且仅当:假设图中存在一条从 l l l r r r 的路径,则图中也存在从 l l l l + 1 , l + 2 , ⋯   , r − 1 l+1,l+2,\cdots,r-1 l+1,l+2,,r1 的路径。

给出一张无向图,求至少需要添加多少条边才能将其变为和谐的。

思路:求每个联通块最大编号,若后一个联通块的最小编号小于这个联通块的最大编号,则内部一定有相交部分,需要相连,ans++

#include<bits/stdc++.h>
using namespace std;
constexpr int maxn=2e5+5,inf=0x3f3f3f3f;
int n,m,k,t,cnt,mm;
vector<int>e[maxn];
int vis[maxn],ye[maxn];
void dfs(int u){
	vis[u]=1;
	mm=max(mm,u);
	for(auto v:e[u]){
		if(!vis[v]){
			dfs(v);
		}
	}
}
int main(){
	cin>>n>>m;
	for(int i=1;i<=m;i++){
		int u,v;
		cin>>u>>v;
		e[u].push_back(v);
		e[v].push_back(u);
	}
	int ans=0;
	for(int i=1;i<=n;i++){
		if(!vis[i]){
			if(mm>i)ans++;
			dfs(i);
		}
	}
	cout<<ans;
	return 0;
} 

17.CF219D Choosing Capital for Treeland

题目描述

Treeland国有n个城市,这n个城市连成了一颗树,有n-1条道路连接了所有城市。每条道路只能单向通行。现在政府需要决定选择哪个城市为首都。假如城市i成为了首都,那么为了使首都能到达任意一个城市,不得不将一些道路翻转方向,记翻转道路的条数为k。你的任务是找到所有满足k最小的首都。

乍一看想到暴力搜,然而2e5的次方肯定t,然后写了一个假的dfs,去看了题解发现是换根dp,去学了一下,换根dp的思路是找一个点作为根进行一遍dfs预处理出信息,然后再在根上进行二次dfs根据公式进行递推每个节点的信息,从而避免了对每个节点都做根遍历的巨大复杂度。

回到这题,题目给的是有向边,但是用有向边是没法遍历整棵树的,所以我们先要存成无向边,加上边权,1代表是正向,0代表是反向,在第一遍dfs的统计中,如果走到的是反向边,则需要翻转,ans++。

在第二遍dfs中,每个节点需要翻转的次数可以由其父亲推导而来,画个图在这里插入图片描述
1做完第一遍dfs后,f[1]=2,开始第二遍dfs
点1到点4,是正向边,f[4]=f[1]+1=3
点4到点3,是反向边,f[3]=f[4]-1=2
点4到点2,是反向边,f[2]=f[4]-1=2
所以答案是
2
1 2 3

#include<bits/stdc++.h>
using namespace std;
constexpr int maxn=2e5+5,inf=0x3f3f3f3f;
int n,m,k,t,cnt,f[maxn];
struct edge{
	int v,w;
};
vector<edge>e[maxn];
int dfs(int u,int fa){
	int ans=0;
	for(auto x:e[u]){
		int v=x.v;
		int w=x.w;
		if(v==fa)continue;
		if(!w)ans++;
		ans+=dfs(v,u);
	}
	return ans;
}
void dfs2(int u,int fa){
	cnt=min(f[u],cnt);
	for(auto x:e[u]){
		int v=x.v;
		int w=x.w;
		if(v==fa)continue;
		f[v]=f[u]+(w==1?1:-1);
		dfs2(v,u);
	}
}
int main(){
	cin>>n;
	cnt=inf;
	for(int i=1;i<=n-1;i++){
		int u,v;
		cin>>u>>v;
		e[u].push_back((edge){v,1});
		e[v].push_back((edge){u,0});
	}
	f[1]=dfs(1,0);
	dfs2(1,0);
	cout<<cnt<<endl;
	for(int i=1;i<=n;i++){
		if(f[i]==cnt){
			printf("%d ",i);
		}
	}
	return 0;
}

18.咕了

19.咕了

20.咕了

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值