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



發佈了68 篇原創文章 · 獲贊 17 · 訪問量 12萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章