• 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 kruskall\) 计算出至少要加入前 \(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); 
    }
    
  • 相关阅读:
    msql 触发器
    微信模板消息推送
    微信朋友朋友圈自定义分享内容
    微信退款
    异步调起微信支付
    微信支付
    第一次作业
    【Linus安装MongoDB及Navicat】
    【前端】ES6总结
    【开发工具】Pycharm使用
  • 原文地址:https://www.cnblogs.com/C202044zxy/p/16044998.html
Copyright © 2020-2023  润新知