IOI2020集训队作业-16 (CF605E, AGC031E, AGC028D)

A - CF605E Intergalaxy Trips

Sol

i i i号点到达 n n n需要的期望天数是 f i f_i fi

每个点的策略一定是:钦定一个集合 S S S,等到 S S S里面至少有一个点和它连通的时候,走 S S S中的、与它连通的、到 n n n的期望距离最小的点。显然不在 S S S中的点到 n n n的期望距离要大于 S S S中的点,否则这种钦定 S S S的方法一定是不优秀的。

对于一个点 u u u来说,如果它选的集合是 S S S,那么有
f u = 1 + f u ⋅ ∏ x ∈ S ( 1 − p u , x ) + ∑ x ∈ S f x p u , x ⋅ ∏ y ∈ S , f y < f x ( 1 − p u , y ) f_u = 1 + f_u \cdot \prod_{x\in S} (1-p_{u,x}) + \sum_{x\in S} f_xp_{u,x} \cdot \prod_{y\in S,f_y< f_x}(1-p_{u,y}) fu=1+fuxS(1pu,x)+xSfxpu,xyS,fy<fx(1pu,y)

也就是
f u = 1 + ∑ x ∈ S f x p u , x ⋅ ∏ y ∈ S , f y < f x ( 1 − p u , y ) 1 − ∏ x ∈ S ( 1 − p u , x ) f_u = {1+\sum_{x\in S} f_xp_{u,x} \cdot \prod_{y\in S,f_y< f_x}(1-p_{u,y}) \over1-\prod_{x\in S} (1-p_{u,x})} fu=1xS(1pu,x)1+xSfxpu,xyS,fy<fx(1pu,y)

显然 ∀ x ∈ S , f u > f x \forall x\in S,f_u > f_{x} xS,fu>fx

故而可以模拟dijkstra算法的过程,每次找出 f u f_u fu最小的点,然后用它去更新其它点,看把它加入 S S S中会不会更优秀。

Code

#include <cstdio>
#include <iostream>
#include <algorithm>
#include <cstring>
#define db long double
#define ll long long
using namespace std;
template <class T>
inline void rd(T &x) {
	x=0; char c=getchar(); int f=1;
	while(!isdigit(c)) { if(c=='-') f=-1; c=getchar(); }
	while(isdigit(c)) x=x*10-'0'+c,c=getchar(); x*=f;
}
const int N=1010;
db sum[N],pro[N],dis[N];
int p[N][N],vis[N],n;
int to[N];
void work(int u,int flg=1) {
	vis[u]=1;
	dis[u]=(sum[u]+flg)/(1-pro[u]);
//	cout<<u<<':'<<dis[u]<<endl;
	for(int v=1;v<=n;++v) if(!vis[v]) {
		if(p[v][u]==0) continue;
//		cout<<"upd:"<<v<<endl;
		if(!to[v]) {
			sum[v]=dis[u]*p[v][u]/100.0;
			pro[v]=(100-p[v][u])/100.0;
			to[v]=1;
			continue;
		}
		db tmp1=(sum[v]+1)/(1-pro[v]);
		db tmp2=(sum[v]+pro[v]*p[v][u]/100.0*dis[u]+1)/(1-pro[v]*(100-p[v][u])/100.0);
		if(tmp2<tmp1) sum[v]+=pro[v]*p[v][u]/100.0*dis[u],pro[v]*=(100-p[v][u])/100.0;
	}
}
int main() {
	rd(n);
	for(int i=1;i<=n;++i)
		for(int j=1;j<=n;++j)
			rd(p[i][j]);
	if(n==1) {
		printf("0");
		return 0;
	}
	work(n,0);
	while(1) {
		int p=0; db mi=1e20;
		for(int i=1;i<=n;++i) if(!vis[i]&&to[i]) {
			db tmp=(sum[i]+1)/(1-pro[i]);
			if(!p||tmp<mi) mi=tmp,p=i;
		}
		if(p==1) {
			printf("%.20Lf\n",(sum[1]+1)/(1-pro[1]));
			return 0;
		}
		work(p);
	}
	return 0;
}

B - AGC031E Snuke the Phantom Thief

Sol

枚举偷走的宝物的总数 K K K,然后就可以把所有的限制都转化成横坐标/纵坐标小于等于某个值 a a a的宝物的数量要小于等于/大于等于某个值 b b b,这可以等价地转化成将所有的宝物按照横坐标/纵坐标排序后,第 b + 1 / b b+1/b b+1/b个宝物的横坐标/纵坐标的范围要大于/小于等于某个值 a a a

也就是说,对这些宝物的横坐标第 i i i大的我们限制了它的取值区间;对于纵坐标第 i i i大的我们也限制了它的取值区间。这些区间的左右端点显然会随着 i i i单调移动。

分别建两排点,一个从源点经过一些其它点连向它,表示每一个横坐标值;另一排点表示每一个纵坐标值,经过它可以到达汇点。从第一排点的 x i x_i xi向第二排点的 y i y_i yi连流量为 1 1 1费用为 v i v_i vi的边,跑最大费用最大流。

Code

#include <cstdio>
#include <iostream>
#include <algorithm>
#include <cstring>
#include <queue>
#include <vector>
#define PB push_back
#define ll long long
using namespace std;
template <class T>
inline void rd(T &x) {
	x=0; char c=getchar(); int f=1;
	while(!isdigit(c)) { if(c=='-') f=-1; c=getchar(); }
	while(isdigit(c)) x=x*10-'0'+c,c=getchar(); x*=f;
}
template <class T> inline void cmin(T &x,T y) { if(y<x) x=y; }
template <class T> inline void cmax(T &x,T y) { if(x<y) x=y; }
const ll inf=1e16;
int S,T,ncnt;
namespace Flow {
	const int N=410;
	int head[N],cur[N];
	ll dis[N];
	struct ed { int to,next,f; ll w; };
	vector<ed> e;
	void init() { memset(head,-1,sizeof(head)),e.clear(); }
	void ad(int x,int y,int f,ll w) {
		e.PB((ed){y,head[x],f,w}); head[x]=e.size()-1;
		e.PB((ed){x,head[y],0,-w}); head[y]=e.size()-1;
	}
	queue<int> que;
	int vis[N];
	bool bfs() {
		for(int i=1;i<=ncnt;++i) dis[i]=-inf,cur[i]=head[i];
		dis[S]=0,vis[S]=1,que.push(S);
		while(!que.empty()) {
			int u=que.front(); que.pop(),vis[u]=0;
			for(int k=head[u];~k;k=e[k].next) if(e[k].f) {
				int v=e[k].to;
				if(dis[v]<dis[u]+e[k].w) {
					dis[v]=dis[u]+e[k].w;
					if(!vis[v]) vis[v]=1,que.push(v);
				}
			}
		}
		return dis[T]>-inf;
	}
	int dfs(int u,int f) {
		if(u==T||!f) return f; int ret=0,tmp;
		vis[u]=1;
		for(int &k=cur[u];~k;k=e[k].next) if(e[k].f) {
			int v=e[k].to;
			if(!vis[v]&&dis[v]==dis[u]+e[k].w&&(tmp=dfs(v,min(f,e[k].f)))) {
				e[k].f-=tmp,e[k^1].f+=tmp;
				f-=tmp,ret+=tmp;
				if(!f) break;
			}
		}
		vis[u]=0;
		return ret;
	}
	ll work() { ll ans=0; while(bfs()) ans+=dfs(S,1e9)*dis[T]; return ans; }
}
const int N=110;
const int M=410;
char ss[M],str[10];
int a[M],b[M];
int xi[N],yi[N],n,m;
int lbx[N],rbx[N],lby[N],rby[N];
int idx[N],idy[N];
ll vi[N];
ll sol(int K) {
	for(int i=1;i<=K;++i)
		lbx[i]=lby[i]=1,
		rbx[i]=rby[i]=100;
	for(int i=1;i<=m;++i) {
		if(b[i]>K) continue;
		if(ss[i]=='L') cmax(lbx[b[i]+1],a[i]+1);
		else if(ss[i]=='R') cmin(rbx[K-b[i]],a[i]-1);
		else if(ss[i]=='D') cmax(lby[b[i]+1],a[i]+1);
		else cmin(rby[K-b[i]],a[i]-1);
	}
	for(int i=2;i<=K;++i)
		cmax(lbx[i],lbx[i-1]),
		cmax(lby[i],lby[i-1]);
	for(int i=K-1;i>=1;--i)
		cmin(rbx[i],rbx[i+1]),
		cmin(rby[i],rby[i+1]);
	
	for(int i=1;i<=K;++i) if(lbx[i]>rbx[i]||lby[i]>rby[i]) return 0;
	ncnt=0; Flow::init();
	S=++ncnt,T=++ncnt;
	for(int i=1;i<=100;++i) idx[i]=++ncnt,idy[i]=++ncnt;
	for(int i=1;i<=K;++i) {
		int u=++ncnt;
		Flow::ad(S,u,1,0);
		for(int j=lbx[i];j<=rbx[i];++j)
			Flow::ad(u,idx[j],1,0);
	}
	for(int i=1;i<=K;++i) {
		int u=++ncnt;
		Flow::ad(u,T,1,0);
		for(int j=lby[i];j<=rby[i];++j)
			Flow::ad(idy[j],u,1,0);
	}
	for(int i=1;i<=n;++i)
		Flow::ad(idx[xi[i]],idy[yi[i]],1,vi[i]);
	return Flow::work();
}
int main() {
	rd(n);
	for(int i=1;i<=n;++i) rd(xi[i]),rd(yi[i]),rd(vi[i]);
	rd(m);
	for(int i=1;i<=m;++i) {
		scanf("%s",str);
		ss[i]=str[0];
		rd(a[i]),rd(b[i]);
	}
	ll ans=0;
	for(int i=1;i<=n;++i) ans=max(ans,sol(i));
	printf("%lld",ans);
	return 0;
}

C - AGC028D Chords

Sol

f i , j f_{i,j} fi,j表示不考虑编号不在 [ i , j ] [i,j] [i,j]中的点时,所有配对方案中包含(下标最小的点是 i i i,下标最大的点是 j j j的连通块)的数量。设 g i , j g_{i,j} gi,j表示将 [ i , j ] [i,j] [i,j]区间中的点配对并且没有点连向区间以外的点的方案数。

那么有
A n s = ∑ f i , j g j + 1 , i − 1 f i , j = g i , j − ∑ k = i + 1 j − 1 f i , k g k + 1 , j Ans = \sum f_{i,j} g_{j+1,i-1} \\ f_{i,j} = g_{i,j} - \sum_{k=i+1}^{j-1} f_{i,k}g_{k+1,j} Ans=fi,jgj+1,i1fi,j=gi,jk=i+1j1fi,kgk+1,j

Code

#include <cstdio>
#include <iostream>
#include <algorithm>
#include <cstring>
#define ll long long
using namespace std;
template <class T>
inline void rd(T &x) {
	x=0; char c=getchar(); int f=1;
	while(!isdigit(c)) { if(c=='-') f=-1; c=getchar(); }
	while(isdigit(c)) x=x*10-'0'+c,c=getchar(); x*=f;
}
const int N=610,mod=1e9+7;
inline void Add(int &x,int y) { x+=y; if(x>=mod) x-=mod; }
inline void Dec(int &x,int y) { x-=y; if(x<0) x+=mod; }
int f[N][N],g[N][N],h[N][N],fac[N];
int A[N],B[N];
int n,m;
bool In(int x,int l,int r) { return x>=l&&x<=r; }
int main() {
	rd(n),rd(m);
	for(int i=1;i<=m;++i) rd(A[i]),rd(B[i]);
	fac[0]=1;
	for(int i=2;i<=2*n;i+=2) fac[i]=fac[i-2]*(ll)(i-1)%mod;
	for(int i=1;i<=2*n;++i)
		for(int j=i+1;j<=2*n;j+=2) {
			int tot=0,flg=1;
			for(int k=1;k<=m;++k) {
				flg&=In(A[k],i,j)==In(B[k],i,j);
				tot+=In(A[k],i,j)+In(B[k],i,j);
			}
			if(!flg) continue;
			g[i][j]=fac[j-i+1-tot];
			h[i][j]=fac[2*n-m*2-(j-i+1-tot)];
		}
	int ans=0;
	for(int len=2;len<=2*n;len+=2)
		for(int i=1;i+len-1<=2*n;++i) {
			int j=i+len-1;
			f[i][j]=g[i][j];
			for(int k=i+1;k<j;k+=2)
				Dec(f[i][j],f[i][k]*(ll)g[k+1][j]%mod);
			Add(ans,f[i][j]*(ll)h[i][j]%mod);
		}
	printf("%d",ans);
	return 0;
}
j-i+1-tot)];
		}
	int ans=0;
	for(int len=2;len<=2*n;len+=2)
		for(int i=1;i+len-1<=2*n;++i) {
			int j=i+len-1;
			f[i][j]=g[i][j];
			for(int k=i+1;k<j;k+=2)
				Dec(f[i][j],f[i][k]*(ll)g[k+1][j]%mod);
			Add(ans,f[i][j]*(ll)h[i][j]%mod);
		}
	printf("%d",ans);
	return 0;
}
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值