[BZOJ1495][NOI2006]网络收费
sol
观察系数\(k\)可以发现一个很有趣的规律:系数恰好可以视作对相对数量较少的用户个数。(若两者数量相等则视作\(B\)少)
比如说,表格的第二行,当\(i\)的付费方式为\(A\),\(j\)的付费方式为\(B\),且\(n_A<n_B\)时,产生的代价是一倍的\(F_{i,j}\)。可以当作是对相对数量较少的\(i\)用户收了\(F_{i,j}\),对\(j\)用户没有收费。
那么一个用户产生的费用就可以由他自己选取的类型以及他的所有祖先节点的状态(\(A\)多还是\(B\)多)唯一确定。
这样子的话就可以做一个\(dp\)了。设\(dp[x][y][z]\)表示\(dp\)到树上的第\(x\)个节点,这个节点中包含\(y\)个\(B\),然后祖先节点的状态是\(z\)的最小代价。底层状态就是\(x\)为叶子,这个时候可以\(O(\log n)\)算一下这一个用户的代价。否则可以按照背包转移。
发现这个\(dp\)是三维的。但是考虑一下,随着\(x\)的深度增加,\(z\)会多一位而\(y\)的上界减小一半,也就是说对于一个\(x\),\(y,z\)的状态数量的乘积是一定的,而且这个数量也就是\(O(2^n)\)的。
那么就可以把\(y,z\)两维状态压进一维,然后记忆化一下就可以了。复杂度是\(O(2^{2n})\)
code
#include<cstdio>
#include<algorithm>
#include<cstring>
using namespace std;
int gi()
{
int x=0,w=1;char ch=getchar();
while ((ch<'0'||ch>'9')&&ch!='-') ch=getchar();
if (ch=='-') w=0,ch=getchar();
while (ch>='0'&&ch<='9') x=(x<<3)+(x<<1)+ch-'0',ch=getchar();
return w?x:-x;
}
const int N = 2500;
const int inf = 2e9;
int n,all,b[N],c[N],f[N][N],L[N],R[N],dp[N][N],ans=inf;
void build(int x,int l,int r)
{
L[x]=l;R[x]=r;if (l==r) return;
int mid=l+r>>1;
build(x<<1,l,mid);build(x<<1|1,mid+1,r);
}
int dfs(int x,int y,int z,int dep)
{
if (dp[x][(y<<dep)|z]!=-1) return dp[x][(y<<dep)|z];
if (dep==n)
{
int sum=c[x-all+1]*(y!=b[x-all+1]);
for (int u=x,d=n-1;d>=0;--d,u>>=1)
sum+=(f[x-all+1][R[u^1]]-f[x-all+1][L[u^1]-1])*(y!=((z>>d)&1));
return dp[x][(y<<dep)|z]=sum;
}
else
{
int res=inf,zt=y*2>R[x]-L[x]+1;
for (int i=max(0,y-(R[x<<1|1]-L[x<<1|1]+1));i<=min(y,R[x<<1]-L[x<<1]+1);++i)
res=min(res,dfs(x<<1,i,z|(zt<<dep),dep+1)+dfs(x<<1|1,y-i,z|(zt<<dep),dep+1));
return dp[x][(y<<dep)|z]=res;
}
}
int main()
{
n=gi();all=1<<n;
for (int i=1;i<=all;++i) b[i]=gi();
for (int i=1;i<=all;++i) c[i]=gi();
for (int i=1;i<=all;++i)
for (int j=i+1;j<=all;++j)
f[i][j]=f[j][i]=gi();
for (int i=1;i<=all;++i)
for (int j=1;j<=all;++j)
f[i][j]+=f[i][j-1];
build(1,1,all);memset(dp,-1,sizeof(dp));
for (int i=0;i<=all;++i)
ans=min(ans,dfs(1,i,0,0));
printf("%d\n",ans);
return 0;
}