1. 程式人生 > >[2018.6.23集訓]y-帶權二分

[2018.6.23集訓]y-帶權二分

read () stderr print define top 有時 方法 spa

題目大意

幾乎同HNOI2018道路,但將題面中的"小 W 決定對每個城市翻修恰好一條通向它的道路,即從公路和鐵路中選擇一條並進行翻修"這句話刪去,即改為可以任意選擇$n-1$條邊翻修。

數據範圍不變。

題解

考慮樸素DP。
即,在可以通過原題的代碼的基礎上,加上一維表示已選擇的邊的數量,同時轉移上多出兩邊都選和都不選的方案。

復雜度$O(n^2d^2)$($d$為深度,$d \leq 40$),30分。
考慮優化,然而從dp的角度較難下手。

註意到有一維跟數量有關,同時打表發現,若令答案關於選擇邊的數量的函數,這個函數單增,且斜率(即差分後)也是單增的。

於是考慮帶權二分(出題人那邊叫凸優化?)。

刪除最後一維,二分斜率$k$,每次轉移時若新加入一條邊,則強制加上$k$的貢獻。
這樣,可以通過控制$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

k$)。

——然後就會像博主一樣被卡常$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-帶權二分