2019雅禮集訓 D4T1 w [費用流]
阿新 • • 發佈:2019-01-09
題目描述:
樣例:
input1:
4 1 2
1 2 3 4
1 2
1 3
3 4
1 2
2 3
1 4
2
1 3
4 1
1
2 3
output1:
9
input2:
5 1 1
3 99 99 100 2
1 2
1 3
3 4
3 5
1 3
1 2
2 4
2 5
2
1 2
3 1
2
1 2
2 1
output2:
198
資料範圍:
標籤:費用流
先放個原題地址:CF1061E。
毒瘤出題人搬原題差評
毒瘤出題人題目翻譯出鍋差評
這題看到如此不倫不類的問法,似乎不是dp、貪心等演算法,而且資料範圍有如此之小,於是自然(個鬼)想到了費用流。
雖然如此,我由於做的題太少,賽場上仍然想不出如何建模。(其實賽場上就沒想到費用流)
注意到題目保證根節點一定會有限制,我們記有限制的點為關鍵點,不關鍵的點的貢獻可以算在關鍵點上。
可以想到,每個關鍵點\(p\)實際能夠操控的點\(x\)滿足:\(p\)是\(x\)的第一個關鍵祖先,記為\(id_x=p\)
我們再記每個關鍵點\(p\)能操控的\(x\)中能被啟用的點的總數為\(sum_p\),即\(p\)的子樹內可選節點減去其他關鍵點的可選節點。(怎麼越說越複雜……)
那麼:
一、源點向每個紅樹的關鍵點\(p\)連一條流量為\(sum_p\),費用為0的邊。
二、每個藍樹的關鍵點\(p\)
三、\(1\)至\(n\)的每個點\(x\)紅樹和藍樹分別有一個關鍵祖先\(p\),\(p'\)。由\(p\)向\(p'\)連一條流量為1,費用為\(value_x\)的邊。
第三點的邊走了就代表選了這個點,否則就是不選這個點。
最後跑一邊最大費用最大流即可。
判無解的方法:一棵樹內有關鍵點的可選節點為負數(即自相矛盾,許多人被出題人坑死在這個點上),最大流不能跑滿或兩棵樹總共選的節點數不一樣。
程式碼:
#include<bits/stdc++.h> namespace my_std{ using namespace std; #define mod 998244353 #define pii pair<int,int> #define fir first #define sec second #define MP make_pair #define rep(i,x,y) for (int i=(x);i<=(y);i++) #define drep(i,x,y) for (int i=(x);i>=(y);i--) #define go(x) for (int i=head[x];i;i=edge[i].nxt) #define sz 500500 typedef long long ll; template<typename T> inline void read(T& t) { t=0;char f=0,ch=getchar(); double d=0.1; while(ch>'9'||ch<'0') f|=(ch=='-'),ch=getchar(); while(ch<='9'&&ch>='0') t=t*10+ch-48,ch=getchar(); if(ch=='.') { ch=getchar(); while(ch<='9'&&ch>='0') t+=d*(ch^48),d*=0.1,ch=getchar(); } t=(f?-t:t); } template<typename T,typename... Args> inline void read(T& t,Args&... args){read(t); read(args...);} void file() { #ifndef ONLINE_JUDGE freopen("a.txt","r",stdin); #endif } inline ll mul(ll a,ll b){ll d=(ll)(a*(double)b/mod+0.5);ll ret=a*b-d*mod;if (ret<0) ret+=mod;return ret;} } using namespace my_std; int n; namespace tree { int rt0,rt1; struct hh{int t,nxt;}edge[sz]; int head[sz],ecnt; void make_edge(int f,int t) { edge[++ecnt]=(hh){t,head[f]}; head[f]=ecnt; edge[++ecnt]=(hh){f,head[t]}; head[t]=ecnt; } int w[sz],a[sz],id[sz],sum[sz],T; void dfs(int x,int fa) { if (a[x]) { sum[id[x]=++T]=a[x]; if (fa) sum[id[fa]]-=a[x]; if (fa&&sum[id[fa]]<0) puts("-1"),exit(0); } else id[x]=id[fa]; #define v edge[i].t go(x) if (v!=fa) dfs(v,x); #undef v } void init() { read(rt0,rt1);rt1+=n; int m,x,y; rep(i,1,n) read(w[i]); rep(i,1,n-1) read(x,y),make_edge(x,y); rep(i,1,n-1) read(x,y),make_edge(x+n,y+n); read(m); rep(i,1,m) read(x,y),a[x]=y; read(m); rep(i,1,m) read(x,y),a[x+n]=y; dfs(rt0,0);dfs(rt1,0); } } using tree::a;using tree::w;using tree::id;using tree::sum; struct hh{int t,w,dis,nxt;}edge[sz]; int head[sz],ecnt=1; void make_edge(int f,int t,int w,int dis) { edge[++ecnt]=(hh){t,w,dis,head[f]}; head[f]=ecnt; edge[++ecnt]=(hh){f,0,-dis,head[t]}; head[t]=ecnt; } int pre[sz],dis[sz],flow[sz]; bool in[sz]; int S,T; bool SPFA() { queue<int>q; memset(dis,~0x3f,sizeof(dis)); q.push(S); dis[S]=0;in[S]=1; flow[S]=INT_MAX; while (!q.empty()) { int x=q.front();q.pop();in[x]=0; #define v edge[i].t go(x) if (dis[v]<dis[x]+edge[i].dis&&edge[i].w>0) { dis[v]=dis[x]+edge[i].dis; pre[v]=i; flow[v]=min(flow[x],edge[i].w); if (!in[v]) q.push(v); in[v]=1; } #undef v } return dis[T]!=dis[0]; } int L,R; void build() { S=n*2+1;T=S+1; rep(i,1,n) if (a[i]) {make_edge(S,id[i],sum[id[i]],0);L+=sum[id[i]];} rep(i,n+1,n<<1) if (a[i]) {make_edge(id[i],T,sum[id[i]],0);R+=sum[id[i]];} rep(i,1,n) make_edge(id[i],id[i+n],1,w[i]); } int ans,mxflow; void update(){mxflow+=flow[T];ans+=flow[T]*dis[T];for (int x=T,y;(y=pre[x],x!=S);x=edge[y^1].t) edge[y].w-=flow[T],edge[y^1].w+=flow[T];} void MCF(){build();while (SPFA()) update();} int main() { file(); read(n); tree::init(); MCF(); printf("%d",L==R&&L==mxflow?ans:-1); }