【省选模拟试题】减肥 树的分治+排序

【问题描述】

  小明长的很胖,为了减肥,他经常到附近的郊区做运动。

  那里有N个村庄,编号为1~N,而且对于任意两个村庄,最多只有1条双向公路连接他们。另外,任意两个村庄之间有且仅有一条路径(公路的序列)。每条公路具有一定的长度w,小明会选择一条路径,路径长度在[S,E]范围内,但小明很懒惰,他希望能找到一条长度为k的路径,k满足:k属于[S,E],且k必须是最小的,你帮助他吗?

【输入格式】

  第1行包含三个整数N,S,E,接下来的N-1行,每一行包含三个整数:u,v,w,表示村子u和v之间具有一条长度为w的公路。

【输出格式】

  一个整数k,如果没有任何路径的长度在[S,E]的范围内,则输出-1。

【输入样例】

5 10 40
2 4 80
2 3 57
1 2 16
2 5 49

【输出样例】

16

【数据范围】

30%的数据满足:2<=N<=1000,1<=S<=E<=3000
50%的数据满足:2<=N<=5000,1<=S<=E<=30000000
50%的数据满足:2<=N<=30000,1<=w<=10000,1<=S<=E<=30000000

——————————————————————————————————————————————————

如果这个题是考试题,我没写出正解也可以笑疯。。。。
因为。。。。

我的50分暴力写出了80分233333333,这表明了用题目条件适当剪枝的重要性。
这里写图片描述
所以各位打暴力的时候也请走心嗯。

说正解,树上分治。
对于当前子树,找重心然后计算dist,显然dist在[S,E]之间的可以用来更新答案。大于E的丢掉,没有意义。
显然,对于一颗树,答案只有两种:不经过根结点经过根结点的路径。经过根结点的路径的话显然可以用排序来解决,一端为根结点的路径的话直接看dist就可以了;不经过根节点的就是同构的子问题,分治来解决。
但是注意经过根结点的路径是有可能在同一颗子树中的,意味着这不是合法的树上路径,可能这样的路径会干扰最优解的生成,应该掐掉(简单来说就是一颗子树在前面的子树里面去找答案不要在这棵子树里面找答案,还有找重心的时候记得带上当前树的结点数量)。
当然日常找个重心来优化递归的深度问题。时间复杂度O(NlogN*logN)。

(表示我家题库太牛逼要制裁STL,这里还是给出set的代码吧,可以自己改成归并排序,话说对于大量数字有序的序列排序的时候归并还是挺快的)

AC代码:

#include<cstdio>
#include<cstring>
#include<cstdlib>
#include<algorithm>
#include<cmath>
#include<queue>
#include<set>
#include<map>
#include<vector>
#include<cctype>
#define inf 1e9+5
using namespace std;
const int maxn=30005;

int N,S,T,ans;
struct edge{ int to,next,w; }E[maxn<<1];
int first[maxn],np,dist[maxn],sz[maxn],MAX=inf,root;
bool mark[maxn];
set<int>Set;
int A[maxn],cnt;

void _scanf(int &x)
{
    x=0;
    char ch=getchar();
    while(ch<'0'||ch>'9') ch=getchar();
    while(ch>='0'&&ch<='9') x=x*10+ch-'0',ch=getchar();
}
void add_edge(int u,int v,int w)
{
    E[++np]=(edge){v,first[u],w};
    first[u]=np;
}
void data_in()
{
    _scanf(N);_scanf(S);_scanf(T);
    int x,y,z;
    for(int i=1;i<N;i++)
    {
        _scanf(x);_scanf(y);_scanf(z);
        add_edge(x,y,z);
        add_edge(y,x,z);        
    }
}
void getroot(int i,int f,int &o,int tot)
{
    sz[i]=1;
    int tmp=0;
    for(int p=first[i];p;p=E[p].next)
    {
        int j=E[p].to;
        if(j==f||mark[j]) continue;
        getroot(j,i,o,tot);
        sz[i]+=sz[j];
        if(sz[j]>tmp) tmp=sz[j];
    }
    if(tot-sz[i]>tmp) tmp=N-sz[i];
    if(tmp<MAX) MAX=tmp,o=i;
}
void calc(int i,int f,int l,int rt)
{
    dist[i]=l;
    if(dist[i]>=S && dist[i]<ans) ans=dist[i];
    if(i!=rt) A[++cnt]=dist[i];
    for(int p=first[i];p;p=E[p].next)
    {
        int j=E[p].to;
        if(j==f||mark[j]) continue;
        calc(j,i,l+E[p].w,rt);
        if(i==rt)
        {
            if(Set.size()!=0)
            {
                for(int k=1;k<=cnt;k++)
                {
                    int v=*Set.lower_bound(S-A[k]);
                    if(v+A[k]>=S && v+A[k]<ans) ans=v+A[k];
                }
            }
            for(int k=1;k<=cnt;k++)
                if(A[k]<ans) Set.insert(A[k]);
            cnt=0;
        }
    }
}
void DFS(int i,int tot)
{
    MAX=N+1;
    getroot(i,0,root,tot);
    mark[root]=1;
    calc(root,0,0,root);
    Set.clear();
    for(int p=first[root];p;p=E[p].next)
    {
        int j=E[p].to;
        if(mark[j]) continue;
        DFS(j,sz[j]);
    }
}
void work_100()
{
    ans=T+1;
    DFS(1,N);
    if(ans==T+1) ans=-1;
    printf("%d\n",ans);
}
int main()
{
    freopen("test.in","r",stdin);
    freopen("test.out","w",stdout);
    data_in();
    work_100();
    return 0;
}
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章