并不对劲的[noi2006]网络收费
题目略长,就从大视野上复制了。
听上去好像费用流,然而……
***************************表示略长的题目的分界线************************
1495: [NOI2006]网络收费
Description
网络已经成为当今世界不可或缺的一部分。每天都有数以亿计的人使用网络进行学习、科研、娱乐等活动。然而,
不可忽视的一点就是网络本身有着庞大的运行费用。所以,向使用网络的人进行适当的收费是必须的,也是合理的
。MY市NS中学就有着这样一个教育网络。网络中的用户一共有2N个,编号依次为1, 2, 3, …, 2N。这些用户之间
是用路由点和网线组成的。用户、路由点与网线共同构成一个满二叉树结构。树中的每一个叶子结点都是一个用户
,每一个非叶子结点(灰色)都是一个路由点,而每一条边都是一条网线(见下图,用户结点中的数字为其编号)
MY网络公司的网络收费方式比较奇特,称为“配对收费”。即对于每两个用户i, j (1≤i < j ≤2N ) 进行收费。
由于用户可以自行选择两种付费方式A、B中的一种,所以网络公司向学校收取的费用与每一位用户的付费方式有关
。该费用等于每两位不同用户配对产生费用之和。 为了描述方便,首先定义这棵网络树上的一些概念: 祖先:根
结点没有祖先,非根结点的祖先包括它的父亲以及它的父亲的祖先; 管辖叶结点:叶结点本身不管辖任何叶结点
,非叶结点管辖它的左儿子所管辖的叶结点与它的右儿子所管辖的叶结点; 距离:在树上连接两个点之间的用边
最少的路径所含的边数。 对于任两个用户i, j (1≤i)
由于最终所付费用与付费方式有关,所以NS中学的用户希望能够自行改变自己的付费方式以减少总付费。然而,由
于网络公司已经将每个用户注册时所选择的付费方式记录在案,所以对于用户i,如果他/她想改变付费方式(由A
改为B或由B改为A),就必须支付Ci元给网络公司以修改档案(修改付费方式记录)。 现在的问题是,给定每个用
户注册时所选择的付费方式以及Ci,试求这些用户应该如何选择自己的付费方式以使得NS中学支付给网络公司的总
费用最少(更改付费方式费用+配对收费的费用)。
Input
输入文件中第一行有一个正整数N。 第二行有2N个整数,依次表示1号,2号,…,2N号用户注册时的付费方式,每
一个数字若为0,则表示对应用户的初始付费方式为A,否则该数字为1,表示付费方式为B。 第三行有2N个整数,
表示每一个用户修改付费方式需要支付的费用,依次为C1, C2, …,CM 。( M=2N ) 以下2N-1行描述给定的两两用
户之间的流量表F,总第(i + 3)行第j列的整数为Fi, j+i 。(1≤i<2N,1≤j≤2N ? i) 所有变量的含义可以参
见题目描述。N≤10,0≤Fi, j≤500,0≤Ci≤500 000
Output
你的程序只需要向输出文件输出一个整数,表示NS中学支付给网络公司的最小总费用。(单位:元)
Sample Input
2
1 0 1 0
2 2 10 9
10 1 2
2 1
3
1 0 1 0
2 2 10 9
10 1 2
2 1
3
Sample Output
8
***************************表示略长的题目结束了的分界线************************
这么长的题目告诉我们一定要学好政治。
收费方式看上去很复杂,实际上没有想象中那么复杂但是也很复杂,可以看成A少就收所有的A,B少就收所有的B。这样用前缀和就能解决收费的问题了。
根据N<10大概猜到应该是状压dp,进而猜出dp(x,y,z)表示到第x个点,有y个A(或y个B),且祖先的取舍方案为z的收费情况。
那么问题就来了:用dp[x][y][z]表示的话,空间总共要占(2^10)^3!这样空间肯定会出问题。能不能将其中两维合成一维呢?
经过一番不对劲的思考,发现每往下走一层,z就会增加一位,而y的最大值会减少一半。把这两维放在同一维似乎不错。
#include<algorithm> #include<iostream> #include<cstring> #include<cstdio> #include<cmath> #include<iomanip> #include<cstdlib> #define maxm 5050 #define maxk 2050 using namespace std; int dp[maxk][maxm],n,m,c[maxk],f[maxk][maxk]; int li[maxk],ri[maxk], ab[maxk]; int read() { int x=0,f=1; char ch=getchar(); while(isdigit(ch)==0 && ch!='-')ch=getchar(); if(ch=='-')f=-1; while(isdigit(ch))x=x*10+ch-'0',ch=getchar(); return x*f; } int solve(int x,int y,int z,int l,int r,int lim) { li[x]=l,ri[x]=r; int xi=x,xj=(z<<(lim+1))|y; if(dp[xi][xj]!=-1) { return dp[xi][xj]; } if(x>=m) { int tab,now=x-m+1,sum=c[now]*(ab[now]!=y),lu,ru; for(int lstu=x,u=x>>1;u;lstu=u,u>>=1) { tab=z&1; z>>=1; lu=(lstu!=(u<<1))?li[u]:((li[u]+ri[u])>>1)+1; ru=(lstu!=(u<<1))?((li[u]+ri[u])>>1):ri[u]; sum+=(f[now][ru]-f[now][lu-1])*(tab!=y); } dp[xi][xj]=sum; return sum; } else { int tmp=(z<<1)|(y>(1<<lim)-y),res=0x7fffffff,mi=(l+r)>>1,siz=1<<(lim-1); if(siz>y)siz=y; for(int p=y-siz;p<=siz;p++) { int t=solve(x<<1,p,tmp,l,mi,lim-1)+solve((x<<1)+1,y-p,tmp,mi+1,r,lim-1); res=min(res,t); } dp[xi][xj]=res; return res; } } int main() { //cout<<"Boy_next_door is playing game."<<endl; n=read(); m=1<<n; for(int i=1;i<=m;i++) ab[i]=read(); for(int i=1;i<=m;i++) c[i]=read(); for(int i=1;i<=m;i++) for(int j=i+1;j<=m;j++) { f[i][j]=read(); f[j][i]=f[i][j]; } memset(dp,-1,sizeof(dp)); for(int i=1;i<=m;i++) for(int j=1;j<=m;j++) f[i][j]=f[i][j-1]+f[i][j]; int ans=0x7fffffff; for(int i=0;i<=m;i++) { int tmp1=solve(1,i,0,1,m,n); ans=min(ans,tmp1); } cout<<ans; return 0; }