[2018.6.23集訓]y-帶權二分
題目大意
幾乎同HNOI2018道路,但將題面中的"小 W 決定對每個城市翻修恰好一條通向它的道路,即從公路和鐵路中選擇一條並進行翻修"這句話刪去,即改為可以任意選擇$n-1$條邊翻修。
數據範圍不變。
題解
考慮樸素DP。
即,在可以通過原題的代碼的基礎上,加上一維表示已選擇的邊的數量,同時轉移上多出兩邊都選和都不選的方案。
復雜度$O(n^2d^2)$($d$為深度,$d \leq 40$),30分。
考慮優化,然而從dp的角度較難下手。
註意到有一維跟數量有關,同時打表發現,若令答案關於選擇邊的數量的函數,這個函數單增,且斜率(即差分後)也是單增的。
於是考慮帶權二分(出題人那邊叫凸優化?)。
這樣,可以通過控制$k$的大小來控制得到的最優解中選擇的邊的數量,$k$大則選的邊減少,$k$小則選的邊增多。
二分到最優解選擇的邊數恰好為$n-1$時即可停止。
復雜度$O(nd^2\log k)$,其中$k$為斜率的值域。
——然而以上部分博主考試都想到了
但是,有時會出現二分不到$n-1$的情況。
具體來說,比如需要$3$條邊,而最後二分的結果卻一直在$2$和$4$之間來回變化。
有兩種解決方案,一種是開long double,並在最後強制認為選到了$3$,即,對$4$或$2$得到的結果$f$,令$ans=f-3k$(本來應是$ans=f-4
——然後就會像博主一樣被卡常$0.1$秒變成60分,需要調斜率值域才能過。
第二種,在dp統計方案時,若某個轉移的價值和目前為止的最優值相同,但選取次數較最優值更少,那麽更新選取次數為新的更小的選取次數。
然後在統計答案時若出現這種情況,那認定$2$是正確的,選擇$ans=f_2-3*k$即可。
這樣就可以開long long了!
然後程序就快了整整3倍,無壓力通過......
第二種方法來自XZK dalao。
(即現場rk1 205 pts的dalao,rk2 才160,蒟蒻博主rk4 才80 pts,現場有接近80個35-45分的人......)
事實證明,對帶權二分的理解還不到位......
代碼:
#include<cstdio>
#include<cstring>
using namespace std;
#define print(...) fprintf(stderr,__VA_ARGS__)
typedef long long ll;
const int N=20009;
const int D=49;
inline int read()
{
int x=0,f=1;char ch=getchar();
while(ch<'0' || '9'<ch){if(ch=='-')f=-1;ch=getchar();}
while('0'<=ch && ch<='9')x=x*10+(ch^48),ch=getchar();
return x*f;
}
inline bool chkmin(ll &a,ll b){if(a>b)return a=b,1;return 0;}
int n,top;
int ch[N][2];
ll a[N],b[N],c[N],inc;
ll f[D*2][D][D],h[D*2][D][D],g[D][D],cg[D][D];
bool v[D*2][D][D],tv[D][D];
inline void dfs(int u,int dep)
{
if(u<0)
{
top++;
memset(v[top],0,sizeof(v[top]));
for(int i=0;i<=dep;i++)
for(int j=0;j+i<=dep;j++)
{
f[top][i][j]=c[-u]*(a[-u]+i)*(b[-u]+j);
v[top][i][j]=1;
h[top][i][j]=0;
}
return;
}
dfs(ch[u][0],dep+1);
dfs(ch[u][1],dep+1);
for(int i=0;i<=dep;i++)
for(int j=0;j+i<=dep;j++)
{
g[i][j]=1e18;tv[i][j]=0;
if(v[top-1][i][j] && v[top][i][j+1])
{
if(chkmin(g[i][j],f[top-1][i][j]+f[top][i][j+1]+inc))
tv[i][j]=1,cg[i][j]=h[top-1][i][j]+h[top][i][j+1]+1;
else if(g[i][j]==f[top-1][i][j]+f[top][i][j+1]+inc)
chkmin(cg[i][j],h[top-1][i][j]+h[top][i][j+1]+1);
}
if(v[top-1][i+1][j] && v[top][i][j])
{
if(chkmin(g[i][j],f[top-1][i+1][j]+f[top][i][j]+inc))
tv[i][j]=1,cg[i][j]=h[top-1][i+1][j]+h[top][i][j]+1;
else if(g[i][j]==f[top-1][i+1][j]+f[top][i][j]+inc)
chkmin(cg[i][j],h[top-1][i+1][j]+h[top][i][j]+1);
}
if(v[top-1][i][j] && v[top][i][j])
{
if(chkmin(g[i][j],f[top-1][i][j]+f[top][i][j]+inc+inc))
tv[i][j]=1,cg[i][j]=h[top-1][i][j]+h[top][i][j]+2;
else if(g[i][j]==f[top-1][i][j]+f[top][i][j]+inc+inc)
chkmin(cg[i][j],h[top-1][i][j]+h[top][i][j]+2);
}
if(v[top-1][i+1][j] && v[top][i][j+1])
{
if(chkmin(g[i][j],f[top-1][i+1][j]+f[top][i][j+1]))
tv[i][j]=1,cg[i][j]=h[top-1][i+1][j]+h[top][i][j+1];
else if(g[i][j]==f[top-1][i+1][j]+f[top][i][j+1])
chkmin(cg[i][j],h[top-1][i+1][j]+h[top][i][j+1]);
}
}
top--;
memset(v[top],0,sizeof(v[top]));
for(int i=0;i<=dep;i++)
for(int j=0;j+i<=dep;j++)
{
v[top][i][j]=tv[i][j];
f[top][i][j]=g[i][j];
h[top][i][j]=cg[i][j];
}
}
int main()
{
n=read();
for(int i=1;i<n;i++)
ch[i][0]=read(),ch[i][1]=read();
for(int i=1;i<=n;i++)
a[i]=read(),b[i]=read(),c[i]=read();
ll l=-1e14,r=1e14,ans=1e18;
while(l<=r)
{
inc=l+r>>1;
dfs(1,top=0);
if(h[1][0][0]<n-1)
{
ans=f[1][0][0]-(n-1)*inc;
r=inc-1;
}
else if(h[1][0][0]>n-1)
l=inc+1;
else
{
ans=f[1][0][0]-(n-1)*inc;
break;
}
}
printf("%lld\n",ans);
return 0;
}
[2018.6.23集訓]y-帶權二分