专题:CF图论杂题

题目是来自HZW的博客(构造题我是各种不会...)

Solved 1 / 1 A CodeForces 500A New Year Transportation
Solved 1 / 1 B CodeForces 437C The Child and Toy
Solved 1 / 2 C CodeForces 510C Fox And Names
Solved 1 / 3 D CodeForces 475B Strongly Connected City
Solved 1 / 2 E CodeForces 639B Bear and Forgotten Tree 3
Solved 1 / 3 F CodeForces 623A Graph and String
Solved 1 / 20 G CodeForces 449B Jzzhu and Cities
Solved 1 / 4 H CodeForces 543B Destroying Roads

A

给一些线性排列的节点,每个节点有向后面第ai个节点的单向边,问是否能从1到T

1n,ai10^5

//直接模拟,1能到的点,所能到的点也是能到的,最后检查t
//
#include <string.h> #include <iostream> #include <algorithm> #include <stdio.h> using namespace std; const int maxn=1e5+10; const int maxm=1e6+10; const int INF=0x3f3f3f3f; int casn,n,m,k; int num[maxn]; int vis[maxn]; int main(){ //#define test #ifdef test freopen("in.txt","r",stdin);freopen("out.txt","w",stdout); #endif while(cin>>n>>m){ memset(vis,0,sizeof vis); memset(num,0,sizeof num); for(int i=1;i<n;i++){ cin>>num[i]; } vis[1]=1; for(int i=1;i<n;i++){ if(!vis[i])continue; vis[i+num[i]]=true; } if(vis[m])puts("YES"); else puts("NO"); } #ifdef test fclose(stdin);fclose(stdout);system("out.txt"); #endif return 0; }

B

n个点.m条边的无向图,输入保证不含重边

删除一个点的代价是与这个点相邻的剩余点的权值和,求将所有点删除的最小代价

/*
很巧妙的思路,核心点在于,假设a,b两点相连
若先删除a,最终代价就是 b的权值+删除剩下所有点的花费
若先删除b,最终代价就是 a的权值+删除剩下所有点的花费
这个规则对于所有的相邻点都成立,简单来说,我们可以通过改变删除顺序,贪心的得到最小的代价
简单来说,对于任意一个边,其权值较大的一个端点就是我们先要删除的,也就是说代价是所有边中权值较小的一端
*/
#include <string.h>
#include <iostream>
#include <algorithm>
#include <stdio.h>
using namespace std;
const int maxn=1e5+10;
const int maxm=1e6+10;
const int INF=0x3f3f3f3f;
int casn,n,m,k;
int num[maxn];
int vis[maxn];

int main(){
//#define test
#ifdef test
	freopen("in.txt","r",stdin);freopen("out.txt","w",stdout);
#endif

	while(cin>>n>>m){
		int ans=0;
		for(int i=1;i<=n;i++) cin>>num[i];
		while(m--){
			int a,b;
			cin>>a>>b;
			ans+=min(num[a],num[b]);
		}
		cout<<ans<<endl;
	}

#ifdef test
	fclose(stdin);fclose(stdout);system("out.txt");
#endif
	return 0;
}

C

给定n个字符串,问是否存在一种自字母表,使得让这些字符串按照输入顺序排列满足字典序

1n,|S|100

 

直接按照输入顺序遍历,并建立一条从大字母到小字母的边,并记录所有点的入度

我们考虑dfs

对于入度为0的字母,自然放在新字母表的前端,然后把该字母节点删去,也就是把所有小于它的字母入度减一,如果入度为零了,自然也就可以放进剩下字母表的前端了

直到所有字母都进入字母表,进行检查是否合法,如果有的字母入度不为零,也就是还没入字母表,不存在答案

注意如果一个字符串是另一个字符串的前缀,它的字典序是更小的,也就是说如果一个字符串比他的前缀还靠前,不存在答案

其实这个过程也就是一个拓扑排序

#include <string.h>
#include <iostream>
#include <algorithm>
#include <stdio.h>
#include <vector>
using namespace std;
const int maxn=1e5+10;
const int maxm=1e6+10;
const int INF=0x3f3f3f3f;
int casn,n,m,k;
int stk[maxn];
int top;
int deg[maxn];
int alp[123];
vector<int>g[maxn];
char s[123][123];
int ans[123];
int main(){
//#define test
#ifdef test
	freopen("in.txt","r",stdin);freopen("out.txt","w",stdout);
#endif

	while(cin>>n){
		top=0;
		for(int i=0;i<30;i++)g[i].clear();
		memset(deg,0,sizeof deg);
//		for(int i=0;i<26;i++) alp[i]=i;
		for(int i=0;i<n;i++){
			cin>>s[i];
		}
		int flag=1;
		for(int i=0;i<n&&flag;i++){
			int len=strlen(s[i]);
			for(int j=i+1;j<n&&flag;j++){
				for(int k=0;k<len;k++){
					if(k>=strlen(s[j])){
						flag=0;
						break;
					}
					if(s[i][k]!=s[j][k]) {
						deg[s[j][k]-'a'+1]++;
						g[s[i][k]-'a'+1].push_back(s[j][k]-'a'+1);
						break;
					}
				}
			}
		}
		for(int i=1;i<=26;i++)if(!deg[i])stk[++top]=i;
		int cnt=0;
		while(top){
			int now=stk[top--];
			alp[cnt++]=now;
			for(int i=0;i<g[now].size();i++){
				int to=g[now][i];
				deg[to]--;
				if(!deg[to]){
					stk[++top]=to;
				}
			}
		}
		for(int i=1;i<=26;i++){
			if(deg[i]){
				flag=0;
				break;
			}
		}
		if(!flag)puts("Impossible");
		else {
			for(int i=0;i<26;i++){
				cout<<((char)(alp[i]+'a'-1));
			}
			cout<<endl;
		}
	}
#ifdef test
	fclose(stdin);fclose(stdout);system("out.txt");
#endif
	return 0;
}

D

一个网格,你可以沿着格子上的线移动,

每一条横着贯穿的线只能向左或者向右走

每一个竖着贯穿的线只能向上或者向下走

问是否任意两点可达

 

dfs(我并不会),或者tarjan(敲得很舒服),写完看题解说可以只看外围情况...太菜了Orz

#include <string.h>
#include <iostream>
#include <algorithm>
#include <stdio.h>
using namespace std;
const int maxn=1e5+10;
const int maxm=1e6+10;
const int INF=0x3f3f3f3f;
int casn,n,m,k;
char hor[123];
char ver[123];
int g[23][23][23][23];
int vis[123][123];
const int dir[4][2]={1,0,-1,0,0,1,0,-1};
#define dx dir[d][0]
#define dy dir[d][1]
void dfs(int nx,int ny){
	vis[nx][ny]++;
	for(int d=0;d<4;d++){
		int x=nx+dx;
		int y=ny+dy;
		if(x&&y&&x<=n&&y<=m&&g[nx][ny][x][y]&&!vis[x][y]){
			dfs(x,y);
		}
	}
}
int main(){

#ifdef test
	freopen("in.txt","r",stdin);freopen("out.txt","w",stdout);
#endif
	while(cin>>n>>m){
		cin>>(hor+1)>>(ver+1);
		memset(g,0,sizeof g);
		for(int i=1;i<=n;i++){
			for(int j=1;j<=m;j++){
				if(hor[i]=='<'&&j){
					g[i][j][i][j-1]=1;
				}else if(hor[i]=='>'&&j<m){
					g[i][j][i][j+1]=1;
				}
			}
		}
		for(int i=1;i<=m;i++){
			for(int j=1;j<=n;j++){
				if(ver[i]=='v'&&j<n){
					g[j][i][j+1][i]=1;
				}else if(ver[i]=='^'&&j){
					g[j][i][j-1][i]=1;
				}
			}
		}
		int flag=1;
		memset(vis,0,sizeof vis);
		for(int i=1;i<=n;i++){
			for(int j=1;j<=m;j++){
				memset(vis,0,sizeof vis);
				dfs(i,j);
				for(int k=1;k<=n;k++){
					for(int q=1;q<=m;q++){
						if(!vis[k][q]){
							flag=0;
							k=q=1e9;
						}
					}
				}
				if(!flag) i=j=1e9;
			}
		}
		if(flag) puts("YES");
		else puts("NO");
	}

#ifdef test
	fclose(stdin);fclose(stdout);system("out.txt");
#endif
	return 0;
}
#include <string.h>
#include <iostream>
#include <algorithm>
#include <stdio.h>
using namespace std;
const int maxn=1e5+10;
const int maxm=1e6+10;
const int INF=0x3f3f3f3f;
int casn,n,m,k;
char hor[123],ver[123];
int stk[1234],top,cnt,dfn[1234],low[1234],numc,belong[1234];
int vis[1234];
struct node {
	int to,next;
}e[maxn];
int nume,head[1234];
void add(int a,int b){
	e[nume]=(node){b,head[a]};
	head[a]=nume++;
}
void tdfs(int now){
	dfn[now]=low[now]=++cnt;
	stk[top++]=now;
	vis[now]=1;
	for(int i=head[now];~i;i=e[i].next){
		int to=e[i].to;
		if(!dfn[to]){tdfs(to);low[now]=min(low[now],low[to]);}
		else if(vis[to]) low[now]=min(low[now],low[to]);
	}
	if(low[now]==dfn[now]){
		numc++;
		int to;
		do{
			to=stk[--top];
			belong[to]=numc;
			vis[to]=0;
		}while(to!=now);
	}
}
void tarjan(int numv=n){
	for(int i=1;i<=numv;i++){
		if(!dfn[i]) tdfs(i);
	}
}
int main(){
//#define test
#ifdef test
	freopen("in.txt","r",stdin);freopen("out.txt","w",stdout);
#endif

	while(cin>>n>>m){
		nume=top=numc=cnt=0;
		memset(head,-1,sizeof head);
		memset(dfn,0,sizeof dfn);
		memset(low,0,sizeof low);
		memset(belong,0,sizeof belong);
		memset(vis,0,sizeof vis);
		cin>>(hor+1)>>(ver+1);
		for(int i=1;i<=n;i++){
			if(hor[i]=='<')
				for(int j=2;j<=m;j++)
					add((i-1)*m+j,(i-1)*m+j-1);
			else for(int j=1;j<m;j++)
				add((i-1)*m+j,(i-1)*m+j+1);
		}
		for(int i=1;i<=m;i++){
			if(ver[i]=='v')
				for(int j=1;j<n;j++)
					add((j-1)*m+i,(j)*m+i);
			else for(int j=2;j<=n;j++)
					add((j-1)*m+i,(j-2)*m+i);
		}
		tarjan(n*m);
		puts((numc==1)?"YES":"NO");
	}

#ifdef test
	fclose(stdin);fclose(stdout);system("out.txt");
#endif
	return 0;
}

E

给一个树的3种属性,点数,深度,直径

让你构造出来其中一种

 

先判断是否可行

再去构造深度

再去构造剩下的满足直径

再把其他点放在..不影响之前性质的地方(我一开始写的链式前向星存图,其实只用保留前驱节点就行..)

#include <string.h>
#include <iostream>
#include <algorithm>
#include <stdio.h>
using namespace std;
const int maxn=1e5+10;
const int maxm=1e6+10;
const int INF=0x3f3f3f3f;
int casn,n,m,k;
struct node {int to,cost,next;}e[maxm];int head[maxn],nume;
void add(int a,int b,int c=1){e[++nume]=(node){b,c,head[a]};head[a]=nume;}
int pre[maxn];

int main(){
//#define test
#ifdef test
    freopen("in.txt","r",stdin);freopen("out.txt","w",stdout);
#endif

    while(cin>>n>>k>>m){
        if(k<m||k==n||k==1&&n>2||k>2*m){
            puts("-1");
            continue;
        }
        memset(pre,0,sizeof pre);
        for(int i=2;i<=m+1;i++) pre[i]=i-1;
        (m+2<=k+1) pre[m+2]=1;
        for(int i=m+3;i<=k+1;i++) pre[i]=i-1;
        for(int i=k+2;i<=n;i++) pre[i]=m;
        for(int i=2;i<=n;i++) cout<<pre[i]<<' '<<i<<endl;
    }

#ifdef test
    fclose(stdin);fclose(stdout);system("out.txt");
#endif
    return 0;
}

F

给n个点m个边

告诉你这个图其实是一个abc三个字母的字符串构成的

如果Si和Sj差距小于等于1,则连接一条无向边

问一个合法解,也就是问一种合法的染色

 

首先,记录入度

如果入度等于n-1,也就是和所有其他点都有边,这个字母自然是b

然后,对于任意一对没有染色的点,自然一个是a,一个是c

然后,其中和a连接的,自然是a,反之是c

#include <string.h>
#include <iostream>
#include <algorithm>
#include <stdio.h>
using namespace std;
const int maxn=1e3+10;
const int maxm=1e6+10;
const int INF=0x3f3f3f3f;
int casn,n,m,k;
int deg[maxn];
int g[maxn][maxn];
int ans[maxn];
int main(){
//#define test
#ifdef test
	freopen("in.txt","r",stdin);freopen("out.txt","w",stdout);
#endif

	while(cin>>n>>m){
		memset(g,0,sizeof g);
		memset(deg,0,sizeof deg);
		for(int i=0;i<m;i++){
			int a,b;
			scanf("%d%d",&a,&b);
			g[a][b]=g[b][a]=1;
			deg[a]++;
			deg[b]++;
		}
		memset(ans,0,sizeof ans);
		for(int i=1;i<=n;i++){
			if(deg[i]==n-1) ans[i]=2;
		}
		for(int i=1;i<=n;i++){
			if(ans[i]) continue;
			for(int j=i+1;j<=n;j++){
				if(!g[i][j]){
					ans[i]=1;ans[j]=3;
					for(int k=1;k<=n;k++){
						if(k==i||k==j||ans[k]) continue;
						if(g[k][i]) ans[k]=1;
						if(g[k][j]) ans[k]=3;
					}
				}
			}
		}
		int sin=1;
		for(int i=1;i<=n;i++){
			if(!ans[i]){
				sin=0;
				break;
			}
		}
		for(int i=1;i<=n&&sin;i++){
			for(int j=i+1;j<=n;j++){
				if(g[i][j]&&abs(ans[i]-ans[j])>1){
					sin=0;
					break;
				}
				if(!g[i][j]&&abs(ans[i]-ans[j])<=1){
					sin=0;
					break;
				}
			}
		}
		if(sin){
			puts("YES");
			for(int i=1;i<=n;i++)cout<<(char)(ans[i]+'a'-1);
			cout<<endl;
		}
		else puts("NO");
	}

#ifdef test
	fclose(stdin);fclose(stdout);system("out.txt");
#endif
	return 0;
}

G

给一个无向图,又有一些额外边是从起点直接到某些点的,存在重边

问你最多删除多少个额外路,使得每个点到起点的最短路不变

 

先构造图,跑一边spfa,然后如果有特殊路大于当前最短路,直接删掉

否则枚举这个点的边,如果有和这个点相邻的点+边权等于到该点的特殊路径,也删掉

而对于任意一点

如果特殊路径被保存了一个,其他到该点的特殊路径也都应该被删除

如果该点的特殊路径等于其他走普通路径的距离,其他所有到该点的特殊路径也应该被删除

标记,剪枝一下即可

注意,该题卡spfa,但是用spfa+slf优化可以秒杀

当然,也可能是我这个做法比较臭(也有可能是因为重边太多了)

能想到的还有比如记录入度,如果入度大于1且有同样长度,就删除,速度应该差不多

还有就是先跑一遍spfa,然后加入特殊路之前先剪枝一波,然后再在原图的基础上跑一遍spfa

或者就是把有特殊路的节点标记上,如果这个点被松弛了,自然这个地方的特殊路就会被删除,最后统计...

#include <string.h>
#include <iostream>
#include <algorithm>
#include <stdio.h>
using namespace std;
const int maxn=1e5+10;
const int maxm=1e6+10;
const int INF=0x3f3f3f3f;
#define ll long long
int casn,n,m,k;
struct node {int to;int cost;int next;}e[maxm];int head[maxn],nume;
inline void add(int a,int b,int c=1){e[++nume]=(node){b,c,head[a]};head[a]=nume;}
int road[maxn];
ll dis[maxn];
bool vis[maxn];
int que[maxn];
int go[maxn];

void spfa(int st=1,int ed=n){
  int top=0,end=0,now;
  memset(dis,INF,sizeof dis);
  memset(vis,0,sizeof vis);
  que[end++]=st,dis[st]=0;
  while(top!=end){
    now=que[top++],top%=maxn;
    vis[now]=false;
    for(int i=head[now];i;i=e[i].next){
      int to=e[i].to;
      if(now==to) continue;
      if(dis[now]+e[i].cost<dis[to]){
        dis[to]=dis[now]+e[i].cost;
        if(!vis[to]){
       	 vis[to]=true;
        	if(dis[to]<dis[que[top]]){
		  		  top--;
		  		  if(top==-1)top=maxn-1;
		  		  que[top]=to;
					}else{
						que[end++]=to;
						end%=maxn;
					}
				}
      }
    }
  }
}

int main(){
//#define test
#ifdef test
	freopen("in.txt","r",stdin);freopen("out.txt","w",stdout);
#endif

	cin>>n>>m>>k;
	for(int i=0;i<m;i++){
		int a,b,c;
		scanf("%d%d%d",&a,&b,&c);
		add(a,b,c);
		add(b,a,c);
	}
	int ans=0;
	int cnt=0;
	for(int i=1;i<=k;i++){
		int a,b;
		scanf("%d%d",&a,&b);
		add(1,a,b);
		road[i]=b;
		go[i]=a;
	}
	spfa();
	memset(vis,0,sizeof vis);
	for(int i=1;i<=k;i++){
		if(vis[go[i]]||road[i]>dis[go[i]]){
			ans++;
			continue;
		}
		for(int j=head[go[i]];j;j=e[j].next){
			int to=e[j].to;
			if(dis[go[i]]==dis[to]+e[j].cost){
				ans++;
				break;
			}
		}
		vis[go[i]]=1;
	}
	cout<<ans<<endl;

#ifdef test
	fclose(stdin);fclose(stdout);system("out.txt");
#endif
	return 0;
}

H

给一个无权图,问你如何删除最多的边

才能使得s1 t1的距离依然大于l1,s2 t2的距离依然大于l2

n<=3000

 

原题等价于最少多少条边可满足l1,l2的条件,其他的边都可以删掉

首先如果两个路无交集,就是要保留s1 t1,s2 t2的路径边数之和

然后n^2枚举共同路径,如果走共同路径的路径长度满足条件,就更新答案

注意点数可以达到3000,我的floyd一直T,改成n次spfa+slf优化就ac了...原谅我spfa敲得太顺手了..

#include <string.h>
#include <iostream>
#include <algorithm>
#include <stdio.h>
using namespace std;
const int maxn=1e5+10;
const int maxm=1e6+10;
const int INF=0x3f3f3f3f;
int casn,n,m,k;
struct node {int to,cost,next;}e[maxm];int head[maxn],nume;
void add(int a,int b,int c=1){e[++nume]=(node){b,c,head[a]};head[a]=nume;}
int s1,s2,t1,t2,l1,l2;
int dis[3000+15][3000+15];
bool vis[maxn];
int que[maxn];
void spfa(int st=1,int ed=n){
  int top=0,end=0,now;
  memset(vis,0,sizeof vis);
  que[end++]=st,dis[st][st]=0;
  while(top!=end){
    now=que[top++],top%=maxn;
    vis[now]=false;
    for(int i=head[now];i;i=e[i].next){
      int to=e[i].to;
      if(now==to) continue;
      if(dis[st][now]+e[i].cost<dis[st][to]){
        dis[st][to]=dis[st][now]+e[i].cost;
        if(!vis[to]){
       	 vis[to]=true;
        	if(dis[now][to]<dis[now][que[top]]){
		  		top--;
		  		if(top==-1)top=maxn-1;
		  		que[top]=to;
			}else{
				que[end++]=to;
				end%=maxn;
			}
		}
      }
    }
  }
}

int main(){
//#define test
#ifdef test
	freopen("in.txt","r",stdin);freopen("out.txt","w",stdout);
#endif
	ios::sync_with_stdio(false);
	cin>>n>>m;
	memset(dis,0x3f,sizeof dis);
	for(int i=0;i<m;i++){
		int a,b;
		cin>>a>>b;
		add(a,b,1);
		add(b,a,1);
	}
	cin>>s1>>t1>>l1;
	cin>>s2>>t2>>l2;
	for(int i=1;i<=n;i++){
		spfa(i);
	}
	int flag=1;
	if(dis[s1][t1]>l1||dis[s2][t2]>l2){
		flag=0;
	}
	int ans=dis[s1][t1]+dis[s2][t2];
	for(int i=1;i<=n;i++){
		for(int j=1;j<=n;j++){
			int d1=dis[s1][i]+dis[j][t1];
			int d2=dis[s2][i]+dis[j][t2];
			int mid=dis[i][j];
			int d3=dis[s1][j]+dis[i][t1];
			int d4=dis[s2][j]+dis[i][t2];
			if(d1+mid<=l1&&d2+mid<=l2){
				ans=min(ans,d1+d2+mid);
			}
			if(d3+mid<=l1&&d4+mid<=l2){
				ans=min(ans,d3+d4+mid);
			}
			if(d1+mid<=l1&&d4+mid<=l2){
				ans=min(ans,d1+d4+mid);
			}
			if(d3+mid<=l1&&d2+mid<=l2){
				ans=min(ans,d3+d2+mid);
			}
		}
	}
	if(flag) cout<<m-ans<<endl;
	else puts("-1");
#ifdef test
	fclose(stdin);fclose(stdout);system("out.txt");
#endif
	return 0;
}
原文地址:https://www.cnblogs.com/nervendnig/p/8669262.html