2017 CDQZ 聯訓 Day9 T2 可憐與超市

今天狀態好差啊~ 寫一發題解壓壓驚,非常感謝左側 ← HSZX TS_Hugh 大佬教我樹形DP..

題面

題目描述

樣例1

樣例2

解題思路

題中的依賴關係顯然是一種樹的結構,因爲n比較小而b巨大無比,DP時可以把n設爲狀態的一維。令g[x][i] 表示在以x爲根的子樹中,不使用優惠券,購買i個物品要花費的最小代價。類似地,令f[x][i] 表示在以x爲根的子樹中,至少使用一張優惠券,購買i個物品的最小代價。

遞歸處理每棵子樹,然後依次把當前子樹的信息與根節點當前記錄的信息合併。令tmp[i]表示在根節點x及x的前k-1棵子樹中購買i個物品的最小代價,g[u][i] 表示在以u爲根的子樹中購買i個物品的最小代價(u爲x的第k棵子樹),用這兩個數組更新數組g[x],f[x]也是同理。

因爲優惠券的使用有依賴關係,所以f[x]的合併稍微特殊一點,先不考慮根節點的貢獻,在所有子樹合併完成之後直接把根節點的那個物品加入到f[x]數組的所有選取方案中。這樣就實現了,讓f[x]中的所有方案都必須購買根節點x所表示的物品。

這個方法好像看起來是O(n3) 的,但實際上卻是O(n2) 的。當且僅當處理lca(u,v)時,u和v節點的信息才被合併。也就是說,每個點對的信息只會被合併一次,樹上有O(n2) 個點對,所以時間愛你複雜度爲O(n2)

代碼

此代碼常數巨大無比,因爲我最開始爆了int,發現要開long long,懶得改了,直接寫了#define ing long long。。

#include<cstdio>
#include<cstdlib>
#include<algorithm>
#include<cctype>
#include<vector>
#include<cstring>
using namespace std;
const int maxn=5000+10;
typedef long long LLint;
#define int long long
LLint f[maxn][maxn],g[maxn][maxn],n,b;
vector<int>son[maxn];int c[maxn],d[maxn],siz[maxn];
inline int geti(){
    int ans=0,flag=0;char c=getchar();
    while(!isdigit(c)){flag|=c=='-';c=getchar();}
    while( isdigit(c)){ans=ans*10+c-'0';c=getchar();}
    return flag?-ans:ans;
}
void inline puti(int x){
    if(x<0)x=-x,putchar('-');
    if(x>9)puti(x/10); putchar(x%10+'0');
}
LLint tmp[maxn];
void sol(int x){
    g[x][0]=0;g[x][1]=c[x];siz[x]=1;//not use
    f[x][0]=0;//use
    for(int i=0;i<son[x].size();i++){//考慮所有子樹
        int u=son[x][i];sol(u);
        memcpy(tmp,g[x],(siz[x]+1)*sizeof(LLint));//複製數據,防止重複影響
        for(int v=0;v<=siz[x];v++)
            for(int j=0;j<=siz[u];j++)
                g[x][v+j]=min(g[x][v+j],tmp[v]+g[u][j]);
        memcpy(tmp,f[x],(siz[x]+1)*sizeof(LLint));//複製數據,同理
        for(int v=0;v<siz[x];v++)
            for(int j=0;j<=siz[u];j++)
                f[x][v+j]=min(f[x][v+j],
                tmp[v]+min(f[u][j],g[u][j]));
        siz[x]+=siz[u];//表示當前已經和根節點合併的連通塊大小
    }
    for(int i=siz[x];i>=1;i--){//把x加到所有方案中
        f[x][i]=f[x][i-1]+c[x]-d[x];
    }
}
signed main(){
    freopen("supermarket.in","r",stdin);
    freopen("supermarket.out","w",stdout);
    memset(g,0x7f,sizeof(g));
    memset(f,0x7f,sizeof(f));
    n=geti();b=geti();
    c[1]=geti();d[1]=geti();
    for(int i=2;i<=n;i++){
        c[i]=geti();d[i]=geti();
        int x=geti();
        son[x].push_back(i);
    }
    sol(1);//dfs
    for(int i=n;i>=0;i--){//O(n)掃一遍即可
        int cost=min(f[1][i],g[1][i]);
        if(cost<=b){
            printf("%d\n",i);
            break;
        }
    }
    return 0;
}

[2017.12.28] 來自TS_Hugh 大佬的卡常神技

char xB[(1<<15)+10],*xS=xB,*xT=xB;
#define gtc (xS==xT&&(xT=(xS=xB)+fread(xB,1,1<<15,stdin),xS==xT)?0:*xS++)

本機實測,讀入107 個整數,比普通的getchar() 讀入優化快了0.2s !!!

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章