BZOJ_1495_[NOI2006]网络收费_树形DP

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

Sample Output

8


直接模拟退火有80分。

代码:

  1. // luogu-judger-enable-o2
  2. #include <cstdio>
  3. #include <cstring>
  4. #include <algorithm>
  5. #include <cmath>
  6. #include <cstdlib>
  7. using namespace std;
  8. typedef double f2;
  9. #define N 1050
  10. int f[N][N],C[N],n,m,cnt,ls[N<<2],rs[N<<2],a[N],fa[N<<2],L[N][N],R[N][N];
  11. int c1[N],c2[N],b[N];
  12. struct A {
  13. int l,r;
  14. }t[N<<2];
  15. void build(int l,int r,int &p) {
  16. if(l==r) {p=l; return ;}
  17. else p=++cnt;
  18. t[p]=(A){l,r};
  19. int mid=(l+r)>>1;
  20. build(l,mid,ls[p]); build(mid+1,r,rs[p]);
  21. fa[ls[p]]=p; fa[rs[p]]=p;
  22. }
  23. A lca(int x,int y) {
  24. while(x!=y) {
  25. x=fa[x]; y=fa[y];
  26. }
  27. return t[x];
  28. }
  29. struct Q {
  30. int b[N];
  31. }ans;
  32. int mn=1<<30;
  33. int get_cost(const Q tmp) {
  34. int i,re=0,j;
  35. for(i=1;i<=m;i++) b[i]=tmp.b[i];
  36. for(i=1;i<=m;i++) {
  37. if(a[i]!=b[i]) re+=C[i];
  38. if(!b[i]) c1[i]=c1[i-1]+1,c2[i]=c2[i-1];
  39. else c1[i]=c1[i-1],c2[i]=c2[i-1]+1;
  40. }
  41. for(i=1;i<=m;i++) {
  42. for(j=i+1;j<=m;j++) {
  43. int l=L[i][j],r=R[i][j],na=c1[r]-c1[l-1],nb=c2[r]-c2[l-1];
  44. if(!b[i]&&!b[j]) {
  45. if(na<nb) re+=2*f[i][j];
  46. }else if(!b[i]&&b[j]) {
  47. re+=f[i][j];
  48. }else if(b[i]&&!b[j]) {
  49. re+=f[i][j];
  50. }else {
  51. if(na>=nb) re+=2*f[i][j];
  52. }
  53. }
  54. }
  55. if(mn>re) {
  56. mn=re; ans=tmp;
  57. }
  58. return re;
  59. }
  60. f2 Rand() {
  61. return 1.0*rand()/RAND_MAX;
  62. }
  63. void orz(f2 BG,f2 ED,f2 d) {
  64. f2 B=BG;
  65. int i,tmn;
  66. Q nowp;
  67. for(i=1;i<=m;i++) nowp.b[i]=a[i];
  68. tmn=get_cost(nowp);
  69. for(i=1;i<=m;i++) b[i]=a[i];
  70. for(;B>ED;B*=d) {
  71. Q tmp=nowp;
  72. for(i=1;i<=B;i++) {
  73. int k=rand()%m+1;
  74. tmp.b[k]^=1;
  75. }
  76. int tans=get_cost(tmp);
  77. if(tans<tmn||Rand()<exp(1.0*(tmn-tans)/B)) {
  78. tmn=tans; nowp=tmp;
  79. }
  80. }
  81. for(i=1;i<=1000;i++) {
  82. Q tmp=ans;
  83. int k=rand()%m+1;
  84. tmp.b[k]^=1;
  85. get_cost(tmp);
  86. }
  87. }
  88. int main() {
  89. // freopen("network.in","r",stdin);
  90. // freopen("network.out","w",stdout);
  91. srand(19260817); rand();
  92. scanf("%d",&n);
  93. m=1<<n; cnt=1<<n;
  94. int i,j;
  95. for(i=1;i<=m;i++) scanf("%d",&a[i]);
  96. for(i=1;i<=m;i++) scanf("%d",&C[i]);
  97. for(i=1;i<=m;i++) for(j=i+1;j<=m;j++) scanf("%d",&f[i][j]);
  98. int root=n;
  99. build(1,m,root);
  100. for(i=1;i<=m;i++) {
  101. for(j=i+1;j<=m;j++) {
  102. A tmp=lca(i,j); L[i][j]=tmp.l; R[i][j]=tmp.r;
  103. }
  104. }
  105. orz(m,1.5,0.99);
  106. printf("%d\n",mn);
  107. }
  108. /*
  109. 2
  110. 1 0 1 0
  111. 2 2 10 9
  112. 10 1 2
  113. 2 1
  114. 3
  115. */

观察计算贡献的方式,相当于选哪边的少就计算哪边的贡献,此时i的贡献不止有小于i的这部分,还有大于i的这部分。

这样,只需要知道每个点对应子树内选哪边的少就可以确定贡献的计算方法。

设s[i][j]表示第i个点对应的深度为j的祖先上有多少贡献,这步在求lca的时候处理。

然后dfs整棵树。对于每个节点,枚举选a多还是选b多,在叶子节点统计答案。

时间复杂度:O(2^n *n)

代码:

  1. #include <cstdio>
  2. #include <cstring>
  3. #include <algorithm>
  4. using namespace std;
  5. #define N 2050
  6. #define ls p<<1
  7. #define rs p<<1|1
  8. #define _min(x,y) ((x)<(y)?(x):(y))
  9. __attribute__((optimize("-O3")))inline char nc() {
  10. static char buf[100000],*p1,*p2;
  11. return p1==p2&&(p2=(p1=buf)+fread(buf,1,100000,stdin),p1==p2)?EOF:*p1++;
  12. }
  13. __attribute__((optimize("-O3")))int rd() {
  14. int x=0; char s=nc();
  15. while(s<'0'||s>'9') s=nc();
  16. while(s>='0'&&s<='9') x=(x<<3)+(x<<1)+s-'0',s=nc();
  17. return x;
  18. }
  19. int f[N][1050],n,C[N],s[N][15],m,sta[N],a[N],g[4][N];
  20. struct A {
  21. int l,r,dep;
  22. }t[N];
  23. __attribute__((optimize("-O3")))void build(int l,int r,int p) {
  24. t[p].l=l; t[p].r=r;
  25. if(l==r) return ;
  26. int mid=(l+r)>>1;
  27. t[ls].dep=t[rs].dep=t[p].dep+1;
  28. build(l,mid,ls); build(mid+1,r,rs); g[0][ls]=g[0][rs]=p;
  29. }
  30. __attribute__((optimize("-O3")))int lca(int x,int y) {
  31. int i;
  32. if(g[3][x]!=g[3][y]) x=g[3][x],y=g[3][y];
  33. if(g[2][x]!=g[2][y]) x=g[2][x],y=g[2][y];
  34. if(g[1][x]!=g[1][y]) x=g[1][x],y=g[1][y];
  35. if(g[0][x]!=g[0][y]) x=g[0][x],y=g[0][y];
  36. return g[0][x];
  37. }
  38. __attribute__((optimize("-O3")))void dfs(int p) {
  39. int d=t[p].dep,i,j;
  40. if(d==n) {
  41. if(a[p-m+1]) f[p][1]=0,f[p][0]=C[p-m+1];
  42. else f[p][0]=0,f[p][1]=C[p-m+1];
  43. for(i=0;i<n;i++) f[p][!sta[i]]+=s[p-m+1][i];
  44. return ;
  45. }
  46. int y=1<<(n-d-1);
  47. for(i=0;i<=y<<1;i++) f[p][i]=1<<30;
  48. sta[d]=0;
  49. dfs(ls); dfs(rs);
  50. for(i=0;i<=y;i++) for(j=0;i+j<=y;j++) f[p][i+j]=_min(f[p][i+j],f[ls][i]+f[rs][j]);
  51. sta[d]=1;
  52. dfs(ls); dfs(rs);
  53. for(i=1;i<=y;i++) for(j=y-i+1;j<=y;j++) f[p][i+j]=_min(f[p][i+j],f[ls][i]+f[rs][j]);
  54. }
  55. __attribute__((optimize("-O3")))int main() {
  56. n=rd();
  57. m=1<<n;
  58. build(1,m,1);
  59. int i,j,x;
  60. int k=m<<1;
  61. for(i=1;i<=3;i++) for(j=1;j<=k;j++) g[i][j]=g[i-1][g[i-1][j]];
  62. for(i=1;i<=m;i++) a[i]=rd();
  63. for(i=1;i<=m;i++) C[i]=rd();
  64. for(i=1;i<=m;i++) {
  65. for(j=i+1;j<=m;j++) {
  66. int l=lca(i+m-1,j+m-1);
  67. x=rd();
  68. s[i][t[l].dep]+=x;
  69. s[j][t[l].dep]+=x;
  70. }
  71. }
  72. // puts("FUCK");
  73. dfs(1);
  74. int ans=1<<30;
  75. for(i=0;i<=m;i++) ans=_min(ans,f[1][i]);
  76. printf("%d\n",ans);
  77. }

BZOJ_1495_[NOI2006]网络收费_树形DP的更多相关文章

  1. BZOJ1495 [NOI2006]网络收费 【树形dp + 状压dp】

    题目链接 BZOJ1495 题解 观察表格,实际上就是分\(A\)多和\(B\)两种情况,分别对应每个点选\(A\)权值或者\(B\)权值,所以成对的权值可以分到每个点上 所以每个非叶节点实际对应一个 ...

  2. 【bzoj1495】[NOI2006]网络收费 暴力+树形背包dp

    题目描述 给出一个有 $2^n$ 个叶子节点的完全二叉树.每个叶子节点可以选择黑白两种颜色. 对于每个非叶子节点左子树中的叶子节点 $i$ 和右子树中的叶子节点 $j$ :如果 $i$ 和 $j$ 的 ...

  3. BZOJ 1495 [NOI2006]网络收费(暴力DP)

    题意 给定一棵满二叉树,每个叶节点有一个状态0/10/10/1,对于每两个叶节点i,ji,ji,j,如果这两个叶节点状态相同但他们的LCALCALCA所管辖的子树中的与他们状态相同的叶节点个数较少(少 ...

  4. 【BZOJ1495】[NOI2006]网络收费 暴力+DP

    [BZOJ1495][NOI2006]网络收费 Description 网络已经成为当今世界不可或缺的一部分.每天都有数以亿计的人使用网络进行学习.科研.娱乐等活动.然而,不可忽视的一点就是网络本身有 ...

  5. 洛谷 P4297 [NOI2006]网络收费

    P4297 [NOI2006]网络收费 题目背景 noi2006 day1t1 题目描述 网络已经成为当今世界不可或缺的一部分.每天都有数以亿计的人使用网络进行学习.科研.娱乐等活动.然而,不可忽视的 ...

  6. 并不对劲的[noi2006]网络收费

    题目略长,就从大视野上复制了. 听上去好像费用流,然而…… ***************************表示略长的题目的分界线************************ 1495: [ ...

  7. 【简】题解 P4297 [NOI2006]网络收费

    传送门:P4297 [NOI2006]网络收费 题目大意: 给定一棵满二叉树,每个叶节点有一个状态(0,1),任选两个叶节点,如果这两个叶节点状态相同但他们的LCA所管辖的子树中的与他们状态相同的叶节 ...

  8. BZOJ_1864_[Zjoi2006]三色二叉树_树形DP

    BZOJ_1864_[Zjoi2006]三色二叉树_树形DP 题意: 分析:递归建树,然后DP,从子节点转移. 注意到红色和蓝色没有区别,因为我们可以将红蓝互换而方案是相同的.这样的话我们只需要知道当 ...

  9. BZOJ_3573_[Hnoi2014]米特运输_树形DP+hash

    BZOJ_3573_[Hnoi2014]米特运输_树形DP+hash 题意: 给你一棵树每个点有一个权值,要求修改最少的权值,使得每个节点的权值等于其儿子的权值和且儿子的权值都相等. 分析: 首先我们 ...

随机推荐

  1. Spring HTTP Service

    基于Spring MVC, 使用Http Service Invoke远程调用方法 (参考: http://blog.csdn.net/hanqunfeng/article/details/43031 ...

  2. PC和手机怎么实现绝对居中?

    示例1(懒人之家): http://www.51xuediannao.com/js/nav/360buy_nav.html 示例2(google官方):

  3. 【Python】输出程序运行的百分比

    对于一些大型的Python程序.我们须要在命令行输出其百分比,显得更加友好,以免被人误会程序陷入死循环.假死的窗口. 关键是利用到不换行的输出符\r,\r的输出.将直接覆盖掉此行的内容. 比方例如以下 ...

  4. USACO 1.2 Milking Cows (枚举)

    标记数组(哈希) 1e6的范围,开一个char数组全然能够,有人为1,无人为0,注意边界就可以.最后线性扫描就可以. 时间复杂度,应该是O(n),n为最后结束的时间. 缺点就是--比較慢 /* ID: ...

  5. java性能监控工具jcmd-windows

    jcmd Sends diagnostic command requests to a running Java Virtual Machine (JVM). Synopsis jcmd [-l|-h ...

  6. CTP报单状态 OrderStatus全部状态

  7. javascript 判断字符串是否包换字符串

    用"ghiahgiahgia".indexOf("hg"); 返回值>=0为包含,否则就是-1(不包含)

  8. Java序列化算法

    Serialization(序列化)是一种将对象以一连串的字节描述的过程:反序列化deserialization是一种将这些字节重建成一个对象的过程.java序列化API提供一种处理对象序列化的标准机 ...

  9. adb命令具体解释(二)——手机缺失sqlite3时操作数据库的多种解决方式

    在android应用开发无处不在SQLite数据库的身影.那么在开发中怎么使用adb命令操作数据库的功能呢? 以下我们将完整的介绍与数据库操作相关的命令集及当手机缺少sqlite3的时候的多种解决方式 ...

  10. 排序&匿名函数

    nums=[11,34234,23,344,123,1,23,124,523,4,12342341,423,43545] nums.sort() print(nums) #这个就是排序,从小到到 匿名 ...