• #20 CF303E & CF343E & CF360E


    Random Ranking

    题目描述

    点此看题

    解法

    首先考虑一个简单的问题,假设 \(k\) 个人的得分在 \([l,r]\) 中随机选取,那么某个人获得 \(1\sim k\) 排名的概率都是 \(\frac{1}{k}\)

    那么我们考虑对于离散化后的每一个小段 \([p_i,p_{i+1})\),我们把一些人分配到小段的左边,把一些人分配到小段的右边,把 \(x\) 和一些人分配到小段中。那么计算出分配的概率,就可以套用上面的模型,得到 \(x\) 对应排名的概率。

    那么问题转化成了计算这东西:\(f[i][a][b]\) 表示考虑前 \(i\) 个不是 \(x\) 的人,小段左边的人有 \(a\) 个,小段右边的人有 \(b\) 个,剩下的人都在小段中的概率。一个人的概率可以通过讨论线段关系简单计算,那么这就变成一个背包问题了。

    时间复杂度 \(O(n^5)\),好像不是很能过的样子。但考虑排除某个点,又是加入的问题显然可以用分治优化,对于每个段批量处理,加入左半边的点递归到右半边,加入右半边的点递归到左半边,时间复杂度 \(O(n^4\log n)\)

    总结

    很多实数概率题的技巧通常是,先解决一个能用概率简单计算的子问题,然后把原问题化归到这个简单问题上(比如 地震后的幻想乡

    #include <cstdio>
    #include <iostream>
    #include <algorithm>
    using namespace std;
    const int M = 165;
    #define db double
    const db eps = 1e-8;
    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,v,l[M],r[M],p[M],k[10];
    db a[M][M],f[10][M][M],g[M][M],s[M][M][M];
    db A[M][M],B[M][M],C[M][M];
    db ins(int i,int x)
    {
    	if(l[i]>x) return 0;
    	if(r[i]<x) return 1;
    	return 1.0*(x-l[i])/(r[i]-l[i]);
    }
    void add(int d,int x)
    {
    	for(int i=0;i<=k[d];i++)
    		for(int j=0;i+j<=k[d];j++)
    		{
    			if(f[d][i][j]<eps) continue;
    			g[i+1][j]+=f[d][i][j]*A[x][v];
    			g[i][j+1]+=f[d][i][j]*B[x][v];
    			g[i][j]+=f[d][i][j]*C[x][v];
    		}
    	k[d]++;
    	for(int i=0;i<=k[d];i++)
    		for(int j=0;i+j<=k[d];j++)
    			f[d][i][j]=g[i][j],g[i][j]=0;
    }
    void cdq(int d,int l,int r)
    {
    	if(l==r)
    	{
    		for(int i=0;i<=k[d];i++)
    			for(int j=0;i+j<=k[d];j++)
    				s[l][i][j]+=f[d][i][j]*B[l][v];
    		return ;
    	}
    	int mid=(l+r)>>1;
    	k[d+1]=k[d];
    	for(int i=0;i<=k[d];i++)
    		for(int j=0;i+j<=k[d];j++)
    			f[d+1][i][j]=f[d][i][j];
    	for(int i=l;i<=mid;i++) add(d+1,i);
    	cdq(d+1,mid+1,r);
    	k[d+1]=k[d];
    	for(int i=0;i<=k[d];i++)
    		for(int j=0;i+j<=k[d];j++)
    			f[d+1][i][j]=f[d][i][j];
    	for(int i=mid+1;i<=r;i++) add(d+1,i);
    	cdq(d+1,l,mid);
    }
    signed main()
    {
    	n=read();
    	for(int i=1;i<=n;i++)
    	{
    		l[i]=read();r[i]=read();
    		p[++m]=l[i];p[++m]=r[i];
    	}
    	sort(p+1,p+1+m);
    	m=unique(p+1,p+1+m)-p-1;
    	for(int i=1;i<=n;i++)c++
    		for(int j=1;j<m;j++)
    		{
    			db x=ins(i,p[j]),y=ins(i,p[j+1]);
    			A[i][j]=x;
    			B[i][j]=y-x;
    			C[i][j]=1-y;
    		}
    	f[1][0][0]=1.0;k[1]=0;
    	for(v=1;v<m;v++) cdq(1,1,n);
    	for(int x=1;x<=n;x++) for(int i=0;i<n;i++)
    		for(int j=0;i+j<n;j++) for(int k=1;k<=j+1;k++)
    			a[x][i+k]+=s[x][i][j]/(j+1);
    	for(int i=1;i<=n;i++,puts(""))
    		for(int j=1;j<=n;j++)
    			printf("%.8f ",a[i][j]);
    }
    

    Pumping Stations

    题目描述

    点此看题

    解法

    还是第一次用到最小割树这个知识点,能用得上去也是很不错了。

    首先我们建出原图的最小割树,那么问题变成了寻找一个排列,使得相邻两点路径上最小值之和最大。

    我们猜测答案上界是每条边恰好贡献一次。证明可以考虑调整法,我们取出最小的边 \((u,v)\),设 \(u\) 子树内的两个点 \((u_0,u_1)\)\(v\) 子树的两个点 \((v_0,v_1)\),如果按照 \(u_0,v_0,u_1,v_1\) 的顺序是没有 \(u_0,u_1,v_0,v_1\) 更优的,所以两个子树的点要挨在一起,那么这条边只会贡献一次。

    所以可以递归地构造答案,每次找到最小的边,然后分成两棵子树递归下去即可。

    #include <cstdio>
    #include <queue>
    using namespace std;
    const int M = 2005;
    const int inf = 0x3f3f3f3f;
    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,k,tot=1,S,T,f[M],cur[M],d[M];
    int ans,t,a[M],b[M],c[M],w[M],vis[M];
    struct edge{int v,c,next;}e[M<<1];
    struct node{int v,id;};vector<node> g[M];
    //part I : network flow
    void add(int u,int v,int c)
    {
    	e[++tot]=edge{v,c,f[u]},f[u]=tot;
    	e[++tot]=edge{u,0,f[v]},f[v]=tot;
    }
    int bfs()
    {
    	queue<int> q;
    	for(int i=1;i<=n;i++) d[i]=0;
    	q.push(S);d[S]=1;
    	while(!q.empty())
    	{
    		int u=q.front();q.pop();
    		if(u==T) return 1;
    		for(int i=f[u];i;i=e[i].next)
    		{
    			int v=e[i].v;
    			if(e[i].c>0 && !d[v])
    				d[v]=d[u]+1,q.push(v);
    		}
    	}
    	return 0;
    }
    int dfs(int u,int ept)
    {
    	if(u==T) return ept;
    	int flow=0,tmp=0;
    	for(int &i=cur[u];i;i=e[i].next)
    	{
    		int v=e[i].v;
    		if(e[i].c>0 && d[v]==d[u]+1)
    		{
    			tmp=dfs(v,min(ept,e[i].c));
    			if(!tmp) continue;
    			flow+=tmp;ept-=tmp;
    			e[i].c-=tmp;e[i^1].c+=tmp;
    			if(!ept) break;
    		}
    	}
    	return flow;
    }
    int dinic(int A,int B)
    {
    	for(int i=2;i<=tot;i+=2)
    		e[i].c+=e[i^1].c,e[i^1].c=0;
    	S=A;T=B;int r=0;
    	while(bfs())
    	{
    		for(int i=1;i<=n;i++) cur[i]=f[i];
    		r+=dfs(S,inf);
    	}
    	return r;
    }
    //part II : build the mincut-tree
    void cdq(int l,int r)
    {
    	if(l==r) return ;
    	w[++k]=dinic(a[l],a[l+1]);
    	g[a[l]].push_back({a[l+1],k});
    	g[a[l+1]].push_back({a[l],k});
    	int tl=0,tr=0;
    	for(int i=l;i<=r;i++)
    	{
    		if(d[a[i]]) b[++tl]=a[i];
    		else c[++tr]=a[i];
    	}
    	for(int i=1;i<=tl;i++) a[l+i-1]=b[i];
    	for(int i=1;i<=tr;i++) a[l+tl+i-1]=c[i];
    	cdq(l,l+tl-1);cdq(l+tl,r);
    }
    //part III : dfs to construct
    void find(int u,int fa,int &mn)
    {
    	for(auto x:g[u]) if(x.v^fa && !vis[x.id])
    	{
    		find(x.v,u,mn);
    		if(w[x.id]<w[mn])
    			mn=x.id,S=u,T=x.v;
    	}
    }
    void dfs(int u)
    {
    	int x=0;find(u,0,x);
    	if(x==0) {a[++t]=u;return ;}
    	vis[x]=1;ans+=w[x];
    	int A=S,B=T;dfs(A);dfs(B);
    }
    signed main()
    {
    	n=read();m=read();
    	for(int i=1;i<=m;i++)
    	{
    		int u=read(),v=read(),c=read();
    		add(u,v,c);add(v,u,c);
    	}
    	for(int i=1;i<=n;i++) a[i]=i;
    	cdq(1,n);w[0]=inf;dfs(1);
    	printf("%d\n",ans);
    	for(int i=1;i<=n;i++)
    		printf("%d ",a[i]);
    	puts("");
    }
    

    Levko and Game

    题目描述

    点此看题

    解法

    关键的 \(\tt observation\) 是:一条边只可能调整到 \(l_i\)\(r_i\),证明考虑调整一条边:

    • 如果没有任何人经过这条边,那么无影响。
    • 如果 \(A\) 经过了这条边,\(B\) 没有经过这条边,那么肯定是尽可能调小,直到调到 \(l_i\)
    • 如果 \(A\) 没有经过这条边,\(B\) 经过了这条边,那么肯定是尽可能调大,直到调到 \(r_i\)
    • 如果 \(A,B\) 都经过了这条边,那么无影响。

    并且通过这个观察我们可以得到大致的想法:对于 \(A\) 有利的边,我们取 \(l_i\),对于 \(B\) 有利的边,我们取 \(r_i\)

    首先考虑判定 \(A\) 是否能赢,一开始我们把边都设置成 \(r_i\),如果跑出来的最短路满足 \(d_1(x)<d_2(x)\),那么我们把这条边改成 \(l_i\),这是基于关键结论:修改边权之后 \(\forall x,d_1(x)<d_2(x)\) 的关系不会改变:

    考虑修改的边是 \((u,v)\),那么一定满足 \(d_1(u)<d_2(u)\),使用反证法,假设此时出现了某个 \(x\)\(d_1(x)<d_2(x)\) 变成了 \(d_1(x)\geq d_2(x)\)

    由于此时 \(d_2(x)\) 变小了,那么 \((u,v)\) 一定在 \(s_2\)\(x\) 的最短路上,所以有 \(d_2(x)=d_2(u)+dis(u,x)\),但是我们又知道 \(d_1(x)\leq d_1(u)+dis(u,x)\),由于 \(d_1(u)<d_2(u)\),说明 \(d_1(x)<d_2(x)\),矛盾。

    考虑判断平局,就是在满足 \(d_1(x)\leq d_2(x)\) 的时候修改,本质上是一样的。

    由于只会修改 \(k\) 次,而每次都需要重新跑 \(\tt dijkstra\),所以时间复杂度 \(O(kn\log n)\)

    总结

    在给定区间确定权值的问题,可能有经典结论是权值只会在端点处取得(其他的例子一时想不起来了

    可以把本题和 Cow and Exercise 对比来理解,虽然都是都是涉及修改边权最短路的问题。后者是要最大化最短路,所以用费用流来划分路径类;但是本题只需要战胜对手即可,所以在分析时侧重两个起点到某点距离的大小关系及其变化。想清楚这点可以帮助你确定大方向,知道你要向什么地方去找性质。

    #include <cstdio>
    #include <cstring>
    #include <iostream>
    #include <queue>
    using namespace std;
    const int M = 20005;
    #define int long long
    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,k,s1,s2,t,tot,d1[M],d2[M],f[M];
    int a[M],b[M],c[M],nw[M];
    struct edge{int v,c,next;}e[M];
    struct node
    {
    	int u,c;
    	bool operator < (const node &b) const
    		{return c>b.c;}
    };
    void add(int u,int v,int c)
    {
    	e[++tot]=edge{v,c,f[u]},f[u]=tot;
    }
    void dijk(int s,int *d)
    {
    	priority_queue<node> q;
    	for(int i=1;i<=n;i++) d[i]=1e18;
    	q.push({s,0});d[s]=0;
    	while(!q.empty())
    	{
    		int u=q.top().u,w=q.top().c;q.pop();
    		if(d[u]<w) continue;
    		for(int i=f[u];i;i=e[i].next)
    		{
    			int v=e[i].v,c=e[i].c;
    			if(d[v]>d[u]+c)
    			{
    				d[v]=d[u]+c;
    				q.push({v,d[v]});
    			}
    		}
    	}
    }
    int solve(int d)
    {
    	int fl=0;
    	do
    	{
    		fl=0;
    		dijk(s1,d1);
    		dijk(s2,d2);
    		for(int i=1;i<=k;i++)
    			if(nw[i]!=c[i] && d1[a[i]]<d2[a[i]]+d)
    			{
    				nw[i]=c[i];
    				add(a[i],b[i],c[i]);
    				fl=1;
    			}
    	}while(fl);
    	return d1[t]<d2[t]+d;
    }
    signed main()
    {
    	n=read();m=read();k=read();
    	s1=read();s2=read();t=read();
    	for(int i=1;i<=m;i++)
    	{
    		int u=read(),v=read(),c=read();
    		add(u,v,c);
    	}
    	for(int i=1;i<=k;i++)
    	{
    		int u=read(),v=read(),l=read(),r=read();
    		add(u,v,r);a[i]=u;b[i]=v;c[i]=l;nw[i]=r;
    	}
    	if(solve(0))
    	{
    		puts("WIN");
    		for(int i=1;i<=k;i++)
    			printf("%lld ",nw[i]);
    		puts("");
    		return 0;
    	}
    	if(solve(1))
    	{
    		puts("DRAW");
    		for(int i=1;i<=k;i++)
    			printf("%lld ",nw[i]);
    		puts("");
    		return 0;
    	}
    	puts("LOSE");
    	return 0;
    }
    
  • 相关阅读:
    linux 进程等待 wait 、 waitpid
    数理逻辑量词的引入
    Android自己定义动态布局 — 多图片上传
    Dynamics CRM 2015/2016 Web API:Unbound Custom Action 和 Bound Custom Action
    iOS 9应用开发教程之显示编辑文本标签文本框
    iOS 9应用开发教程之ios9中实现按钮的响应
    iOS 9应用开发教程之使用代码添加按钮美化按钮
    iOS 9应用开发教程之ios9的视图
    iOS 9应用开发教程之定制应用程序图标以及真机测试
    iOS 9应用开发教程之编辑界面与编写代码
  • 原文地址:https://www.cnblogs.com/C202044zxy/p/16308106.html
Copyright © 2020-2023  润新知