這道題很明顯就是一道最小生成樹的題,但是我交了17次才AC。。。
本題用Prim是應該是過不了的(即使用了堆優化),只能用Kruskal過。但是用Kruskal要注意以下幾點:
1.排序問題,快排是不行的,算了一下,快排在這道題中最壞的時間頻度是1.2e8+,這樣子基本上是很難卡過去的,何況還有常數係數。解決方案是用桶排序。
2.並查集優化,本題數據量很大,如果並查集最後數據成了一條鏈,那必定是涼涼的了。解決方案是用路徑壓縮或秩優化。
3.輸入問題,用scanf會TLE,得自己寫個輸入掛(小型的就好了)。
4.滿足以上3點了以後,還不一定能夠AC,上面三點可能比較容易想到,這最後一點可能就沒那麼容易了,就是剪枝!我最後是加了兩處剪枝才過的!
另外附上桶排序的一篇講解:
https://www.jianshu.com/p/204ed43aec0c
代碼如下:
#include<algorithm>
#include<cstring>
#include<cstdio>
#include<iostream>
#include<vector>
using namespace std;
typedef long long ll;
const int maxn=1e6+6;
ll T[maxn];
int head[1005];
int fa[maxn],rk[maxn],cnt;
struct edge
{
ll u,v,w;
int next;
}e[maxn<<3];
int find(int rt)
{
int son=rt,temp;
while(rt!=fa[rt])
rt=fa[rt];
while(son!=rt) //路徑壓縮
{
temp=fa[son];
fa[son]=rt;
son=temp;
}
return rt;
}
bool Kruskal(ll t)
{
ll sum=0;
for(int i=0;i<=1000;++i)
{
if(head[i]==-1) continue;
for(int j=head[i]; j!=-1 ;j=e[j].next)
{
int a=find(e[j].u), b=find(e[j].v);
if(a!=b)
{
sum+=e[j].w;
if(rk[a]>rk[b]) //秩優化
fa[b]=a;
else
{
fa[a]=b;
if(rk[a]==rk[b])
++rk[b];
}
if(sum>t) //剪枝1(很重要)
return 0;
}
}
}
return sum<=t;
}
void addedge(ll u,ll v,ll w) //添加邊的同時,進行桶排序
{
e[cnt].u=u; e[cnt].v=v; e[cnt].w=w; e[cnt].next=head[w]; head[w]=cnt++;//後面兩句就是桶排序
}
inline void read(ll &ret)
{
char c;
while((c=getchar())&&(c>'9'||c<'0'));
ret=0;
while(c>='0'&&c<='9') ret=ret*10+c-'0', c=getchar();
}
int main()
{
ll n,m,k,t;
read(n); read(m);
read(k); read(t);
memset(head,-1,sizeof(head));
for(int i=0;i<=n;++i)
{
fa[i]=i;
rk[i]=0;
}
for(int i=1;i<=n;++i)
read(T[i]);
cnt=0;
for(int i=1;i<=k;++i)
{
ll K;
read(K);
T[K]=0;
}
ll sum=0;
for(int i=1;i<=n;++i)
{
addedge(0,i,T[i]);
sum+=T[i];
}
for(int i=1;i<=m;++i)
{
ll u,v,w;
read(u); read(v); read(w);
addedge(u,v,w);
}
if(sum<=t) //剪枝2(不知重不重要...)
{
printf("Yes\n");
return 0;
}
if(Kruskal(t))
printf("Yes\n");
else printf("No\n");
return 0;
}
小結:
1.數據嚴苛的情況下,排序問題可以考慮桶排序(在本題中,邊權最大爲1000,所以也不難想到可以建立1000個“桶”),或可以大大優化時間;
2.並查集在數據嚴苛的情況下,也要考慮優化,路徑壓縮 / 秩優化,個人感覺秩優化會好一些;
3.瘋狂TLE,走投無路時可以多想想剪枝,或者一開始在做題的時候就考慮剪枝,可能有奇效。