P7177 [COCI2014-2015#4] MRAVI题解
这道题是我随机跳题跳到的,没什么技术含量却卡了我整整一个下午,发篇题解纪念一下,另外这是本人第一次发题解,如有问题还请指出。
Part1 思路
首先我们很容易就能看出每个叶子节点能收到的液体和倒入的液体是成正比的,答案满足所谓单调性,那么就可以用二分答案来求解。
大体思路很简单,可是 check 函数怎么写呢?
我们可以使用 DFS,把每个叶子节点能获得的液体求出来,再和
Part2 代码实现
2-1 前置工作
我们首先要存树,我这里用的是链式前向星,不过多讲解,直接上代码(该开 double 记得开):
struct edge
{
int to, pre, opt;//opt表示是否是超级管道
double dis;//边权,即题中的流量
}e[maxn << 1];//因为不知道父子关系,所以要存双向边,开二倍数组
int head[maxn], cnt;
void add(int u, int v, double w, int t)//分别对应 a,b,x,t
{//这里不细讲了
cnt++;
e[cnt].to = v;
e[cnt].dis = w;
e[cnt].opt = t;
e[cnt].pre = head[u];
head[u] = cnt;
}
然后是输入部分,很简单,但要注意细节,尤其注意不能在输入时直接将流量百分比
“它们是超级管道,有超能力将流经它们的液体流量平方”
所以要用边的流量百分比
代码:
scanf("%d", &n);
int a, b, t;
double x;
for(int i = 1; i < n; i++)//注意只有n-1条边
{
scanf("%d%d%lf%d", &a, &b, &x, &t);
add(a, b, x / 100, t);//上面提到的双向边
add(b, a, x / 100, t);
}
for(int i = 1; i <= n; i++)
{
scanf("%lf", &k[i]);//double要用lf输入,scanf要加 &(我会告诉你我因为没加 & 卡了一个小时?)
}
2-2 二分答案
因为是在实数域进行二分,所以代码和整数的不太一样。
我们首先定义
const double eps = 1e-3;
然后是改进过后的二分答案:
double l = 0;
double r = 2e9; //这里上界为题目要求L的最大值
while(l + eps < r) //eps是这么用的
{
double mid = (l + r) / 2;
if(check(mid)) r = mid; //如果当前答案合法,继续寻找更小的答案(更优解)
else l = mid;//注意:这里不用-1或+1
}
2-3 DFS+check(重点)
对于 DFS,我们就按一般的图的深度优先遍历写就行。不过我们可以很容易地发现只要有一个叶子节点接收到的液体不够(即check() 函数内将
下面给出 DFS 和 check 的代码:
bool f;
bool vis[maxn];
void dfs(int u, double w) //点的编号,当前节点的液体量
{
if(!f) return; //这里就是剪枝了
if(k[u] != -1) //判断是否为叶子节点
{
if(w < k[u]) f = 0; //赋值
return;//搜到叶子直接返回
}
for(int i = head[u]; i; i = e[i].pre) //遍历每条边
{
int to = e[i].to;
double d = e[i].dis;
if(vis[to] == 0) //防止重复搜
{
vis[to] = 1;
if(e[i].opt) dfs(to, (w * d) * (w * d)); //特判超级管道
else dfs(to, w * d);
if(!f) return; //剪枝×2
}
}
}
bool check(double x)
{
f = 1;
memset(vis, 0, sizeof(vis));
vis[1] = 1;//记着起点也要做标记,(我会告诉你我因为没有把1做标记卡了两个小时吗?)
dfs(1, x);
if(f) return true;//直接 return f 更精简
return false;
}
讲解就到此为止,下面上总代码:
#include<bits/stdc++.h>
#define maxn 1010
using namespace std;
const double eps = 1e-3;
int n;
double k[maxn];
double ans;
struct edge
{
int to, pre, opt;
double dis;
}e[maxn << 1];
int head[maxn], cnt;
void add(int u, int v, double w, int t)
{
cnt++;
e[cnt].to = v;
e[cnt].dis = w;
e[cnt].opt = t;
e[cnt].pre = head[u];
head[u] = cnt;
}
bool f;
bool vis[maxn];
void dfs(int u, double w)
{
if(!f) return;
if(k[u] != -1)
{
if(w < k[u]) f = 0;
return;
}
for(int i = head[u]; i; i = e[i].pre)
{
int to = e[i].to;
double d = e[i].dis;
if(vis[to] == 0)
{
vis[to] = 1;
if(e[i].opt) dfs(to, (w * d) * (w * d));
else dfs(to, w * d);
if(!f) return;
}
}
}
bool check(double x)
{
f = 1;
memset(vis, 0, sizeof(vis));
vis[1] = 1;
dfs(1, x);
if(f) return true;
return false;
}
int main()
{
scanf("%d", &n);
int a, b, t;
double x;
for(int i = 1; i < n; i++)
{
scanf("%d%d%lf%d", &a, &b, &x, &t);
add(a, b, x / 100, t);
add(b, a, x / 100, t);
}
for(int i = 1; i <= n; i++)
{
scanf("%lf", &k[i]);
}
double l = 0;
double r = 2e9;
while(l + eps < r)
{
double mid = (l + r) / 2;
if(check(mid)) r = mid;
else l = mid;
}
printf("%.5lf", r); //要用printf
return 0; //结束罪恶的一生~~
}
最后一点,输出要用 printf 而不是 cout,因为后者精度不够,会WA。