#2416. 点燃的火焰(flame)

题目描述
bx2k发明了许多有趣的物品。因为他是一个神犇。他决定使用一种古老的计时方法:燃烧绳子。

bx2k有许多长短不一粗细均匀的绳子,如果它的一头被点燃,每秒会沿着绳子的方向烧去一个单位长度。

bx2k把 $n$ 根绳子排成了一棵树($n$ 个点 $n-1$ 条边的无向连通图)的形状。其中绳子代表边。两根绳子只会在端点处接触。如果一根绳子的某一端开始燃烧,与它接触的绳子也会同时被点着。

bx2k只能在开始时同时点燃树的若干个叶子节点。现在他想知道他可以统计出多少种不同的时间。一种时间可以被统计当且仅当存在一种点燃叶子的方式使得从点燃开始到整棵树燃烧殆尽的时间恰好为该时间。

由于答案可能很多,请你输出方案数对 $998244353$($=7×17×223+1$,一个质数)取模的结果。并输出前 $1000$ 小的可被统计的时间。
数据范围
对于100%的数据,$2 \le n \le 500,1 \le w \le 10000$
题解
将根转化成有根树

考虑暴力,由于只有叶子结点被烧,所以 $2^n$ 枚举叶子一开始烧的状态,然后考虑如何快速统计答案

可以转化为每个点什么时候开始被烧,记为 $f$ 值,初始时只有要烧的叶子是 $0$ ,其余是 $inf$ ,然后这样的话就可以 $dfs$ 两遍,第一遍是从下往上更新,第二遍是从下往上更新,就可以处理出 $f$ ,然后每条边 $(x,y)$ 的答案就是 $\frac{f_x+f_y+w}{2}$ ,这里可能被烧的地方不在这条边上,不过没有影响的

然后考虑正解,发现被烧的情况可以分为两种,要么是两个被烧叶子之间是最远的,要么一个被烧叶子,一个未被烧叶子是最远的,所以我们可以枚举两个叶子结点,然后我们的目的是尽量使得这个方案成为答案,所以我们将能烧的叶子结点全部烧掉,再用上述方法判断这个方案是否是答案即可

具体讲判断叶子结点能否被烧掉,假设两个叶子结点是 $a,b$

1. $a,b$ 都烧:
考虑叶子结点 $c$ ,则如果 $dis(a,b) \le dis(b,c)$ 或者 $dis(a,b) \le dis(a,c)$ , $c$ 点就可以被烧,可以画图理解一下

2. $a$ 烧 $b$ 不烧:
考虑叶子结点 $c$ ,则如果 $dis(a,b) \le dis(b,c)$ , $c$ 点就可以被烧,可以画图理解一下

效率:$O(n^3+n^2logn)$
代码

#include <bits/stdc++.h>
using namespace std;
const int N=505,M=N<<1;
int n,hd[N],V[M],nx[M],t,in[N],W[M],h[N],H,rt;
int s[N],d[N][N],fa[N],dp[N],A[N*N],S,q[N],Q;
void add(int u,int v,int w){
    nx[++t]=hd[u];in[v]++;V[hd[u]=t]=v;W[t]=w;
}
void dfs(int u,int fr){
    fa[u]=fr;dp[u]=dp[fr]+1;
    for (int i=hd[u];i;i=nx[i])
        if (V[i]!=fr) s[V[i]]=s[u]+W[i],dfs(V[i],u);
}
int Dfs(int u){
    for (int i=hd[u];i;i=nx[i]) if (V[i]!=fa[u])
        Dfs(V[i]),s[u]=min(s[u],s[V[i]]+W[i]);
    return s[u];
}
int work(){
    for (int i=1;i<=n;i++) s[i]=1e9;
    for (int i=1;i<=Q;i++) s[q[i]]=0,q[i]=0;
    s[rt]=Dfs(rt);int ss=0,l=1,r=1;q[1]=rt;
    while(l<=r){
        int x=q[l++];
        for (int i=hd[x];i;i=nx[i])
            if (V[i]!=fa[x]){
                s[V[i]]=min(s[V[i]],s[x]+W[i]);
                ss=max(s[x]+s[V[i]]+W[i],ss);
                q[++r]=V[i];
            }
    }
    return ss;
}
bool J(int a,int b){
    q[1]=a;q[Q=2]=b;
    for (int i=1;i<=H;i++)
        if (h[i]!=a && h[i]!=b)
            if (d[a][b]<=d[b][h[i]]||d[a][b]<=d[a][h[i]]) q[++Q]=h[i];
    return work()==d[a][b];
}
bool G(int a,int b){
    q[Q=1]=a;
    for (int i=1;i<=H;i++)
        if (h[i]!=a && h[i]!=b)
            if (d[a][b]<=d[b][h[i]]) q[++Q]=h[i];
    return work()==(d[a][b]<<1);
}
int main(){
    scanf("%d",&n);
    for (int u,v,w,i=1;i<n;i++)
        scanf("%d%d%d",&u,&v,&w),
        add(u,v,w),add(v,u,w);
    for (int i=1;i<=n;i++)
        if (in[i]<2) h[++H]=i;
        else rt=i;dfs(rt,0);
    for (int x,y,i=1;i<=n;i++)
        for (int j=i;j<=n;j++){
            x=i,y=j;if (dp[x]<dp[y]) swap(x,y);
            while(dp[x]>dp[y]) x=fa[x];
            while(x!=y) x=fa[x],y=fa[y];
            d[i][j]=d[j][i]=s[i]-s[x]+s[j]-s[x];
        }
    for (int i=1;i<=H;i++)
        for (int j=1;j<=H;j++){
            if (i<j && J(h[i],h[j])) A[++S]=d[h[i]][h[j]];
            if (i!=j && G(h[i],h[j])) A[++S]=(d[h[i]][h[j]]<<1);
        }
    sort(A+1,A+S+1);S=unique(A+1,A+S+1)-A-1;
    printf("%d\n",S);S=min(S,1000);
    for (int i=1;i<=S;i++){
        printf("%d",A[i]>>1);
        if (A[i]&1) putchar('.'),putchar('5');
        putchar(i<S?' ':'\n');
    }
    return 0;
}

 

posted @ 2019-08-05 20:12  xjqxjq  阅读(285)  评论(0编辑  收藏  举报