【省選模擬試題】減肥 樹的分治+排序

【問題描述】

  小明長的很胖,爲了減肥,他經常到附近的郊區做運動。

  那裏有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;
}
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章