【NOI2014】魔法森林
爲了得到書法大家的真傳,小同學下定決心去拜訪住在魔法森林中的隱士。魔法森林可以被看成一個包含個節點條邊的無向圖,節點標號爲 ,邊標號爲。初始時小E同學在 11 號節點,隱士則住在 號節點。小E需要通過這一片魔法森林,才能夠拜訪到隱士。
魔法森林中居住了一些妖怪。每當有人經過一條邊的時候,這條邊上的妖怪就會對其發起攻擊。幸運的是,在 號節點住着兩種守護精靈:型守護精靈與型守護精靈。小可以藉助它們的力量,達到自己的目的。
只要小帶上足夠多的守護精靈,妖怪們就不會發起攻擊了。具體來說,無向圖中的每一條邊 eiei 包含兩個權值 aiai 與 bibi。若身上攜帶的A型守護精靈個數不少於 ,且B型守護精靈個數不少於 ,這條邊上的妖怪就不會對通過這條邊的人發起攻擊。當且僅當通過這片魔法森林的過程中沒有任意一條邊的妖怪向小發起攻擊,他才能成功找到隱士。
由於攜帶守護精靈是一件非常麻煩的事,小想要知道,要能夠成功拜訪到隱士,最少需要攜帶守護精靈的總個數。守護精靈的總個數爲型守護精靈的個數與型守護精靈的個數之和。
輸入格式
第行包含兩個整數 ,表示無向圖共有 個節點, 條邊。
接下來 行,第 行包含個正整數 ,描述第 條無向邊。其中 與 爲該邊兩個端點的標號, 與 的含義如題所述。
注意數據中可能包含重邊與自環。
輸出格式
輸出一行一個整數:如果小可以成功拜訪到隱士,輸出小E最少需要攜帶的守護精靈的總個數;如果無論如何小都無法拜訪到隱士,輸出(不含引號)。
樣例一
input
4 5
1 2 19 1
2 3 8 12
2 4 12 15
1 3 17 8
3 4 1 17
output
32
explanation
如果小E走路徑1→2→4,需要攜帶 19+15=3419+15=34 個守護精靈;
如果小E走路徑1→3→4,需要攜帶 17+17=3417+17=34 個守護精靈;
如果小E走路徑1→2→3→4,需要攜帶 19+17=3619+17=36 個守護精靈;
如果小E走路徑1→3→2→4,需要攜帶 17+15=3217+15=32 個守護精靈。
綜上所述,小E最少需要攜帶 3232 個守護精靈。
樣例二
input
3 1
1 2 1 1
output
-1
explanation
小無法從號節點到達號節點,故輸出。
題意
- 就是給你一張無向圖,每一條邊都有兩個屬性和,你擁有兩個屬性值和,能通過一條邊當且僅當且,通過後和不變,求滿足能從走到的最小
題解
- 將所有邊按照屬性排序,然後遍歷所有邊,用維護屬性的最小生成樹,當遇到環時,即即將加入的邊的兩個端點已經在同一個聯通塊中時,找到現在樹中從到的最大值位置,如果,則先將樹從處斷開,然後再加入當前邊,否則判斷1與n是否在同一個集合中,如果是,查詢最大值並對取
- 這裏小技巧是將邊權轉化爲點權的方法:兩點之間再新建一個節點,節點權值設爲邊權
代碼
#include<bits/stdc++.h>
using namespace std;
const int maxn=2e5+10;
#define inf 0x3f3f3f3f
namespace LCT{
int ch[maxn][2],fa[maxn],sta[maxn],mark[maxn];
int val[maxn],ans[maxn];
inline bool not_root(int x) {
return ch[fa[x]][0]==x||ch[fa[x]][1]==x;
}
inline int dir(int x) {
return ch[fa[x]][1]==x;
}
inline void add_mark(int x) { //將x這顆子樹翻轉
swap(ch[x][0],ch[x][1]);
mark[x]^=1;
}
inline void push_down(int x) {
if(mark[x]) {
if(ch[x][0]) add_mark(ch[x][0]);
if(ch[x][1]) add_mark(ch[x][1]);
mark[x]=0;
}
}
inline void push_up(int x) {
ans[x]=x;
if(val[ans[ch[x][0]]]>val[ans[x]]) ans[x]=ans[ch[x][0]];
if(val[ans[ch[x][1]]]>val[ans[x]]) ans[x]=ans[ch[x][1]];
}
inline void pushall(int x) {
if(not_root(x)) pushall(fa[x]);
push_down(x);
}
inline void rotate(int x){
int y=fa[x],z=fa[y],k=dir(x);
if(ch[x][k^1]) fa[ch[x][k^1]]=y;ch[y][k]=ch[x][k^1];
if(not_root(y)) ch[z][dir(y)]=x;fa[x]=z;
ch[x][k^1]=y;fa[y]=x;
push_up(y);push_up(x);
}
inline void splay(int x,int goal=0) {
pushall(x);
while(not_root(x)) {
int y=fa[x],z=fa[y];
if(not_root(y)) {
if(dir(x)==dir(y)) rotate(y);
else rotate(x);
}
rotate(x);
}
//push_up(x);
}
inline void access(int x) { //從原樹的根向x拉一條實鏈
for(int y=0;x;y=x,x=fa[x]) {
splay(x);ch[x][1]=y;push_up(x);
}
}
inline void make_root(int x) { //使x成爲splay的根
access(x);splay(x);add_mark(x);
}
inline int find_root(int x) { //找到x在原樹中的根
access(x);splay(x);
while(ch[x][0]) push_down(x),x=ch[x][0];
splay(x);
return x;
}
inline void split(int x,int y) { //拉出一條x->y的實鏈,y爲splay根
make_root(x);access(y);splay(y);
}
inline bool link(int x,int y) { //連接x與y,若已經在同一顆原樹中,返回0
make_root(x);
if(find_root(y)==x) return 0;
fa[x]=y;return 1;
}
inline bool cut(int x,int y) {
make_root(x);
if(find_root(y)!=x||fa[y]!=x||ch[y][0]) return 0;
fa[y]=ch[x][1]=0;
push_up(x);
return 1;
}
inline int query(int l,int r) {
split(l,r);
return ans[r];
}
};
using namespace LCT;
struct edge{
int u,v,a,b;
friend bool operator<(const edge &c,const edge &d) {
return c.a<d.a;
}
}o[maxn];
int n,m;
int main()
{
scanf("%d %d",&n,&m);
for(int i=1;i<=m;i++) scanf("%d %d %d %d",&o[i].u,&o[i].v,&o[i].a,&o[i].b);
sort(o+1,o+m+1);
int ans=inf;
for(int i=1;i<=m;i++) {
if(find_root(o[i].u)==find_root(o[i].v)){
int u=query(o[i].u,o[i].v);
if(o[i].b<val[u]) {
cut(o[u-n].u,u);
cut(o[u-n].v,u);
}else {
if(find_root(1)==find_root(n)) ans=min(ans,o[i].a+val[query(1,n)]);
continue;
}
}
val[i+n]=o[i].b;
link(o[i].u,i+n);link(o[i].v,i+n);
if(find_root(1)==find_root(n)) ans=min(ans,o[i].a+val[query(1,n)]);
}
if(ans==inf) printf("%d\n",-1);
else printf("%d\n",ans);
}