这次练习赛的出题其实非常有针对性,只可惜根据我的调查,体会到这一点的童鞋并不多,好伤心……
A. Who Are My Friends?(Ⅰ)
B. Who Are My Friends?(Ⅱ)
两道上机原题,见上机题解。
C. Who Are My Friends?(Ⅲ)
我们注意到这道题与前两题的唯一区别是,查询过程是在线的,也就是说,边修改边查询。也正因为这个原因,这道题不能再用图论的方法dfs。而并查集的操作上则与之前没有什么不同。
#include<cstdio>
using namespace std;
const int MAXN=100005;
int u[MAXN];
void init()
{
for(int i=0; i<MAXN; ++i)
u[i]=i;
}
int find(int x)
{
if(u[x]!=x)
u[x]=find(u[x]);
return u[x];
}
void merge(int x,int y)
{
u[find(x)]=find(y);
}
bool query(int x,int y)
{
return find(x)==find(y);
}
int main()
{
int n,k,a,b;
while(~scanf("%d",&n))
{
init();
while(n--)
{
scanf("%d%d%d",&k,&a,&b);
switch(k)
{
case 1:
merge(a,b);
break;
case 2:
puts(query(a,b)?"Great!":"Pity...");
}
}
}
}
#include<cstdio>
using namespace std;
const int MAXN=100005;
int u[MAXN],r[MAXN];
void init()
{
for(int i=0; i<MAXN; ++i)
{
u[i]=i;
r[i]=0;
}
}
int find(int x)
{
if(u[x]!=x)
return find(u[x]);
return u[x];
}
void merge(int x,int y)
{
x=find(x);
y=find(y);
if(r[x]>r[y])
u[y]=x;
else
{
u[x]=y;
if(r[x]==r[y])
++r[y];
}
}
bool query(int x,int y)
{
return find(x)==find(y);
}
int main()
{
int n,k,a,b;
while(~scanf("%d",&n))
{
init();
while(n--)
{
scanf("%d%d%d",&k,&a,&b);
switch(k)
{
case 1:
merge(a,b);
break;
case 2:
puts(query(a,b)?"Great!":"Pity...");
}
}
}
}
D. 图的DFS遍历
这道题有点无聊,可以说是为了卡内存而卡内存。除了动态管理内存之外,还可以用vector来模拟链表。剩下的就是链表中每个点对应的边应该排好序,然后dfs即可。我直接使用了优先队列和set两种结构来代替vector,用来维护有序性。
#include<cstdio>
#include<cstring>
#include<vector>
#include<queue>
using namespace std;
const int MAXN=1005;
priority_queue<int,vector<int>,greater<int> > g[MAXN];
bool vis[MAXN];
void dfs(int u)
{
vis[u]=true;
printf("%d ",u);
while(!g[u].empty())
{
int v=g[u].top();
g[u].pop();
if(!vis[v])
dfs(v);
}
}
int main()
{
int n,k,m,u,v;
scanf("%d",&n);
while(n--)
{
scanf("%d%d",&k,&m);
while(m--)
{
scanf("%d%d",&u,&v);
g[u].push(v);
g[v].push(u);
}
memset(vis,false,sizeof(vis));
for(int i=0; i<k; ++i)
if(!vis[i])
dfs(i);
putchar('\n');
}
}
#include<cstdio>
#include<cstring>
#include<set>
using namespace std;
const int MAXN=1005;
set<int> g[MAXN];
bool vis[MAXN];
void dfs(int u)
{
vis[u]=true;
printf("%d ",u);
for(set<int>::iterator v=g[u].begin(); v!=g[u].end(); ++v)
if(!vis[*v])
dfs(*v);
}
int main()
{
int n,k,m,u,v;
scanf("%d",&n);
while(n--)
{
scanf("%d%d",&k,&m);
while(m--)
{
scanf("%d%d",&u,&v);
g[u].insert(v);
g[v].insert(u);
}
memset(vis,false,sizeof(vis));
for(int i=0; i<k; ++i)
if(!vis[i])
dfs(i);
putchar('\n');
for(int i=0; i<k; ++i)
g[i].clear();
}
}
E. 蒹葭苍苍
单点到单点的最短路,bfs即可。因为有多次询问,写floyd也是一个办法,只是因为询问次数比较少,相比前者会慢一些。我闲得蛋疼写了三份代码。
#include<cstdio>
#include<cstring>
#include<queue>
using namespace std;
const int MAXN=305;
bool g[MAXN][MAXN],vis[MAXN];
int n;
int bfs(int a,int b)
{
queue<pair<int,int> > q;
vis[a]=true;
q.push(make_pair(a,0));
while(!q.empty())
{
pair<int,int> u=q.front();
q.pop();
for(int v=1; v<=n; ++v)
if(g[u.first][v])
{
if(v==b)
return u.second;
if(!vis[v])
{
vis[v]=true;
q.push(make_pair(v,u.second+1));
}
}
}
return -1;
}
int main()
{
int m,k,u,v;
while(~scanf("%d%d%d",&n,&m,&k))
{
memset(g,false,sizeof(g));
while(m--)
{
scanf("%d%d",&u,&v);
g[u][v]=g[v][u]=true;
}
while(k--)
{
scanf("%d%d",&u,&v);
memset(vis,false,sizeof(vis));
printf("%d\n",bfs(u,v));
}
}
}
#include<cstdio>
#include<cstring>
#include<queue>
using namespace std;
const int MAXN=305;
const int MAXM=20005;
struct graph
{
int head[MAXN];
int to[MAXM];
int next[MAXM];
int tot;
void init()
{
tot=0;
memset(head,0xff,sizeof(head));
}
void add(int x,int y)
{
to[tot]=y;
next[tot]=head[x];
head[x]=tot++;
}
} g;
bool vis[MAXN];
int bfs(int a,int b)
{
queue<pair<int,int> > q;
vis[a]=true;
q.push(make_pair(a,0));
while(!q.empty())
{
pair<int,int> u=q.front();
q.pop();
for(int i=g.head[u.first]; ~i; i=g.next[i])
{
int v=g.to[i];
if(v==b)
return u.second;
if(!vis[v])
{
vis[v]=true;
q.push(make_pair(v,u.second+1));
}
}
}
return -1;
}
int main()
{
int n,m,k,u,v;
while(~scanf("%d%d%d",&n,&m,&k))
{
g.init();
while(m--)
{
scanf("%d%d",&u,&v);
g.add(u,v);
g.add(v,u);
}
while(k--)
{
scanf("%d%d",&u,&v);
memset(vis,false,sizeof(vis));
printf("%d\n",bfs(u,v));
}
}
}
#include<cstdio>
#include<cstring>
#include<algorithm>
using namespace std;
const int MAXN=305;
const int INF=0x3f3f3f3f;
int g[MAXN][MAXN],n,m,k,u,v;
void floyd()
{
for(int k=1; k<=n; ++k)
for(int i=1; i<=n; ++i)
for(int j=1; j<=n; ++j)
g[i][j]=min(g[i][j],g[i][k]+g[k][j]);
}
int main()
{
while(~scanf("%d%d%d",&n,&m,&k))
{
memset(g,0x3f,sizeof(g));
while(m--)
{
scanf("%d%d",&u,&v);
g[u][v]=g[v][u]=1;
}
floyd();
while(k--)
{
scanf("%d%d",&u,&v);
printf("%d\n",g[u][v]==INF?-1:g[u][v]-1);
}
}
}
F. 图的环路(Ⅰ)
图的判环是一个很基础的问题,有很多种方式。对于无向图,我这里给出两种方法。
一种是遍历,在遍历过程中如果遇到已经访问过的节点则意味着有环。至于使用dfs还是bfs并不重要。
#include<cstdio>
#include<cstring>
using namespace std;
const int MAXN=10005;
const int MAXM=200005;
struct graph
{
int head[MAXN];
int to[MAXM];
int next[MAXM];
int tot;
void init()
{
tot=0;
memset(head,0xff,sizeof(head));
}
void add(int x,int y)
{
to[tot]=y;
next[tot]=head[x];
head[x]=tot++;
}
} g;
bool vis[MAXN];
bool dfs(int u,int c)
{
vis[c]=true;
bool flag=false;
for(int i=g.head[c]; !flag&&~i; i=g.next[i])
{
int v=g.to[i];
if(v!=u)
{
if(vis[v])
return true;
flag=dfs(c,v);
}
}
return flag;
}
int main()
{
int n,m,u,v;
while(~scanf("%d%d",&n,&m))
{
g.init();
while(m--)
{
scanf("%d%d",&u,&v);
g.add(u,v);
g.add(v,u);
}
memset(vis,false,sizeof(vis));
bool flag=false;
for(int i=1; !flag&&i<=n; ++i)
if(!vis[i])
flag=dfs(0,i);
puts(flag?"Yes":"No");
}
}
#include<cstdio>
#include<cstring>
#include<vector>
using namespace std;
const int MAXN=10005;
vector<int> g[MAXN];
bool vis[MAXN];
bool dfs(int u,int c)
{
vis[c]=true;
bool flag=false;
for(int i=0; !flag&&i<g[c].size(); ++i)
{
int v=g[c][i];
if(v!=u)
{
if(vis[v])
return true;
flag=dfs(c,v);
}
}
return flag;
}
int main()
{
int n,m,u,v;
while(~scanf("%d%d",&n,&m))
{
while(m--)
{
scanf("%d%d",&u,&v);
g[u].push_back(v);
g[v].push_back(u);
}
memset(vis,false,sizeof(vis));
bool flag=false;
for(int i=1; !flag&&i<=n; ++i)
if(!vis[i])
flag=dfs(0,i);
puts(flag?"Yes":"No");
for(int i=1; i<=n; ++i)
g[i].clear();
}
}
一种是并查集,不断合并有边相连的两个点,如果合并前两个点已经在一个集合内,则意味着他们在一个环里。
#include<cstdio>
#include<cstring>
using namespace std;
const int MAXN=10005;
int f[MAXN];
void init()
{
for(int i=0; i<MAXN; ++i)
f[i]=i;
}
int find(int x)
{
if(f[x]!=x)
f[x]=find(f[x]);
return f[x];
}
void merge(int x,int y)
{
f[find(x)]=find(y);
}
bool query(int x,int y)
{
return find(x)==find(y);
}
int main()
{
int n,m,u,v;
while(~scanf("%d%d",&n,&m))
{
bool flag=false;
init();
while(m--)
{
scanf("%d%d",&u,&v);
if(query(u,v))
flag=true;
merge(u,v);
}
puts(flag?"Yes":"No");
}
}
G. 图的环路(Ⅱ)
对于有向图,这里也给出两种做法。一种仍然是遍历,由于遍历到的已访问过的点不一定意味着成环,只有当这个点是这次遍历的祖先节点时才成环,所以记录状态时要多一个状态:0表示尚未访问,-1表示正在访问子节点,1表示已访问过所有子节点。
#include<cstdio>
#include<cstring>
using namespace std;
const int MAXN=10005;
const int MAXM=200005;
struct graph
{
int head[MAXN];
int to[MAXM];
int next[MAXM];
int tot;
void init()
{
tot=0;
memset(head,0xff,sizeof(head));
}
void add(int x,int y)
{
to[tot]=y;
next[tot]=head[x];
head[x]=tot++;
}
} g;
int vis[MAXN];
bool dfs(int u)
{
vis[u]=-1;
bool flag=false;
for(int i=g.head[u]; !flag&&~i; i=g.next[i])
{
int v=g.to[i];
if(!~vis[v])
return true;
if(!vis[v])
flag=dfs(v);
}
vis[u]=1;
return flag;
}
int main()
{
int n,m,u,v;
while(~scanf("%d%d",&n,&m))
{
g.init();
while(m--)
{
scanf("%d%d",&u,&v);
g.add(u,v);
}
memset(vis,0,sizeof(vis));
bool flag=false;
for(int i=1; !flag&&i<=n; ++i)
if(!vis[i])
flag=dfs(i);
puts(flag?"Yes":"No");
}
}
#include<cstdio>
#include<cstring>
#include<vector>
using namespace std;
const int MAXN=10005;
vector<int> g[MAXN];
int vis[MAXN];
bool dfs(int u)
{
vis[u]=-1;
bool flag=false;
for(int i=0; !flag&&i<g[u].size(); ++i)
{
int v=g[u][i];
if(!~vis[v])
return true;
if(!vis[v])
flag=dfs(v);
}
vis[u]=1;
return flag;
}
int main()
{
int n,m,u,v;
while(~scanf("%d%d",&n,&m))
{
while(m--)
{
scanf("%d%d",&u,&v);
g[u].push_back(v);
}
memset(vis,0,sizeof(vis));
bool flag=false;
for(int i=1; !flag&&i<=n; ++i)
if(!vis[i])
flag=dfs(i);
puts(flag?"Yes":"No");
for(int i=1; i<=n; ++i)
g[i].clear();
}
}
另外拓扑排序可以判环。
#include<cstdio>
#include<cstring>
#include<queue>
using namespace std;
const int MAXN=10005;
const int MAXM=200005;
struct graph
{
int head[MAXN];
int to[MAXM];
int next[MAXM];
int tot;
void init()
{
tot=0;
memset(head,0xff,sizeof(head));
}
void add(int x,int y)
{
to[tot]=y;
next[tot]=head[x];
head[x]=tot++;
}
} g;
int du[MAXN],n;
bool toposort()
{
memset(du,0,sizeof(du));
for(int i=1; i<=n; ++i)
for(int j=g.head[i]; ~j; j=g.next[j])
++du[g.to[j]];
int tot=0;
queue<int> q;
for(int i=1; i<=n; ++i)
if(!du[i])
q.push(i);
while(!q.empty())
{
int u=q.front();
q.pop();
++tot;
for(int i=g.head[u]; ~i; i=g.next[i])
{
int v=g.to[i];
if(!(--du[v]))
q.push(v);
}
}
return tot==n;
}
int main()
{
int m,u,v;
while(~scanf("%d%d",&n,&m))
{
g.init();
while(m--)
{
scanf("%d%d",&u,&v);
g.add(u,v);
}
puts(!toposort()?"Yes":"No");
}
}
#include<cstdio>
#include<cstring>
#include<vector>
#include<queue>
using namespace std;
const int MAXN=10005;
vector<int> g[MAXN];
int du[MAXN],n;
bool toposort()
{
memset(du,0,sizeof(du));
for(int i=1; i<=n; ++i)
for(int j=0; j<g[i].size(); ++j)
++du[g[i][j]];
int tot=0;
queue<int> q;
for(int i=1; i<=n; ++i)
if(!du[i])
q.push(i);
while(!q.empty())
{
int u=q.front();
q.pop();
++tot;
for(int i=0; i<g[u].size(); ++i)
{
int v=g[u][i];
if(!(--du[v]))
q.push(v);
}
}
return tot==n;
}
int main()
{
int m,u,v;
while(~scanf("%d%d",&n,&m))
{
while(m--)
{
scanf("%d%d",&u,&v);
g[u].push_back(v);
}
puts(!toposort()?"Yes":"No");
for(int i=1; i<=n; ++i)
g[i].clear();
}
}
H. Draught
有童鞋过掉这道题还是挺开心的……这道题是CodeChef上的一道原题,算是一个比较弱的树形dp,或者一个比较难的遍历题。第一个问很好做,对于每个连通子图记录节点个数即可;第二个问,节点有气流穿过无非是两种情况,祖先至少有一个节点开窗且子树至少有一个节点开窗,或者子树有至少两个节点开窗,其中子树包括这个节点。两个问可以两次遍历分开求,也可以一次遍历一起做,两种写法稍有不同,代码都会放上来。注意在适当的地方用long long。
#include<cstdio>
#include<cstring>
using namespace std;
const int MAXN=50005;
const int MAXM=100005;
struct graph
{
int head[MAXN];
int to[MAXM];
int next[MAXM];
int tot;
void init()
{
tot=0;
memset(head,0xff,sizeof(head));
}
void add(int x,int y)
{
to[tot]=y;
next[tot]=head[x];
head[x]=tot++;
}
} g;
bool win[MAXN],air[MAXN];
int vis[MAXN],wcnt;
void dfs1(int u)
{
vis[u]=1;
if(win[u])
++wcnt;
for(int i=g.head[u]; ~i; i=g.next[i])
{
int v=g.to[i];
if(vis[v]==0)
dfs1(v);
}
}
int dfs2(int u)
{
int ret=0,cnt=0;
vis[u]=2;
if(win[u])
++ret;
for(int i=g.head[u]; ~i; i=g.next[i])
{
int v=g.to[i];
if(vis[v]==1)
{
int tmp=dfs2(v);
if(tmp>0)
++cnt;
ret+=tmp;
}
}
if(cnt>=2||(cnt>=1&&win[u])||(ret>0&&wcnt>ret))
air[u]=true;
return ret;
}
int main()
{
int n,m,u,v;
while(~scanf("%d%d",&n,&m))
{
for(int i=1; i<=n; ++i)
scanf("%d",&win[i]);
g.init();
while(m--)
{
scanf("%d%d",&u,&v);
g.add(u,v);
g.add(v,u);
}
memset(vis,0,sizeof(vis));
memset(air,false,sizeof(air));
int fans=0,rans=0;
for(int i=1; i<=n; ++i)
{
if(vis[i]==0)
{
wcnt=0;
dfs1(i);
fans+=(long long)wcnt*(wcnt-1)/2;
dfs2(i);
}
if(air[i])
++rans;
}
printf("%d %d\n",fans,rans);
}
}
#include<cstdio>
#include<cstring>
#include<vector>
using namespace std;
const int MAXN=50005;
vector<int> E[MAXN];
bool win[MAXN],air[MAXN];
int vis[MAXN],wcnt;
void dfs1(int u)
{
vis[u]=1;
if(win[u])
++wcnt;
for(int v=0; v<E[u].size(); ++v)
if(vis[E[u][v]]==0)
dfs1(E[u][v]);
}
int dfs2(int u)
{
int ret=0,cnt=0;
vis[u]=2;
if(win[u])
++ret;
for(int v=0; v<E[u].size(); ++v)
if(vis[E[u][v]]==1)
{
int tmp=dfs2(E[u][v]);
if(tmp>0)
++cnt;
ret+=tmp;
}
if(cnt>=2||(cnt>=1&&win[u])||(ret>0&&wcnt>ret))
air[u]=true;
return ret;
}
int main()
{
int n,m,u,v;
while(~scanf("%d%d",&n,&m))
{
for(int i=1; i<=n; ++i)
scanf("%d",&win[i]);
while(m--)
{
scanf("%d%d",&u,&v);
E[u].push_back(v);
E[v].push_back(u);
}
memset(vis,0,sizeof(vis));
memset(air,false,sizeof(air));
int fans=0,rans=0;
for(int i=1; i<=n; ++i)
{
if(vis[i]==0)
{
wcnt=0;
dfs1(i);
fans+=(long long)wcnt*(wcnt-1)/2;
dfs2(i);
}
if(air[i])
++rans;
}
printf("%d %d\n",fans,rans);
for(int i=1; i<=n; ++i)
E[i].clear();
}
}
#include<cstdio>
#include<cstring>
using namespace std;
const int MAXN=50005;
const int MAXM=100005;
struct graph
{
int head[MAXN];
int to[MAXM];
int next[MAXM];
int tot;
void init()
{
tot=0;
memset(head,0xff,sizeof(head));
}
void add(int x,int y)
{
to[tot]=y;
next[tot]=head[x];
head[x]=tot++;
}
} g;
bool win[MAXN],vis[MAXN],air[MAXN];
int src;
int dfs(int u)
{
int ret=0;
vis[u]=true;
for(int i=g.head[u]; ~i; i=g.next[i])
{
int v=g.to[i];
if(!vis[v])
ret+=dfs(v);
}
if(ret>0||(u!=src&&win[u]))
air[u]=true;
return ret+win[u];
}
int main()
{
int n,m,u,v;
while(~scanf("%d%d",&n,&m))
{
for(int i=1; i<=n; ++i)
scanf("%d",&win[i]);
g.init();
while(m--)
{
scanf("%d%d",&u,&v);
g.add(u,v);
g.add(v,u);
}
memset(vis,false,sizeof(vis));
memset(air,false,sizeof(air));
int fans=0,rans=0;
for(int i=1; i<=n; ++i)
if(!vis[i]&&win[i])
{
src=i;
long long wcnt=dfs(i);
fans+=wcnt*(wcnt-1)/2;
}
for(int i=1; i<=n; ++i)
if(air[i])
++rans;
printf("%d %d\n",fans,rans);
}
}
总得来说,这次练习赛重点比较了图论和并查集的相似点和不同点,练习了图论的遍历,加深对图论概念的理解(无向图、有向图、环等),也有一定挑战性的题。至于从中能得到多少收获,全看童鞋们自己了。