[NOI2006]网络收费

在树上做的题目,而且数据范围不是很大,很容易想到树形DP的说

但是我纠结了很久,因为不知道怎么样表示状态

按理说应该要把叶子节点的状态全都表示出来的(其实这样就是暴搜了……),但是显然不行……

然后其实可以发现这题的系数有个非常巧妙的性质

如果子树中A节点数>B节点数,那么赋给子树的根A属性,否则赋给B属性

然后对于两个点来说,两个点的费用计算可以转化成

->找到两个点的LCA->如果LCA是A属性,那么如果点是B的话要算费用,反之亦然

那么显然这样做和之前是等价的,如果两个点是A,在LCA是A属性的情况下不算费用,1A1B算一倍费用,否则算两倍费用(LCA是B属性就反过来)

记m=2^n

然后,我们可以在O(m^2logm)的时间内把用户之间的费用全部转化到路由器上(具体实现在代码中)

这样做有什么好处呢,在DP的时候,我们可以单独考虑每个节点而不是点对

记f[i][j][k]为点i(不是用户i)为根的子树中有j个A属性点,从j往上一路到root这条链上,j的祖先的信息(以祖先为根的子树是A点多还是B点多),用二进制压位表示

那么枚举分到左子树的j节点,更新k,递归进去做就可以了

边界为叶子节点,这个时候通过k,可以得到叶子节点一路往上到root这条链上祖先的信息,把答案累加起来就可以了

注意如果DP到边界时叶子节点属性和初始不同需要加上更改的费用

到这一步题目还没完,因为以上做法为O(m^3) //or O(m^4)??

不管是时间还是空间都无法承受

实际上后面两维存在浪费,因为对于一个点i来说

它所在的层数决定了以它为根的子树最多只能有多少个A属性点,也就是限制了j的大小

同时他所在的层数也限定了他的祖先的多少,也就是k的大小

实际上后面两位加起来也才2m,因此可以压成一维(不是我们平常说的压)

那么时间复杂度变成O(m^2logm),空间复杂度变成O(m^2)


讲起来不好讲,写起来也不好写……


感谢Byvoid大牛……http://www.byvoid.com/blog/noi-2006-network/


//Lib
#include<cstdio>
#include<cstring>
#include<cstdlib>
#include<cmath>
#include<ctime>

#include<iostream>
#include<algorithm>
#include<vector>
#include<string>
#include<queue>
using namespace std;
//Macro
#define rep(i,a,b) for(int i=a,tt=b;i<=tt;++i)
#define drep(i,a,b) for(int i=a,tt=b;i>=tt;--i)
#define erep(i,e,x) for(int i=x;i;i=e[i].next)
#define irep(i,x) for(__typedef(x.begin()) i=x.begin();i!=x.end();i++)
#define read() (strtol(ipos,&ipos,10))
#define sqr(x) ((x)*(x))
#define pb push_back
#define PS system("pause");
typedef long long ll;
typedef pair<int,int> pii;
const int oo=~0U>>1;
const double inf=1e100;
const double eps=1e-6;
string name="network",in=".in",out=".out";
//Var
int n,m,ans=oo;
int c[1100],flow[1100][1100],cost[1100][1100],fa[1100][1100];
bool type[1100];
struct T
{
	int v[2100],base;
	int& operator ()(int i,int j){return v[i*base+j];}
}f[2100];
void Init()
{
	scanf("%d",&n);m=1<<n;int a;
	rep(i,1,m)scanf("%d",&a),type[i]=a==0;
	rep(i,1,m)scanf("%d",c+i);
	rep(i,1,m)
		rep(j,i+1,m)
			scanf("%d",&a),flow[i][j]=flow[j][i]=a;
}
int Calc(int x,int j,int k)
{
	int ca=0,cb=0;
	rep(i,1,n)
	{
		if(k&1) ca+=cost[i][x];
		else	cb+=cost[i][x];
		k>>=1;
	}
	if(j)return ca+(type[x]?0:c[x]);
	else return cb+(type[x]?c[x]:0);
}
int TDP(int i,int j,int k,int h)
{
	int ret=f[i](j,k);
	if(ret)return ret;
	if(h)
	{
		ret=oo;
		int tmp,now,ls,tk;
		now=j<((1<<h)-j);
		tk=(k<<1)+now;
		ls=1<<h-1;
		for(int l=j-ls<0?0:j-ls;l<=j&&l<=ls;l++)
		{
			tmp=TDP(i<<1,l,tk,h-1)+TDP(i<<1^1,j-l,tk,h-1);
			ret=min(ret,tmp);	
		}
	}
	else ret=Calc(i-m+1,j,k);
	f[i](j,k)=ret;
	return ret;
}
void Work()
{
	rep(i,1,m)
	{
		int k=i+m-1;
		rep(j,1,n){k>>=1;fa[j][i]=k;}
	}
	rep(i,1,n+1)
		rep(j,1<<i-1,(1<<i)-1)f[j].base=1<<i-1;
	rep(i,1,m)rep(j,i+1,m)rep(k,1,n)
		if(fa[k][i]==fa[k][j])
		{
			cost[k][i]+=flow[i][j],
			cost[k][j]+=flow[i][j];	
			break;
		}
	rep(i,0,m)
		ans=min(ans,TDP(1,i,0,n));	
	cout<<ans<<endl;
}
int main()
{
//	freopen((name+in).c_str(),"r",stdin);
//	freopen((name+out).c_str(),"w",stdout);
	Init();
	Work();
	return 0;
}



你可能感兴趣的:(网络,struct,BI,System,NetWork,路由器)