test20190827 NOIP2019 模拟赛

100+100+50=250。最后那道期望题需要用另外的方式统计。

精灵加护

ljss 被 M 个敌人打倒在地上啦!每个敌人有一个威力值 bi。但是他手中还拥有 N 把武器!每把武器有一个威力值 ai,每个武器可以消灭威力值小于等于自己的敌人,然后就不能再用了….

但是他又发现:拿着 N 个武器好像还是打不过这些敌人!但是他不虚,因为每把武器有一个精灵附体来保护他,每个精灵可以令这把武器无视一个特定敌人的威力值而直接秒杀他!

但是他还是不太确定在精灵的帮助下能否消灭所有敌人。如果能消灭所有敌人输出“Success”,否则输出“Fail”。

对于 100%的数据,N,M<=20000

题解

如果没有那个 c,显然应该贪心。把 n 个武器从小到大排序,对于每个武器贪心选取小于等于它的最大的敌人。这样做的最优性可以用决策包容来证明。

多了那个 c,分为两种情况。

  1. 消灭的敌人小于等于自己。这种情况下 c 无用。
  2. 消灭的敌人大于自己。那么显然应该直接选择它。这样做的最优性也可以用决策包容性证明。
    注意多对一的情况。我们需要从小到大考虑所有的 c。

时间复杂度(O(n log n))

co int N=20000+10;
struct node{int v,c;}a[N];
il bool operator<(co node&a,co node&b){
	return a.v<b.v;
}
int b[N],ta[N];
bool vis[N];
multiset<int> s;
multiset<int>::iterator it;
void real_main(){
	int n=read<int>(),m=read<int>();
	for(int i=1;i<=n;++i) read(a[i].v);
	for(int i=1;i<=m;++i) read(b[i]),vis[i]=0;
	for(int i=1;i<=n;++i) read(a[i].c);
	sort(a+1,a+n+1);
	int tot=0;
	for(int i=1;i<=n;++i){
		if(!vis[a[i].c]&&b[a[i].c]>a[i].v) vis[a[i].c]=1;
		else ta[++tot]=a[i].v;
	}
	s.clear(),s.insert(-1);
	for(int i=1;i<=m;++i)
		if(!vis[i]) s.insert(b[i]);
	for(int i=1;s.size()>1&&i<=tot;++i){
		it=s.upper_bound(ta[i]),--it;
		if(*it!=-1) s.erase(it);
	}
	puts(s.size()==1?"Success":"Fail");
}
int main(){
	freopen("guard.in","r",stdin),freopen("guard.out","w",stdout);
	for(int T=read<int>();T--;) real_main();
	return 0;
}

黄学长写的网络流。可以实现前缀连边。具体而言,可以将敌人从小到大排序,每个敌人向前一个连一条容量为无穷大的边。这样跑匹配即可得到60分。

相位幻击

首先 wzh1 施法造阵,造出了一个 n 个节点的树(以 1 为根),每个点上有一个点权。

但是敌人有时会干扰 wzh1,令他阵法树中某个结点的子树的所有权值都按位异或上一个值来打乱 wzh1 的部署,wzh1 偶尔也会让 ljss 随意指出两个点,然后把这条路径上的权值按位异或起来给敌人一个打击。请求出敌人每次受到的打击的值。

对于 100%的数据,n,m<=200000

题解

树剖模板题?反正我这么写就过了。实际上有一个 log 的做法。

设 z 为 x,y 的 LCA,从 x 到 y 的路径异或和=F(x)^F(y)^F(z)^F(fa[z]),其中 f(x)为 x 到根的异或和

然后考虑如何维护 F,考虑修改子树时的特点,所有奇数层的点的 F 值相当于异或了 x,而偶数层的点的 F 值没有变化,这是一个可以利用的特点

我们可以回想树链剖分的过程实际上是把树转化成区间,方便对子树和链进行操作,那么我们可以用类似的方法把奇数层和偶数层分开,然后把一个子树内的点并在一起,这样跨层异或就变成了区间异或

具体操作方法可以对树进行两次 DFS,第一次把奇数深度的推进数组,第二次推偶数深度的,然后记下每个点所对应的区间就可以了

也可以像刘老爷那样对于奇偶层分别打标记。

抗拒黄泉

焱犇画出了一个 n*m 的棋盘,其中有些格子是 0,有些是 1,每天 ljss 可以选择一个标有 1 的格子并标记它。只要棋盘的每行、每列都至少有一个被标记的格子时,ljss 就可以立即复活。但是 ljss 实在是太呆了,他每次只会从所有的标有 1 的格子中完全随机选择一个来标记..但是他还是想知道自己复活的期望天数。

对于 50%的数据,n,m<=8
对于 100%的数据,n,m<=20,n*m<=200,aij∈{0,1}

题解

首先说那个50分。将原图看成二分图,对行列覆盖情况进行状态压缩,暴力转移即可。

co int N=8,S=1<<8;
struct edge {int u,v;}e[N*N];
int tot;

bool vis[S][S];
double f[S][S];
double dp(int s,int t){
	if(vis[s][t]) return f[s][t];
	vis[s][t]=1;
	int same=0;
	for(int i=0;i<tot;++i){
		int ns=s|1<<e[i].u,nt=t|1<<e[i].v;
		if(ns==s&&nt==t) ++same;
		else f[s][t]+=dp(ns,nt);
	}
	f[s][t]/=tot-same;
	f[s][t]+=(double)tot/(tot-same);
	return f[s][t];
}
int main(){
	freopen("refuse.in","r",stdin),freopen("refuse.out","w",stdout);
	int n=read<int>(),m=read<int>();
	for(int i=0;i<n;++i)
		for(int j=0;j<m;++j)if(read<int>())
			e[tot++]=(edge){i,j};
	vis[(1<<n)-1][(1<<m)-1]=1;
	printf("%.5lf
",dp(0,0));
	return 0;
}

(P(i)) 为选 (i) 次才成功的概率,那么

[ans=sum_{i=1}^infty i P(i) ]

(P(i)) 可以通过枚举遗漏的行列来进行计算,注意这里要使用容斥原理。记 (S) 为一个行和列构成的集合,(P(S)) 为选中 (S) 集合内的格子的概率,那么

[P(i)=sum_{|S|mod 2=1}(1-P(S))^{i-1}P(S)-sum_{|S|mod 2=0} (1-P(S))^{i-1}P(S) ]

带入 (ans) 求和式

[ans=sum_{i=1}^infty i left(sum_{|S|mod 2=1}(1-P(S))^{i-1}P(S)-sum_{|S|mod 2=0} (1-P(S))^{i-1}P(S) ight)\ =sum_{|S|mod 2=1}sum_{i=1}^infty i (1-P(S))^{i-1}P(S)-sum_{|S|mod 2=0}sum_{i=1}^infty i(1-P(S))^{i-1}P(S) ]

前后两个第二重求和式里面的都是几何分布的期望。其实用等差*等比数列求和方式算一下就行了。

[ans=sum_{|S|mod 2=1} frac{1}{P(S)}-sum_{|S|mod 2=0}frac{1}{P(S)} ]

我们设格子的总数为 (tot)(pi(S))(S) 集合内方块的个数,那么

[ans=sum_{|S|mod 2=1} frac{tot}{pi(S)}-sum_{|S|mod 2=0}frac{tot}{pi(S)} ]

我们不能直接枚举 (S),所以这里应该变动枚举量。我们直接枚举 (pi(S)) 的取值。设 (num(pi)) 表示 (pi(S)=pi)(S) 的个数,注意这里的 (num) 是进行容斥过后的结果,即如果 (|S|) 为偶数,在 (num) 里它的贡献是负的。

[ans=sum_{pi=1}^{tot}num(pi)frac{tot}{pi} ]

然后问题就转化成了求 (num)

假如不选行的话,列是两两无关的。这个可以通过背包 DP 解决。由于 (n imes m leq 200),所以 $min(n,m) leq 14 $。我们可以通过转置使得行数 (n leq 14)。然后我们可以二进制枚举选了哪些行,对列剩下的部分 DP。

设已经选了 (a) 行,里面有 (b) 个格子。设 (f(i,j,k)) 表示前 (i) 列选了 (j) 列,里面有 (k) 个格子的方案数。注意这里的 (f) 没有经过容斥。那么

[ans=sum_{a,b}sum_{j,k}f(m,j,k)frac{tot}{k+b}(-1)^{j+a+1} ]

(f) 的转移是普及组内容,所以问题就解决了。

时间复杂度 (O(2^n mcdot nm)),算出来是 4e7。

co int N=20;
int a[N][N],sum[N];
int p[N],f[N][N*N+1];

void rotate(int&n,int&m){
	static int b[N][N];
	for(int i=0;i<n;++i)
		for(int j=0;j<m;++j) b[j][i]=a[i][j];
	swap(n,m);
	for(int i=0;i<n;++i)
		for(int j=0;j<m;++j) a[i][j]=b[i][j];
}
int main(){
	freopen("refuse.in","r",stdin),freopen("refuse.out","w",stdout);
	int n=read<int>(),m=read<int>();
	int tot=0;
	for(int i=0;i<n;++i)
		for(int j=0;j<m;++j) tot+=read(a[i][j]);
	if(n>m) rotate(n,m);
	for(int i=0;i<n;++i)
		for(int j=0;j<m;++j) sum[i]+=a[i][j];
	double ans=0;
	for(int s=0;s<1<<n;++s){
		int low=0,row=0;
		for(int i=0;i<n;++i)
			if(s>>i&1) low+=sum[i],++row;
		for(int i=0;i<m;++i){
			p[i]=0;
			for(int j=0;j<n;++j)
				if(~s>>j&1) p[i]+=a[j][i];
		}
		fill(f[0],f[0]+tot+1,0);
		if(row&1) f[0][low]=1,f[0][low+p[0]]+=-1;
		else f[0][low]=-1,f[0][low+p[0]]+=1;
		for(int i=1;i<m;++i)
			for(int j=0;j<=tot;++j){
				f[i][j]=f[i-1][j];
				if(j>=p[i]) f[i][j]-=f[i-1][j-p[i]]; // choose
			}
		for(int j=1;j<=tot;++j)
			ans+=(double)f[m-1][j]*tot/j;
	}
	printf("%.5lf
",ans);
	return 0;
}

这份代码的背包 DP 稍微有些奇怪,不过还是可以理解的。

原文地址:https://www.cnblogs.com/autoint/p/test20190827.html