[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; }


  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值