帶權二分
阿新 • • 發佈:2018-11-29
邊界條件 例題 efi 決策單調 http 二分 content sort desc
帶權二分
一種二分答案的套路,又叫做DP凸優化,wqs二分。
用來解決一類題目,要求某個要求出現K次,並且,可以很顯然的發現,在改變相應權值的時候,對應出現的次數具有單調性。而且很顯然,這種題一般滿足一定的要求。而且一般權值為整數二分就可以,但是有的題需要實數二分...而且,邊界條件通常很麻煩,調起來想摔電腦。
例題時間:
BZOJ2654: tree
題目大意:給你一個圖,裏面有白色邊和黑色邊,問恰好有k條邊白色邊的最小生成樹
直接貪心法肯定是錯誤的,因此,我們考慮帶權二分。
給定一個選擇白色邊的權值,之後把白色邊的邊權減去這個權值跑最小生成樹,判斷是否選了K個白色邊。而這個權值通過二分找到。
附上代碼:
#include <cstdio> #include <algorithm> #include <cmath> #include <cstring> #include <iostream> #include <queue> using namespace std; #define N 50005 int fa[N],n,m,K,cnt; struct node { int from,to,op; int val; }e[N<<1]; int sum,num; inline bool cmp(const node &a,const node &b) { if(a.val==b.val) return a.op>b.op; return a.val<b.val; } inline int find(int x) { if(x==fa[x])return x; return fa[x]=find(fa[x]); } inline void add(int x,int y,int z,int op) { e[++cnt].to=y; e[cnt].from=x; e[cnt].val=z; e[cnt].op=op; return ; } void init() { for(int i=0;i<N;i++) { fa[i]=i; } return ; } inline int check(int x) { sum=0,num=0; init(); for(int i=1;i<=m;i++) { e[i].val+=e[i].op*x; } sort(e+1,e+m+1,cmp); int cnt1=0; for(int i=1;i<=m;i++) { int x=e[i].from,y=e[i].to; int fx=find(x),fy=find(y); if(fx!=fy) { sum+=e[i].val; num+=e[i].op; fa[fx]=fy; cnt1++; } if(cnt1==n-1)break; } for(int i=1;i<=m;i++) { e[i].val-=e[i].op*x; } if(num>=K)return 1; return 0; } int main() { scanf("%d%d%d",&n,&m,&K); for(int i=1;i<=m;i++) { int x,y,z,j; scanf("%d%d%d%d",&x,&y,&z,&j); add(x,y,z,j^1); } int l=-101,r=101; int ans; while(l<r) { int mid=(l+r)>>1; if(check(mid)) { l=mid+1; ans=sum-K*mid; }else { r=mid; } } printf("%d\n",ans); return 0; }
BZOJ5311: 貞魚
題目大意:給你n個點,每個點與點之間有權值,將n個點分成k份,每份是連續的,每份的代價是這份中任意兩點的權值和,求最小代價。
二分分割一次的權值,之後在DP的時候轉移一下即可。至於DP的東西去看https://www.cnblogs.com/Winniechen/p/9218864.html
附上代碼:
#include <cstdio> #include <cmath> #include <algorithm> #include <iostream> #include <queue> #include <cstdlib> #include <cstring> using namespace std; #define N 4005 static char buf[1000000],*p1,*p2; #define nc() (p1==p2&&(p2=(p1=buf)+fread(buf,1,1000000,stdin),p1==p2)?EOF:*p1++) #define calc(x,y) (f[x]+((sum[y][y]+sum[x][x]-sum[y][x]-sum[x][y])>>1)) int rd() { register int x=0;register char c=nc(); while(c<‘0‘||c>‘9‘)c=nc(); while(c>=‘0‘&&c<=‘9‘)x=(((x<<2)+x)<<1)+c-‘0‘,c=nc(); return x; } int sum[N][N],num[N],k,n,s[N][N];long long f[N]; struct node{int l,r,p;}q[N]; bool cmp(int i,int j,int k) { long long t1=calc(i,k),t2=calc(j,k); if(t1==t2)return num[i]<=num[j]; return t1<t2; } int find(const node &t,int x) { int l=t.l,r=t.r+1; while(l<r) { int m=(l+r)>>1; if(cmp(x,t.p,m))r=m; else l=m+1; } return l; } int check(int x) { memset(f,0x3f,sizeof(f)); f[0]=0;int h=0,t=0;q[t++]=(node){1,n,0};num[0]=0; for(int i=1;i<=n;i++) { if(q[h].r<i&&h<t)h++; f[i]=calc(q[h].p,i)+x;num[i]=num[q[h].p]+1; if(cmp(i,q[t-1].p,n)) { while(h<t&&cmp(i,q[t-1].p,q[t-1].l))t--; if(h==t)q[t++]=(node){i+1,n,i}; else { int p=find(q[t-1],i); q[t-1].r=p-1; q[t++]=(node){p,n,i}; } } } return num[n]; } int main() { n=rd();k=rd(); for(register int i=1;i<=n;i++) { for(register int j=1;j<=n;j++) { sum[i][j]=sum[i][j-1]+rd(); } } for(register int i=1;i<=n;i++) { for(register int j=1;j<=n;j++) { sum[i][j]+=sum[i-1][j]; } } int l=0,r=1<<30; while(l<r) { int m=(l+r)>>1; if(check(m)>k)l=m+1; else r=m; } check(l); printf("%lld\n",f[n]-1ll*l*k); }
BZOJ1812: [Ioi2005]riv
題目大意:沒有題目大意,直接去看題面吧...
Description
幾乎整個Byteland王國都被森林和河流所覆蓋。小點的河匯聚到一起,形成了稍大點的河。就這樣,所有的河水都匯聚並流進了一條大河,最後這條大河流進了大海。這條大河的入海口處有一個村莊——名叫Bytetown 在Byteland國,有n個伐木的村莊,這些村莊都座落在河邊。目前在Bytetown,有一個巨大的伐木場,它處理著全國砍下的所有木料。木料被砍下後,順著河流而被運到Bytetown的伐木場。Byteland的國王決定,為了減少運輸木料的費用,再額外地建造k個伐木場。這k個伐木場將被建在其他村莊裏。這些伐木場建造後,木料就不用都被送到Bytetown了,它們可以在 運輸過程中第一個碰到的新伐木場被處理。顯然,如果伐木場座落的那個村子就不用再付運送木料的費用了。它們可以直接被本村的伐木場處理。 註意:所有的河流都不會分叉,也就是說,每一個村子,順流而下都只有一條路——到bytetown。 國王的大臣計算出了每個村子每年要產多少木料,你的任務是決定在哪些村子建設伐木場能獲得最小的運費。其中運費的計算方法為:每一塊木料每千米1分錢。 編一個程序: 1.從文件讀入村子的個數,另外要建設的伐木場的數目,每年每個村子產的木料的塊數以及河流的描述。 2.計算最小的運費並輸出。
Input
第一行 包括兩個數 n(2<=n<=100),k(1<=k<=50,且 k<=n)。n為村莊數,k為要建的伐木場的數目。除了bytetown外,每個村子依次被命名為1,2,3……n,bytetown被命名為0。 接下來n行,每行包涵3個整數 wi——每年i村子產的木料的塊數 (0<=wi<=10000) vi——離i村子下遊最近的村子(或bytetown)(0<=vi<=n) di——vi到i的距離(km)。(1<=di<=10000) 保證每年所有的木料流到bytetown的運費不超過2000,000,000分 50%的數據中n不超過20。
Output
輸出最小花費,精確到分。 這道題其實並不用帶權二分的,直接樹形DP,N^2*K(N^2*K^2)都可以過...帶權二分跑不過N^2*K...二分建立鎮守府的權值,狀態f[i][j]表示i的子樹中,所有節點最多到第j層父親的最小代價,之後再記錄一個建立了多少個鎮守府即可。 附上代碼:
#include <cstdio> #include <algorithm> #include <cmath> #include <cstring> #include <iostream> #include <cstdlib> #include <queue> #include <bitset> using namespace std; #define N 105 struct node { int to,next; }e[N<<1]; int a[N],head[N],dis[N],v[N],cnt,n,m,siz[N],fa[N][N],sf[N],num[N][N],f[N][N],mid; void add(int x,int y){e[cnt].to=y;e[cnt].next=head[x];head[x]=cnt++;} void pre(int x) { fa[x][0]=x; for(int i=head[x];i!=-1;i=e[i].next) { int to1=e[i].to;dis[to1]=dis[x]+v[to1]; for(int j=0;j<=sf[x];j++)fa[to1][++sf[to1]]=fa[x][j]; pre(to1); } } void dfs(int x) { for(int i=0,t;i<=sf[x];i++)t=fa[x][i],f[x][i]=(dis[x]-dis[t])*a[x]; for(int i=head[x];i!=-1;i=e[i].next) { int to1=e[i].to;dfs(to1); for(int j=0;j<=sf[x];j++) { if(f[to1][0]+mid<f[to1][j+1]||(f[to1][0]==f[to1][j+1]&&num[to1][0]+1<=num[to1][j+1])) { num[x][j]+=num[to1][0]+1; f[x][j]+=f[to1][0]+mid; }else f[x][j]+=f[to1][j+1],num[x][j]+=num[to1][j+1]; } } } int check() { memset(f,0x3f,sizeof(f));memset(num,0,sizeof(num));dfs(0); return num[0][0]; } int main() { memset(head,-1,sizeof(head)); scanf("%d%d",&n,&m); for(int i=1,x;i<=n;i++)scanf("%d%d%d",&a[i],&x,&v[i]),add(x,i); int l=0,r=1<<21;pre(0); while(l<r) { mid=(l+r)>>1; if(check()>m)l=mid+1; else r=mid; }mid=l;check(); printf("%lld\n",f[0][0]-1ll*mid*m); return 0; }
BZOJ4609: [Wf2016]Branch Assignment
題目沒有大意
直接求出dis[i]表示正向從i到b+1,和反向從b+1到i的權值和,之後我們發現,將dis[i]排序,之後可以得出,取連續的一段必定不會更劣(貪心可證,為了使更小的dis分配到更大的siz)之後通過決策單調性轉移一下即可。
附上代碼:
#include <cstdio> #include <cmath> #include <algorithm> #include <iostream> #include <queue> #include <cstdlib> #include <cstring> #include <vector> using namespace std; #define N 5005 #define ll long long #define calc(x,y) (f[x]+(sum[y]-sum[x])*(y-x-1)) __attribute__((optimize("-O3")))inline char nc() { static char buf[100000],*p1,*p2; return p1==p2&&(p2=(p1=buf)+fread(buf,1,100000,stdin),p1==p2)?EOF:*p1++; } __attribute__((optimize("-O3")))int rd() { int x=0; char ch=nc(); while(ch<‘0‘||ch>‘9‘) ch=nc(); while(ch>=‘0‘&&ch<=‘9‘) x=(x<<3)+(x<<1)+ch-‘0‘,ch=nc(); return x; } struct node{int l,r,p;}q[N];vector<pair<int ,int > >G[2][N]; int n,b,s,r,num[N],vis[N];ll dis[N],a[N],sum[N],f[N]; void Dijkstra(bool op) { memset(dis,0x3f,sizeof(dis));dis[b+1]=0;memset(vis,0,sizeof(vis)); priority_queue<pair<ll,int> >q;q.push(make_pair(0,b+1)); while(!q.empty()) { int x=q.top().second;q.pop();if(vis[x])continue;vis[x]=1; for(int i=0;i<G[op][x].size();i++) { int to1=G[op][x][i].first,val=G[op][x][i].second; if(dis[to1]>dis[x]+val)dis[to1]=dis[x]+val,q.push(make_pair(-dis[to1],to1)); } } for(int i=1;i<=b;i++)a[i]+=dis[i]; } int find(const node &t,int x) { int l=t.l,r=t.r+1; while(l<r) { int m=(l+r)>>1; if(calc(t.p,m)<=calc(x,m))l=m+1; else r=m; }return l; } int check(ll x) { int h=0,t=0;q[t++]=(node){0,n,0};f[0]=0;num[0]=0; for(int i=1;i<=n;i++) { while(h<t&&q[h].r<i)h++; f[i]=calc(q[h].p,i)+x;num[i]=num[q[h].p]+1; if(calc(i,n)<calc(q[t-1].p,n)) { while(h<t&&calc(q[t-1].p,q[t-1].l)>calc(i,q[t-1].l))t--; if(h==t)q[t++]=(node){i,n,i}; else { int x=find(q[t-1],i);q[t-1].r=x-1;q[t++]=(node){x,n,i}; } } } return num[n]; } int main() { n=rd();b=rd();s=rd();r=rd(); for(int i=1,x,y,z;i<=r;i++)x=rd(),y=rd(),z=rd(),G[0][x].push_back(make_pair(y,z)),G[1][y].push_back(make_pair(x,z)); Dijkstra(0);Dijkstra(1);n=b;sort(a+1,a+n+1);for(int i=1;i<=n;i++)sum[i]=sum[i-1]+a[i]; ll l=0,r=1ll<<60; while(l<r) { ll m=(l+r)>>1; if(check(m)>s)l=m+1; else r=m; }check(l); printf("%lld\n",f[n]-l*s); }
大概還有什麽林克卡特樹之類的,就不多說了...
帶權二分