专题: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
1≤n,ai≤10^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个字符串,问是否存在一种自字母表,使得让这些字符串按照输入顺序排列满足字典序
1≤n,|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;
}