杭州學軍中學信友隊邀請賽之第二題《齊心抗疫》

昨天,我講解了關於上週日比賽的第一題,這次,我來講一講關於比賽第二題的解題思路與程序。
首先,

題目

B. 齊心抗疫


時間限制:300ms
空間限制:512MB

題目描述

某市有n個縣,並有n-1條雙向高速公路連通這n個縣,每條高速公路的長度爲1。

受疫情影響,第i個縣裏有a[i]個患者。爲了讓疫情較輕的縣幫助疫情嚴重的縣,政府
決定選擇兩個縣x,y,x的疫情較爲嚴重, y的疫情較爲嚴重(即a[x] <= a[i]),並
讓縣x幫助縣y 。縣x將爲縣y的每一個患者送一份醫療物資,以最短路從x到y運
輸,運送一份醫療物資通過長度爲1的高速公路需要花費1元,由政府掏錢報銷。

請問如果任意選擇兩個縣實施幫扶計劃,政府最多要花多少錢?

輸入描述

第一行,一個正整數 。
第二行, 個正整數,第 個表示 。
接下來 行,每行兩個數 ,表示縣 和縣 之間有一條高速公路。

輸出描述

一個數表示答案。

樣例輸入

8
3 1 4 1 5 9 2 6
1 2
2 3
2 4
1 5
5 6
4 8
3 7

樣例輸出

45

嘿,這輪我們先把題目全部拽出來。

限制及約定

子任務編號 n<=n<= 分值
1 2 18
2 100 30
3 2000 19
4 50000 33

對於所有數據,2<=n<=500002<=n<=500001<=a[i]<=10001<=a[i]<=1000

題目分析

這個題目很容易理解,就是通過各方面條件讓政府花更多的錢。。。出題人一定是一個有錢的大佬。
於是,我們按照題目來看,就可以實現一種特別粗糙的算法:直接硬懟!不過經過上一道題,我猜測硬懟是不行的。

硬懟也有硬懟的方法。方法就是暴力枚舉。先枚舉從每一個點出發,通過遞歸查找需要花最多錢的路徑。此時,算法的時間複雜度應該是n3n2n^3|n^2。我們算一算,以n2n^2爲基礎,算一算能拿到多少分。首先222^2沒問題,能拿到18分。1002100^2也差不多,10000,能拿到30分。200022000^2似乎有點勉強。50000250000^2不用說了, 拿不到分。我們只能看看在實際上測試機器能拿到多少分。

在提交之前,我們需要解決很多問題。
首先,如何存儲可以走的路呢?
我立馬想到了點陣圖,map[i][j]說明i,j之間有通路。這種方法特點是快,但缺點是需要的空間很大,二維數組2000就差不多爆炸了。

其次,遞歸中間如何書寫?

還有很多問題,先讓我們寫寫程序吧。
超級慢版程序:

#include<iostream>
using namespace std;
int num[50001];
int map[2001][2001];
int mmax=-9999999;
int n;int sum;

int search(int w,int last,int llast)
{
	int mmmax=num[w]>num[llast]? w:llast;
	if(mmax<num[mmmax]*sum) mmax=num[mmmax]*sum;
	int flag=0;
	for(int i=1;i<=n;i++)
	{
		if(map[w][i]&&i!=last)
		{
			flag=1;
			sum++;
			search(i,w,llast);
			sum--;
		}
	}
}

int main()
{
	cin>>n;
	for(int i=1;i<=n;i++)
		cin>>num[i];
	for(int i=1;i<n;i++)
	{
		int x,y;
		cin>>x>>y;
		map[x][y]=1;
		map[y][x]=1;
	}
	int w;
	for(int i=1;i*i<=n;i++)
	{
		sum=0;
		w=search(i,0,i);
	}
	cout<<mmax;
	return 0;
}

來吧,我們來看看得了多少分吧!
在這裏插入圖片描述
很顯然,超時得到48分。我就說,暴力肯定不行。

接着分析題目

接着,我思考思考…ei,我們還沒有剖解這道題終究需要求什麼。

剖析

假設f(x,y)f(x,y)表示從x到y的最短路徑,列出方程式,我們要求的就是:
max(a(x),a(y))f(x,y) max(a(x),a(y))*f(x,y)
可以分解爲:
max(a(x)f(x,y),a(y)f(x,y)) max(a(x)*f(x,y),a(y)*f(x,y))
那就很明白了,只要將f(x,y)和x最大化就OK哩;
接下來,我引入一個概念:樹的直徑
在一棵樹裏,最遠的兩個點構成的路徑就是樹的直徑。直徑的兩個端點上距離任何一個點都要遠。
我來證明下吧。

假設點A與點B是在一棵樹上的,且他們之間的線段就是樹的直徑:
點AB
在它們之間,有一個瞎參和的 點C。
在這裏插入圖片描述接着又來一個瞎參和的 的點D。
瞎參和了兩個!
接着問題來了!從點C到點B是最遠的還是到點D最遠?
到點B?有可能。
到點D?也有可能。來,咱們再來畫圖:
在這裏插入圖片描述用紅筆圈出的部分顯然相同,剩下的線段比長度可以換成:誰距離A最遠。那就肯定是B了,要不然相對於點A到點B是樹上的最長路徑這條依據。
到此爲止,論證結束,無論如何,點A到點C或點B到點C是最長的路徑。

程序實現

接下來,就是令人歡喜的程序實現部分了!稍微有點長,諒解。大家慢慢看。

#include<iostream>
using namespace std;
int a[50001];
int head[200005],nxt[200005],to[200005];
int de[50001],dee[50001],deep[50001];
int tot=0;       //所在點 

void add(int u,int v)     //增加路徑 
{
    nxt[++tot]=head[u];
    to[tot]=v;
    head[u]=tot;
}

void dfs(int x,int y,int *d)
{
    d[x]=d[y]+1;
    for(int i=head[x];i;i=nxt[i])
    {
        if(to[i]!=y)
        {
            dfs(to[i],x,d);
        }
    }
}

int main()
{
    int n,u,v,root2=0,ma=0,root1=0;
    cin>>n;
    for(int i=1;i<=n;i++)
    {
        cin>>a[i];
    }
    for(int i=1;i<=n-1;i++)
    {
        cin>>u>>v;
        add(u,v);add(v,u);
    }
    dfs(1,0,de);
    for(int i=1;i<=n;i++)
    {
        if(de[i]>ma)
        {
            ma=de[i];
            root1=i;
        }
    }
    dfs(root1,0,dee);
    ma=0;
    for(int i=1;i<=n;i++)
    {
        if(dee[i]>ma)
        {
            ma=dee[i];
            root2=i;
        }
    }
    dfs(root2,0,deep);
    int res=0;
    for(int i=1;i<=n;i++)
    {
        res=max(res,max(dee[i]-1,deep[i]-1)*a[i]);
    }
    cout<<res;
    return 0;
}

在這裏插入圖片描述
感謝大家支持!


今天看見有人關注我了?哦豁,第一個哦!!!

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章