洛谷 P4297 [NOI2006]网络收费
题目背景
noi2006 day1t1
题目描述
网络已经成为当今世界不可或缺的一部分。每天都有数以亿计的人使用网络进行学习、科研、娱乐等活动。然而,不可忽视的一点就是网络本身有着庞大的运行费用。所以,向使用网络的人进行适当的收费是必须的,也是合理的。
MY 市NS 中学就有着这样一个教育网络。网络中的用户一共有 2^N2N 个,编号依次为1, 2, 3, …, 2^N2N 。这些用户之间是用路由点和网线组成的。用户、路由点与网线共同构成一个满二叉树结构。树中的每一个叶子结点都是一个用户,每一个非叶子结点(灰色)都是一个路由点,而每一条边都是一条网线(见下图,用户结点中的数字为其编号)。
MY 网络公司的网络收费方式比较奇特,称为“ 配对收费 ”。即对于每两个用户i, j (1≤i < j ≤ 2^N2N ) 进行收费。由于用户可以自行选择两种付费方式A、B中的一种,所以网络公司向学校收取的费用与每一位用户的付费方式有关。该费用等于每两位不同用户配对产生费用之和。
为了描述方便,首先定义这棵网络树上的一些概念:
-
祖先:根结点没有祖先,非根结点的祖先包括它的父亲以及它的父亲的祖先;
-
管辖叶结点:叶结点本身不管辖任何叶结点,非叶结点管辖它的左儿子所管辖的叶结点与它的右儿子所管辖的叶结点;
-
距离:在树上连接两个点之间的用边最少的路径所含的边数。
对于任两个用户i, j (1≤i<j≤ 2^N2N ),首先在树上找到与它们距离最近的公共祖先:路由点P,然后观察P 所管辖的叶结点(即用户)中选择付费方式A 与B的人数,分别记为nA 与nB,接着按照网络管理条例第X 章第Y 条第Z 款进行收费(如下表),其中 F_{i,j}Fi,j 为i 和j 之间的流量,且为已知量。
由于最终所付费用与付费方式有关,所以NS 中学的用户希望能够自行改变自己的付费方式以减少总付费。然而,由于网络公司已经将每个用户注册时所选择的付费方式记录在案,所以对于用户i,如果他/她想改变付费方式(由A 改为B 或由B 改为A),就必须支付 C_iCi 元给网络公司以修改档案(修改付费方式记录)。
现在的问题是,给定每个用户注册时所选择的付费方式以及 C_iCi ,试求这些用户应该如何选择自己的付费方式以使得NS 中学支付给网络公司的总费用最少(更改付费方式费用+配对收费的费用)。
输入输出格式
输入格式:
输入文件中第一行有一个正整数N。
第二行有 2^N2N 个整数,依次表示1 号,2 号,…, 2^N2N 号用户注册时的付费方式,每一个数字若为0,则表示对应用户的初始付费方式为A,否则该数字为1,表示付费方式为B。
第三行有 2^N2N 个整数,表示每一个用户修改付费方式需要支付的费用,依次为 C_1C1 , C_2C2 , …, C_MCM 。( M= 2^N2N )
以下 2^N2N -1 行描述给定的两两用户之间的流量表F,总第(i + 3)行第j 列的整数为 F_{i, j+i}Fi,j+i 。(1≤i< 2^N2N ,1≤j≤ 2^N2N – i)
所有变量的含义可以参见题目描述。
输出格式:
你的程序只需要向输出文件输出一个整数,表示NS 中学支付给网络公司的最小总费用。(单位:元)
输入输出样例
说明
【样例说明】
将 1 号用户的付费方式由B 改为A,NS 中学支付给网络公司的费用达到最小。
40%的数据中N≤4;
80%的数据中N≤7;
100%的数据中N≤10,0≤Fi, j≤500,0≤C_iCi≤500 000。
#include<iostream> #include<cstdio> #include<cstring> #define maxn 1050 using namespace std; int n,N,map[maxn][maxn],c[maxn],fa[maxn][maxn],ans=0x7fffffff,sum; int cnt[maxn][2]; int a[maxn]; int father[1000]; void make(int sta){ for(int i=1;i<=n;i++) if(sta&(1<<i-1)) a[i]^=1,sum+=c[i]; } int work(){ memset(cnt,0,sizeof(cnt)); int s=0; for(int i=0;i<N;i++)s+=1<<i; for(int i=1;i<=n;i++){ if(a[i]==0)cnt[i+s][0]++; else cnt[i+s][1]++; } for(int i=s;i>=1;i--){ cnt[i][0]+=cnt[i*2][0]+cnt[i*2+1][0]; cnt[i][1]+=cnt[i*2][1]+cnt[i*2+1][1]; } for(int i=1;i<=n;i++) for(int j=i+1;j<=n;j++){ int f=fa[i][j]; if(cnt[f][0]<cnt[f][1]){ if(a[i]==0&&a[j]==0)sum+=2*map[i][j]; if(a[i]^a[j])sum+=map[i][j]; } else { if(a[i]==1&&a[j]==1)sum+=2*map[i][j]; if(a[i]^a[j])sum+=map[i][j]; } } } void findfather(){ int s=0; for(int i=0;i<N;i++)s+=1<<i; for(int i=1;i<=(1<<N);i++) for(int j=i+1;j<=(1<<N);j++){ int a1=s+i,a2=s+j; while(a1!=a2)a1=father[a1],a2=father[a2]; fa[i][j]=a1; } } int main(){ scanf("%d",&N); n=1<<N; for(int i=1;i<=(1<<N);i++) father[i*2]=i,father[i*2+1]=i; findfather(); for(int i=1;i<=n;i++)scanf("%d",&a[i]); for(int i=1;i<=n;i++)scanf("%d",&c[i]); for(int i=1;i<n;i++) for(int j=i+1;j<=n;j++) scanf("%d",&map[i][j]); for(int sta=0;sta<(1<<n);sta++){ sum=0; make(sta);work(); ans=min(ans,sum); make(sta); } printf("%d",ans); return 0; }
#include<iostream> #include<cstdio> #include<cstring> #define maxn 1030 using namespace std; int n,m,a[maxn],t[maxn]; long long c[maxn],w[maxn][maxn],v[maxn][maxn],f[maxn<<1][maxn]; void make(int x,int d){ if(!d)return; make(x<<1,d-1);make(x<<1|1,d-1); for(int i=x<<d;i<((x<<1)+1)<<(d-1);i++) for(int j=((x<<1)+1)<<(d-1);j<(x+1)<<d;j++) v[i-m][x]+=w[i-m][j-m], v[j-m][x]+=w[i-m][j-m]; } void dfs(int x,int d){ if(!d){ f[x][a[x-m]]=0; f[x][a[x-m]^1]=c[x-m]; for(int i=x>>1;i;i>>=1)f[x][t[i]]+=v[x-m][i]; return; } memset(f[x],0x3f,sizeof(f[x])); t[x]=1; dfs(x<<1,d-1);dfs(x<<1|1,d-1); for(int i=0;i<=1<<(d-1);i++) for(int j=0;j<=(1<<(d-1))-i;j++) f[x][i+j]=min(f[x][i+j],f[x<<1][i]+f[x<<1|1][j]); t[x]=0; dfs(x<<1,d-1);dfs(x<<1|1,d-1); for(int i=1;i<=1<<(d-1);i++) for(int j=(1<<(d-1))-i+1;j<=1<<(d-1);j++) f[x][i+j]=min(f[x][i+j],f[x<<1][i]+f[x<<1|1][j]); } int main(){ long long ans=1LL<<62; scanf("%d",&n);m=1<<n; for(int i=0;i<m;i++) scanf("%d",&a[i]); for(int i=0;i<m;i++) scanf("%lld",&c[i]); for(int i=0;i<m;i++) for(int j=i+1;j<m;j++) scanf("%lld",&w[i][j]); make(1,n); dfs(1,n); for(int i=0;i<=m;i++)ans=min(ans,f[1][i]); printf("%lld",ans); return 0; }