这一回我们的校内赛由敬爱的jyb学长出题,主题是图论。下面来看题:
Problem 1. bricks
【解题报告】
这道题考察了并查集的灵活运用。因为他的修改和询问都很特殊,修改只给你一块砖的序号(你需要知道他所在的那一堆),询问又问你某一块砖以下的砖的个数。由于不告诉我们堆的序号,所以需要搞这三个数组:
father[i]//表示第i块砖所在的那一堆砖的最顶端的序号
up[i]//表示第i块砖以上的砖的个数
down[i]//表示第i块砖以下的砖的个数
除了并查集的合并需要更新这三个数组以外,我们在getfather时也会顺势更新这一路径上点的点。
下面我们来看代码:
#include<stdio.h>
#include<string.h>
#include<algorithm>
#define NAME "bricks"
using namespace std;
const int N=200000;
int father[N+5],down[N+5],up[N+5];//up表示以上,down表示以下
int n,m;
int getfather(int x)//查询时顺势更新
{
int temp;
if(x!=father[x])
{
temp=father[x];
father[x]=getfather(father[x]);
up[x]+=up[temp];
}
return father[x];
}
void _union(int x,int y)//并查集合并,x在上,y在下
{
x=getfather(x),y=getfather(y);
if(x!=y)
{
father[y]=x;
up[y]+=down[x];
down[x]+=down[y];
}
}
int main()
{
freopen(NAME".in","r",stdin);
freopen(NAME".out","w",stdout);
scanf("%d%d",&n,&m);
for(int i=1;i<=n;i++)
father[i]=i,down[i]=1,up[i]=0;//赋初值,down包括自己,up不包括自己
while(m--)
{
char s[10];
scanf("%s",s);
if(s[0]=='M')
{
int u,v;
scanf("%d%d",&u,&v);
_union(u,v);
}
else
{
int x;
scanf("%d",&x);
printf("%d\n",down[getfather(x)]-up[x]-1);//最后输出时把定点的down与这一点的up相减
}
}
return 0;
}
代码如下:
Problem 2. drive
【解题报告】
这道题一看就给人一种图论最短路的感觉,我们毫不犹豫地选择了时间复杂度最优的SPFA。具体思路就是先找到一条最短路劲,在一个一个枚举这条路上的每条边降雨的情况,再重新SPFA就完了。
需要注意的是,建图的时候要建单向边。
代码如下:
#include<stdio.h>
#include<string.h>
#include<algorithm>
#define NAME "drive"
using namespace std;
const int M=10000;
const int N=4000;
int state[N+5],flag[N+5],pre[N+5];
double dis[N+5];
int head[N+5],num;
int n,m;
double tot=-1;
struct edge
{
int u,v,next;//多存储一维u,来记录路的起点
double w;
edge(){next=-1; }
};
edge ed[2*M+5];
void build(int u,int v,double w)
{
ed[++num].v=v;
ed[num].u=u;
ed[num].w=w;
ed[num].next=head[u];
head[u]=num;
}
double SPFA(bool first)
{
memset(flag,0,sizeof(flag));
for(int i=2;i<=n;i++)
dis[i]=1000000.0;
int front=0,rear=1;
dis[1]=0;
state[rear]=1;
flag[1]=1;
do
{
front++;
front=(front+1)%(N+5)-1;
int u=state[front];
flag[u]=0;
for(int i=head[u];i!=-1;i=ed[i].next)
{
int v=ed[i].v;
if(dis[v]>dis[u]+ed[i].w)
{
dis[v]=dis[u]+ed[i].w;
if(first)pre[v]=i;//记录最短路径
if(!flag[v])
{
++rear;
rear=(rear+1)%(N+5)-1;
state[rear]=v;
flag[v]=1;
}
}
}
}while(front!=rear);
return dis[n];
}
int main()
{
freopen(NAME".in","r",stdin);
freopen(NAME".out","w",stdout);
memset(head,-1,sizeof(head));
scanf("%d%d",&n,&m);
for(int i=1;i<=m;i++)
{
int u,v;
double l,s;
scanf("%d%d%lf%lf",&u,&v,&s,&l);
build(u,v,l/s);//注意是单向边
}
int now=n;
double ans=SPFA(true);
while(now!=1)//从后往前修改点权
{
ed[pre[now]].w*=4.0;
ans=max(ans,SPFA(false));
ed[pre[now]].w/=4.0;
now=ed[pre[now]].u;
}
printf("%.4lf",ans);
return 0;
}
Problem 3. graph
【解题报告】
Problem 4. airplane
【解题报告】