BZOJ4770 图样 dp 期望 打表

18 篇文章 0 订阅
1 篇文章 0 订阅

题目链接

题意:
给你一个 n n n个点的完全图,每个点的点权是 [ 0 , 2 m ) [0,2^m) [0,2m)之间的一个随机整数,两点之间的边的权值是两点点权的异或值,求最小生成树的边权和的期望。对258280327(质数)取模, n &lt; = 50 , m &lt; = 8 n&lt;=50,m&lt;=8 n<=50,m<=8

题解:
一道好题!
才不会说虽然我想不出来,但是我不知道为什么对这种复杂的计数和期望题感兴趣呢

首先我们设 f [ i ] [ k ] f[i][k] f[i][k] i i i个点的完全图,每个点的点权在 [ 0 , 2 k ) [0,2^k) [0,2k)时的答案。转移时我们枚举一个 x x x,意思是假设有 x x x个点的二进制下第 k k k位是 1 1 1,另外的 i − x i-x ix个点的二进制下的第 k k k位都是 0 0 0。由于边权是两点点权的异或,所以最小生成树应该是所有第 k k k位是 0 0 0的之间互相连接,所有第 k k k位是 1 1 1的之间互相连,把第 k k k位是0的点看作一个集合,是 1 1 1的点看作一个集合,然后再在两个集合之间连一条边。因为在集合内部连边每条边的权值一定是小于 2 k − 1 2^{k-1} 2k1的,而连接两个集合的边的权值一定是大于 2 k − 1 2^{k-1} 2k1的,所以我们尽可能的少连接两端位于两个不同集合的边,但是不能不连,于是就只连一条边。

两部分内部的答案是 f [ x ] [ k − 1 ] f[x][k-1] f[x][k1] f [ i − x ] [ k − 1 ] f[i-x][k-1] f[ix][k1],那么我们要算的就是两个部分有 n n n个点和 m m m个点,点权都在 [ 0 , 2 k ) [0,2^k) [0,2k)时两部分之间的最小边权期望,我们设这个期望为 g [ n ] [ m ] [ k ] g[n][m][k] g[n][m][k]

我们发现这个 g g g也不是很好算,对于一个期望,我们要算的是概率*权值,于是我们可以从这个角度考虑。我们设 h [ n ] [ m ] [ k ] [ l ] h[n][m][k][l] h[n][m][k][l]表示两部分分别有 n n n个点和 m m m个点,点权在 [ 0 , 2 k ) [0,2^k) [0,2k)时两部分之间边权的最小值大于等于 l l l的概率,我们发现,其实在这里每种情况的权值可以看作是 1 1 1,于是这个期望就可以看作是算每一个 l l l对应的概率的和。

我们考虑怎么算这个 h h h。我们需要枚举两个部分分别有多少个点的点权在二进制下第 k k k位是 1 1 1,我们需要分 l l l 2 k − 1 2^{k-1} 2k1的大小关系来分类讨论。我们需要分四个部分考虑,一个部分是两个集合中二进制下第 k k k位都是 1 1 1的点,这些点之间的边在考虑了第 k k k位之后边权仍然不会有变化,所以答案应该还是只有 k − 1 k-1 k1位时的答案。同理如果两侧第 k k k位是 0 0 0的点之间连的边也是这样。那么我们考虑一侧第 k k k位是 1 1 1与另一侧第 k k k位是 0 0 0时的连边,这些边一定会加上一个 2 k − 1 2^{k-1} 2k1,所以我们只需要考虑在 k − 1 k-1 k1位时都比 l − 2 k − 1 l-2^{k-1} l2k1大就可以了,如果 l &gt; 2 k − 1 l&gt;2^{k-1} l>2k1,就乘上那时候的概率,否则如果 l &lt; = 2 k − 1 l&lt;=2^{k-1} l<=2k1,那么加上一个 2 k − 1 2^{k-1} 2k1一定比当前要求的 l l l要大,概率是 1 1 1,就不用再乘了。最后我们还要用一个组合数除以总的方案数来求出所有与这种情况等级的情况的期望之和。
综合一下以上所说的,我们列出一下的转移式: h [ i ] [ j ] [ k ] [ k ] = ∑ x = 1 n ∑ y = 1 x + y &lt; = n C i x C j y 2 n + m h [ x ] [ y ] [ k − 1 ] [ l ] ∗ h [ i − x ] [ j − y ] [ k − 1 ] [ l ] ∗ h [ x ] [ n − y ] [ k − 1 ] [ l − 2 k − 1 ] ∗ h [ n − x ] [ y ] [ k − 1 ] [ l − 2 k − 1 ] h[i][j][k][k]=\sum_{x=1}^n\sum_{y=1}^{x+y&lt;=n}\frac{C_{i}^{x}C_{j}^{y}}{2^{n+m}}h[x][y][k-1][l]*h[i-x][j-y][k-1][l]*h[x][n-y][k-1][l-2^{k-1}]*h[n-x][y][k-1][l-2^{k-1}] h[i][j][k][k]=x=1ny=1x+y<=n2n+mCixCjyh[x][y][k1][l]h[ix][jy][k1][l]h[x][ny][k1][l2k1]h[nx][y][k1][l2k1]

算出 h h h之后,我们就可以算出 g g g了。 g [ i ] [ j ] [ k ] = ∑ l = 1 2 m − 1 h [ i ] [ j ] [ k ] [ l ] g[i][j][k]=\sum_{l=1}^{2^{m}-1}h[i][j][k][l] g[i][j][k]=l=12m1h[i][j][k][l]

算出 g g g之后,我们再去算 f f f f [ i ] [ k ] = ∑ j = 0 i C i j 2 i ( f [ j ] [ k − 1 ] + f [ i − j ] [ k − 1 ] + g [ j ] [ i − j ] [ k − 1 ] + 2 k ) f[i][k]=\sum_{j=0}^i\frac{C_i^j}{2^i}(f[j][k-1]+f[i-j][k-1]+g[j][i-j][k-1]+2^k) f[i][k]=j=0i2iCij(f[j][k1]+f[ij][k1]+g[j][ij][k1]+2k)

这样就可以了,但是以上的每一个式子都要注意边界,注意是否在边界内。另外就是注意初始化时该初始成什么。

这样复杂度是 O ( n 4 ∗ m ∗ 2 m ) O(n^4*m*2^m) O(n4m2m),复杂度瓶颈是算 h h h。这个复杂度是很难直接通过的,但是我们可以把表打出来交表。

用来打表的代码:

#include <bits/stdc++.h>
using namespace std;

const long long mod=258280327;
int n,m;
long long f[110][1010],g[52][52][10],h[52][52][10][1030],c[52][52],mi[100];
inline long long ksm(long long x,long long y)
{
	long long res=1;
	while(y)
	{
		if(y&1)
		res=res*x%mod;
		x=x*x%mod;
		y>>=1;
	}
	return res;
}
int main()
{
	scanf("%d%d",&n,&m);
	c[0][0]=1;
	for(int i=1;i<=n;++i)
	{
		c[i][0]=1;
		for(int j=1;j<=i;++j)
		c[i][j]=(c[i-1][j-1]+c[i-1][j])%mod;
	}
	mi[0]=1;
	for(int i=1;i<=n+m+1;++i)
	mi[i]=mi[i-1]*2;
	//l=0时对答案不会有影响,所以不用算概率 
	for(int i=1;i<=n;++i)
	{
		for(int j=1;i+j<=n;++j)
		h[i][j][0][1]=ksm(mi[i+j-1]%mod,mod-2);//k=0,权值只能是0或1 
		//有一个权值是1,其他是0和1都写,所以是2^{i+j-1} 		
	}
	for(int k=1;k<m;++k)
	{
		for(int i=1;i<=n;++i)
		{
			for(int j=1;i+j<=n;++j)
			{			
				for(int l=1;l<mi[k+1];++l)
				{
					for(int x=0;x<=i;++x)
					{
						for(int y=0;y<=j;++y)
						{
							long long ji=c[i][x]*c[j][y]%mod;
							if(x&&y)
							ji=ji*h[x][y][k-1][l]%mod;
							if(i-x&&j-y)
							ji=ji*h[i-x][j-y][k-1][l]%mod;
							if(x&&j-y&&l>mi[k])
							ji=ji*h[x][j-y][k-1][l-mi[k]]%mod;
							if(y&&i-x&&l>mi[k])
							ji=ji*h[i-x][y][k-1][l-mi[k]]%mod;
							h[i][j][k][l]=(h[i][j][k][l]+ji)%mod;
						}
					}
					h[i][j][k][l]=h[i][j][k][l]*ksm(mi[i+j]%mod,mod-2)%mod;										
				}
			}			
		}
	}
	for(int i=1;i<=n;++i)
	{
		for(int j=1;i+j<=n;++j)
		{
			for(int k=0;k<m;++k)
			{
				for(int l=1;l<mi[k+1];++l)
				g[i][j][k]=(g[i][j][k]+h[i][j][k][l])%mod;
			}
		}
	}
	for(int i=1;i<=n;++i)
	{
		for(int j=0;j<=i;++j)
		{
			long long ji=c[i][j];
			if(j&&i-j)//两个集合都要有点 
			f[i][0]=(f[i][0]+ji)%mod;			
		}
		f[i][0]=f[i][0]*ksm(mi[i]%mod,mod-2)%mod;
	}
	for(int k=1;k<m;++k)
	{
		for(int i=1;i<=n;++i)
		{
			for(int j=0;j<=i;++j)
			{
				long long ji=0;
				if(j&&i-j)
				ji=(g[j][i-j][k-1]+mi[k])%mod;
				if(j)
				ji=(ji+f[j][k-1])%mod;		
				if(i-j)
				ji=(ji+f[i-j][k-1])%mod;	
				f[i][k]=(f[i][k]+c[i][j]*ji%mod)%mod;
			}
			f[i][k]=f[i][k]*ksm(mi[i]%mod,mod-2)%mod;
		}
	}
	for(int i=1;i<=n;++i)
	{
		for(int j=1;j<=m;++j)
		printf("%lld ",f[i][j-1]);
		printf("\n");
	}	
	return 0;
}

但是我并没有把打好的表一个数一个数的输进交表程序,而是用了另一种写法,我偷了个懒,让程序帮我打表。
最终的打表程序:

#include <bits/stdc++.h>
using namespace std;

const long long mod=258280327;
int n,m;
long long f[110][1010],g[52][52][10],h[52][52][10][1030],c[52][52],mi[100];
inline long long ksm(long long x,long long y)
{
	long long res=1;
	while(y)
	{
		if(y&1)
		res=res*x%mod;
		x=x*x%mod;
		y>>=1;
	}
	return res;
}
int main()
{
	freopen("1.out","w",stdout);//先输出到这个文件里
	scanf("%d%d",&n,&m);
	c[0][0]=1;
	for(int i=1;i<=n;++i)
	{
		c[i][0]=1;
		for(int j=1;j<=i;++j)
		c[i][j]=(c[i-1][j-1]+c[i-1][j])%mod;
	}
	mi[0]=1;
	for(int i=1;i<=n+m+1;++i)
	mi[i]=mi[i-1]*2;
	//l=0时对答案不会有影响,所以不用算概率 
	for(int i=1;i<=n;++i)
	{
		for(int j=1;i+j<=n;++j)
		h[i][j][0][1]=ksm(mi[i+j-1]%mod,mod-2);//k=0,权值只能是0或1 
		//有一个权值是1,其他是0和1都写,所以是2^{i+j-1} 		
	}
	for(int k=1;k<m;++k)
	{
		for(int i=1;i<=n;++i)
		{
			for(int j=1;i+j<=n;++j)
			{			
				for(int l=1;l<mi[k+1];++l)
				{
					for(int x=0;x<=i;++x)
					{
						for(int y=0;y<=j;++y)
						{
							long long ji=c[i][x]*c[j][y]%mod;
							if(x&&y)
							ji=ji*h[x][y][k-1][l]%mod;
							if(i-x&&j-y)
							ji=ji*h[i-x][j-y][k-1][l]%mod;
							if(x&&j-y&&l>mi[k])
							ji=ji*h[x][j-y][k-1][l-mi[k]]%mod;
							if(y&&i-x&&l>mi[k])
							ji=ji*h[i-x][y][k-1][l-mi[k]]%mod;
							h[i][j][k][l]=(h[i][j][k][l]+ji)%mod;
						}
					}
					h[i][j][k][l]=h[i][j][k][l]*ksm(mi[i+j]%mod,mod-2)%mod;										
				}
			}			
		}
	}
	for(int i=1;i<=n;++i)
	{
		for(int j=1;i+j<=n;++j)
		{
			for(int k=0;k<m;++k)
			{
				for(int l=1;l<mi[k+1];++l)
				g[i][j][k]=(g[i][j][k]+h[i][j][k][l])%mod;
			}
		}
	}
	for(int i=1;i<=n;++i)
	{
		for(int j=0;j<=i;++j)
		{
			long long ji=c[i][j];
			if(j&&i-j)//两个集合都要有点 
			f[i][0]=(f[i][0]+ji)%mod;			
		}
		f[i][0]=f[i][0]*ksm(mi[i]%mod,mod-2)%mod;
	}
	for(int k=1;k<m;++k)
	{
		for(int i=1;i<=n;++i)
		{
			for(int j=0;j<=i;++j)
			{
				long long ji=0;
				if(j&&i-j)
				ji=(g[j][i-j][k-1]+mi[k])%mod;
				if(j)
				ji=(ji+f[j][k-1])%mod;		
				if(i-j)
				ji=(ji+f[i-j][k-1])%mod;	
				f[i][k]=(f[i][k]+c[i][j]*ji%mod)%mod;
			}
			f[i][k]=f[i][k]*ksm(mi[i]%mod,mod-2)%mod;
		}
	}
	for(int i=1;i<=n;++i)
	{
		cout<<"{";
		for(int j=1;j<=m;++j)
		{
			printf("%lld",f[i][j-1]);
			if(j!=m)
			cout<<",";
		}
		printf("}");
		if(i!=n)
		cout<<","<<endl;
	}	
	return 0;
}

这样你只需要把那个二维数组的大括号打好就行,另外我们做的时候是从 n , m = 1 n,m=1 n,m=1时开始,但是放到数组里下标是从 0 0 0开始,所以输出答案的时候要输出 n − 1 , m − 1 n-1,m-1 n1,m1时的答案。

最终的交表程序:

#include <bits/stdc++.h>
using namespace std;

int n,m;
int ans[51][9]={{0,0,0,0,0,0,0,0},
{129140164,129140165,129140167,129140171,129140179,129140195,129140227,129140291},
{193710246,64570084,24213786,236084373,77181450,157515737,140774228,215031499},
{225995287,121068906,238102183,203547108,218712265,116264831,99443036,79121964},
{112997644,108962016,46788094,36549268,117877184,14509798,100438248,107231500},
{185638986,53976556,134689163,96426552,194242540,153639990,120659378,101104631},
{221959657,227256424,124881873,171543395,157647025,162557411,23785165,258144187},
{110979829,87995657,38609442,206828800,253024756,206227824,141710270,10320181},
{55489915,109955160,56272067,231265411,63090854,237048705,171426005,85709662},
{27744958,238738659,7008635,102222843,120595225,256801988,235521295,180619543},
{143012643,24411822,194556475,215231251,130161896,73648531,208001599,170619964},
{71506322,157216049,153352677,156238751,192264872,27770309,226347497,54161031},
{164893325,184923874,246796406,52697789,171780083,21983921,223228206,185664220},
{82446663,171588386,198224372,24087433,169856546,85584783,15039313,51032118},
{41223332,233770283,237290397,226108046,172324917,242546032,177098806,40173324},
{149751830,40172257,206855876,234897786,42845559,190284536,34527590,26337201},
{204016079,44767891,130125758,198580206,248091586,33588797,142939578,109276637},
{102008040,53378114,117153053,140321556,207663118,231585645,18039713,42557731},
{180144184,222551860,141542926,124678758,256378666,58621640,169814611,164503795},
{219212256,51118260,22017057,107400239,71063488,208194809,141040710,82368253},
{238746292,9389787,73404282,197290983,187666354,33421568,243481387,230191654},
{248513310,258085441,206092103,175865014,46934405,238338656,34885039,160507772},
{253396819,46472091,52427070,10622380,31557757,78236842,121818911,78588165},
{126698410,14223592,100655489,4793966,28513326,106020021,130112147,61905360},
{192489369,53937637,55392462,243667232,206236595,2350355,2101084,238304638},
{96244685,115840796,252931583,12885294,108834416,142226001,41501066,77317254},
{48122343,186440092,145791627,175931570,191171533,54681626,171376190,8284899},
{24061172,35579780,244199696,219925978,194916377,159910040,18680912,56283054},
{141170750,129762427,191545910,85767863,96762108,75032252,68489928,100763661},
{199725539,123091219,123096044,64432902,33418232,219199667,205120656,160043295},
{99862770,147188326,236754524,80629277,18247771,18374728,15226623,146765106},
{179071549,43396121,1036568,86131407,196354204,129359671,55657413,95474816},
{89535775,177223515,164355065,176671379,141757722,64001077,97905256,148393376},
{44767888,56089100,9744293,28759687,254183618,187978963,48484166,10384133},
{151524108,71287253,195783672,235105036,63372602,48706668,248893454,173302144},
{204902218,189910711,57376262,237466820,231810127,127325080,116537614,29856382},
{231591273,257256954,207490218,200639665,46244374,29857601,109793313,58979680},
{115795637,205506176,45896798,234868496,29541064,251294633,256749187,63816439},
{57897819,124985457,245728025,115443775,144062289,195058805,171514012,53312683},
{28948910,21882968,223817069,76823424,21491759,227107331,131344269,97420818},
{143614619,14590716,250142242,217871110,158025913,120380172,59940904,105696964},
{71807310,75057742,38795205,80612780,246470934,120257251,3687048,158432457},
{165043819,201462147,168744597,172739460,61176618,38568071,97892751,11175058},
{82521910,219673862,27383363,66850809,196775038,6183180,92038631,31326683},
{170401119,101187108,75392768,51235977,45924565,86039077,51855151,254177214},
{85200560,221423464,251701329,91563073,211230946,180879303,101994951,244856176},
{171740444,202450882,103638399,9527704,148889553,41207463,141727928,145038853},
{215010386,225504065,114480260,68277350,11820640,1248372,121446262,9716295},
{236645357,26119321,236275454,147928086,206426334,162743844,101312146,105149937},
{118322679,258260157,59715480,13946009,169602037,225439100,30268418,150919282}};
int main()
{
	scanf("%d%d",&n,&m);
	printf("%d\n",ans[n-1][m-1]);
	return 0;
}
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值