ZJOI 2015 题目选做

幻想乡 Wi-Fi 搭建计划

题目描述

点此看题

解法

博主暂时不会证明这个关键的 \(\tt observaion\)

有一个关键的 \(\tt observation\) 是:考虑一种选取网络架设点的方案,一定存在一种划分方式,使得将景点按照 \(x\) 排序之后,架设点覆盖一段连续的景点(注意这个结论只适用上方和下方单独存在的情况)

所以可以设计 \(dp\),设 \(dp[i][j][k]\) 表示考虑前 \(i\) 个景点,前 \(j\) 个上方的架设点,前 \(k\) 个下方的架设点,当前正在覆盖的架设点是上方的第 \(j\) 个和下方的第 \(k\) 个的最小代价。那么转移考虑用 \(j/k\) 覆盖景点 \(i\),或者在上方 \(/\) 下方拓展一个新的架设点来覆盖景点 \(i\),这种转移需要枚举一下,时间复杂度 \(O(n^4)\)

#include <cstdio>
#include <cstring>
#include <iostream>
#include <algorithm>
using namespace std;
const int M = 105;
#define int long long
const int inf = 1e18;
int read()
{
	int x=0,f=1;char c;
	while((c=getchar())<'0' || c>'9') {if(c=='-') f=-1;}
	while(c>='0' && c<='9') {x=(x<<3)+(x<<1)+(c^48);c=getchar();}
	return x*f;
}
int n,m,R,k1,k2,k3,ans,dp[M][M][M];
struct node{int x,y,w;}a[M],b[M],c[M],d[M];
int sqr(int x) {return x*x;}
void upd(int &x,int y) {x=min(x,y);}
int dis(node A,node B)
{
	return sqr(A.x-B.x)+sqr(A.y-B.y)<=R*R;
}
signed main()
{
	n=read();m=read();R=read();
	for(int i=1;i<=n;i++)
		a[i].x=read(),a[i].y=read();
	for(int i=1;i<=m;i++)
	{
		int x=read(),y=read(),w=read();
		if(y<0) b[++k1]={x,y,w};
		else c[++k2]={x,y,w};
	}
	for(int i=1;i<=n;i++)
	{
		bool f=0;
		for(int j=1;j<=k1;j++) f|=dis(a[i],b[j]);
		for(int j=1;j<=k2;j++) f|=dis(a[i],c[j]);
		if(f) d[++k3]=a[i];
	}
	auto cmp = [&] (node A,node B) {return A.x<B.x;};
	sort(b+1,b+1+k1,cmp);
	sort(c+1,c+1+k2,cmp);
	sort(d+1,d+1+k3,cmp);
	memset(dp,0x3f,sizeof dp);
	ans=inf;dp[0][0][0]=0;
	for(int i=1;i<=k3;i++) for(int j=0;j<=k1;j++)
		for(int k=0;k<=k2;k++) if(dp[i-1][j][k]<inf)
		{
			int t=dp[i-1][j][k];
			if((j && dis(d[i],b[j])) || 
			(k && dis(d[i],c[k]))) upd(dp[i][j][k],t);
			for(int l=j+1;l<=k1;l++) if(dis(d[i],b[l]))
				upd(dp[i][l][k],t+b[l].w);
			for(int l=k+1;l<=k2;l++) if(dis(d[i],c[l]))
				upd(dp[i][j][l],t+c[l].w);
		}
	for(int i=0;i<=k1;i++) for(int j=0;j<=k2;j++)
		upd(ans,dp[k3][i][j]);
	printf("%lld\n%lld\n",k3,ans);
}

地震后的幻想乡

题目描述

点此看题

解法

概率和期望题一定要注意初始的转化啊,一般都有一个比较简洁的形式的。

充分利用题目中的提示,首先我们考虑暴力,可以暴力枚举边的大小关系,然后用 \(\tt kruskal\) 计算出至少要加入前 \(k\) 条边才能够联通,那么答案的期望就是 \(\frac{k}{m+1}\),每种大小关系是等概率出现的,所以枚举全排列即可。

暴力启发我们转化问题,我们可以求出,在所有边中选取 \(k\) 条边加入之后恰好联通的概率(这里就不要再纠结于排列了)。而这可以转化成,选取 \(k-1\) 条边加入之后不连通的概率,减去选取 \(k\) 条边加入之后不连通的概率。

这变成了一个较为简洁的图连通性问题,设 \(g(s,i)/f(s,i)\) 分别表示对于点集 \(s\),在他的导出子图中选择 \(i\) 条边,连通 \(/\) 不连通的方案数,首先有关系式,设 \(d_s\)\(s\) 导出子图的边数:

\[f(s,i)+g(s,i)={d_s\choose i} \]

考虑 \(f(s,i)\) 的转移,其实就是枚举子集 \(t\),满足 \(t\) 是一个连通块并且 \(t\)\(s-t\) 之间没有边,为了避免算重我们需要强制一个特定的点 \(k\)\(t\) 中(转移的本质就是正难则反):

\[f(s,i)=\sum_{k\in t\subset s}\sum_{j=0}^{d_t}g(t,j)\cdot {d_{s-t}\choose i-j} \]

转移完成了,考虑最后的答案是:

\[ans=\sum_{k=1}^{m}\frac{k}{m+1}\cdot\Big(\frac{f(u,k-1)}{{d_u\choose k-1}}-\frac{f(u,k)}{{d_u\choose k}}\Big)=\frac{1}{m+1}\sum_{k=0}^m\frac{f(u,k)}{{d_u\choose k}} \]

时间复杂度 \(O(3^n\cdot m^2)\)但是我真的感觉这题没什么难点就是做不来

总结

概率期望类型的题目中,一些前缀\(/\)差分类型的转化往往能够简化问题。

#include <cstdio>
#include <iostream>
using namespace std; 
const int M = 1<<10;
#define db double
int read()
{
	int x=0,f=1;char c;
	while((c=getchar())<'0' || c>'9') {if(c=='-') f=-1;}
	while(c>='0' && c<='9') {x=(x<<3)+(x<<1)+(c^48);c=getchar();}
	return x*f;
}
int n,m,a[50],b[50],d[M];
db ans,g[M][50],f[M][50],C[50][50];
signed main()
{
	n=read();m=read();
	for(int i=1;i<=m;i++)
		a[i]=read()-1,b[i]=read()-1;
	for(int i=0;i<=m;i++)
	{
		C[i][0]=1;
		for(int j=1;j<=i;j++)
			C[i][j]=C[i-1][j-1]+C[i-1][j];
	}
	for(int s=0;s<(1<<n);s++)
		for(int i=1;i<=m;i++)
			d[s]+=((s>>a[i]&1)&&(s>>b[i]&1));
	for(int s=0;s<(1<<n);s++) for(int i=0;i<=d[s];i++)
	{
		int k=s&(-s);
		for(int t=(s-1)&s;t;t=(t-1)&s) if(t&k)
			for(int j=0;j<=min(i,d[t]);j++)
				f[s][i]+=g[t][j]*C[d[s^t]][i-j];
		g[s][i]=C[d[s]][i]-f[s][i];
	}
	for(int i=0;i<=m;i++)
		ans+=f[(1<<n)-1][i]/C[m][i];
	ans=ans/(m+1);
	printf("%.6f\n",ans); 
}
posted @ 2022-03-23 16:12  C202044zxy  阅读(120)  评论(2编辑  收藏  举报