2018牛客网暑期ACM多校训练营(第二场):discount(基环树DP)
题意:有N个不同的商品,每个商品原价是Pi元,如果选择打折,可以减少Di元。 现在加一种规则,每个商品有一个友好商品Fai,如果i用原价买,则可以免费买Fai。
现在问买到所有物品的最小价格。
思路:显然是一个内向树基环。 先把悬在环上的树都求出DP[][],然后再在链上同理跑一遍DP。
我们先看树:dp[i][0]表示i节点不是原价买,dp[i][1]是原价买。 dp[i][1]对儿子没有任何要求,而dp[i][0]=min(子树随便买+自己打折买, 子树至少一个原价买+自己免费买);
#include<bits/stdc++.h> #define ll long long #define rep(i,a,b) for(int i=a;i<=b;i++) using namespace std; const int maxn=200010; const ll inf=1LL<<60; int fa[maxn],Laxt[maxn],Next[maxn],To[maxn],vis[maxn]; int p[maxn],d[maxn],cnt=1,N,r[maxn],tot; ll dp[maxn][2],sum[maxn],C[maxn][2],ans;//0是打折或者免费,1是原价 void add(int u,int v) { Next[++cnt]=Laxt[u]; Laxt[u]=cnt; To[cnt]=v; } bool dfs(int u) { while(!vis[u]) vis[u]=1,u=fa[u]; while(vis[u]==1) vis[u]=2,r[++tot]=u,u=fa[u]; } void treedp(int u,int f) { if(!vis[u]) vis[u]=1; //所以不要忘了标记,免得重复。 ll sum1=0,Mn=inf; for(int i=Laxt[u];i;i=Next[i]){ if(To[i]==f||To[i]==u||vis[To[i]]==2) continue; int v=To[i]; treedp(v,u); sum1+=dp[v][0]; Mn=min(Mn,dp[v][1]-dp[v][0]); } sum[u]=sum1; dp[u][1]=sum1+p[u]; dp[u][0]=min(sum1+Mn,sum1+p[u]-d[u]); } ll solve(int u) { tot=0; dfs(u); //找环 rep(i,1,tot) treedp(r[i],0); ll res=inf; rep(x,0,1){ //1表示尾巴原价买 if(x==0) C[1][0]=dp[r[1]][0]; else C[1][0]=sum[r[1]]; C[1][1]=dp[r[1]][1]; rep(i,2,tot) { C[i][0]=min(C[i-1][0]+dp[r[i]][0],C[i-1][1]+sum[r[i]]); C[i][1]=dp[r[i]][1]+C[i-1][0]; } res=min(res,C[tot][x]); } return res; } int main() { scanf("%d",&N); rep(i,1,N) scanf("%d",&p[i]); rep(i,1,N) scanf("%d",&d[i]); rep(i,1,N){ scanf("%d",&fa[i]); add(fa[i],i); } rep(i,1,N) if(!vis[i]) ans+=solve(i); printf("%lld\n",ans); return 0; }
It is your time to fight!