洛谷P6054:开门大吉

Description \text{Description} Description

P6054 开门大吉

n n n 位选手去参加节目“开门大吉”。共有 m m m 套题,每套题包含 p p p 个题目,第 i i i 位选手答对第 j j j 套题中第 k k k 道的概率为 f i , j , k f_{i,j,k} fi,j,k
若一位选手答对第 i i i 题,会在已得到奖励的基础上,再得到 c i c_i ci 元奖励。选手总是从第一道开始,按顺序答题的。
同时,为了防止过多的选手做同一套题,还有 y y y 条形如“第 i i i 位选手的套题编号必须至少比第 j j j 位的大 k k k”的限制。
你需要给每一位选手分配一套题(不同选手可以相同),使得所有人的期望奖励和最小。

Solution \text{Solution} Solution

首先,我们可以简单的算出 i i i 选手选套题 j j j 的期望价值 v i , j v_{i,j} vi,j
关键就在于如何表示限制。
建出 n n n 条长度为 m + 1 m+1 m+1 的链,设链上的点为 p 1... n , 1.. m + 1 p_{1...n,1..m+1} p1...n,1..m+1,连边 ( S , p i , 1 , I N F ) , ( p i , m + 1 , T , I N F ) , ( p i , j , p i , j + 1 , v i , j ) (S,p_{i,1},INF),(p_{i,m+1},T,INF),(p_{i,j},p_{i,j+1},v_{i,j}) (S,pi,1,INF),(pi,m+1,T,INF),(pi,j,pi,j+1,vi,j)。断掉 ( p i , j , p i , j + 1 , v i , j ) (p_{i,j},p_{i,j+1},v_{i,j}) (pi,j,pi,j+1,vi,j) 的边即表示令 i i i 选手做 j j j 号题。
这样建图之后限制就容易表示了,设 i i i 选手比 j j j 选手题目编号至少大 k k k,就对于所有合法的下标 x x x,均连边 ( p j , x , p i , x + k , I N F ) (p_{j,x},p_{i,x+k},INF) (pj,x,pi,x+k,INF) 即可。

但是这样是无法通过本题的。主流题解都说还需要加上 ( p i , j + 1 , p i , j , I N F ) (p_{i,j+1},p_{i,j},INF) (pi,j+1,pi,j,INF) 的边,以实现限制的传递性(或者说保证每条链只断一条边),但我认为这并不是问题的本质,这个连边方法并不是必须的,实践结果也证明了这一点。
我们来看看原来的做法到底为什么出错。
原来的连边方式其实是很健全的,它本身已经有了传递性,如:
请添加图片描述红线和绿线限制已经可以进行传递,形成黄色的限制。
那么为什么我们难以通过一些测试点呢?
因为我们的限制没有加全!
题解和我一开始的写法对于限制的加边一般都是写成类似下边的形式:

for(int j=max(1,1-k);j<=m+1&&j+k<=m+1;j++) add(id[y][j],id[x][j+k],inf);

但正确的写法应该是:

for(int j=max(1,1-k);j<=m+1;j++) add(id[y][j],id[x][min(j+k,m+1)],inf);

这两个具象一下就是下面两张图的差别:
请添加图片描述请添加图片描述
注意到,在第一种加法中,后面的两个点没有被限制,成了“法外之地”,这也是为什么加完反向边可以把这个问题修正的原因。
即使可以这么修正,但从问题的本质来看还是不够优美的。因此,建议直接把加限制边的方式修改,而不是进行侧面的修正。

Code \text{Code} Code

#include<bits/stdc++.h>
using namespace std;
#define ll long long
#define ull unsigned long long
#define debug(...) fprintf(stderr,__VA_ARGS__)
#define ok debug("OK\n")
inline ll read(){
	ll x(0),f(1);char c=getchar();
	while(!isdigit(c)){if(c=='-') f=-1;c=getchar();}
	while(isdigit(c)){x=(x<<1)+(x<<3)+c-'0';c=getchar();}
	return x*f;
}
const int N=2e5+100;
const int M=2e6+100;
const double inf=1e11;
const double eps=1e-6;

int n,m,num,y;
int flag;

int tot,s,t;
struct node{
	int to,nxt;
	double cap;
}p[M<<1];
int fi[N],cur[N],cnt;
inline void addline(int x,int y,double c){
	p[++cnt]=(node){y,fi[x],c};fi[x]=cnt;
}
inline void add(int x,int y,double c){
	//printf("%d->%d cap=%.2lf\n\n",x,y,c);
	addline(x,y,c);addline(y,x,0);
}
int bel[N];
queue<int>que;
int bfs(){
	fill(bel,bel+1+tot,0);
	bel[s]=1;que.push(s);
	while(!que.empty()){
		int now=que.front();que.pop();
		for(int i=cur[now]=fi[now];~i;i=p[i].nxt){
			int to=p[i].to;
			if(p[i].cap<eps||bel[to]) continue;
			bel[to]=bel[now]+1;
			que.push(to);
		}
	}
	return bel[t];
}
double dfs(int x,double lim){
	if(x==t||lim<eps) return lim;
	double res(0);
	for(int &i=cur[x];~i;i=p[i].nxt){
		int to=p[i].to;
		if(bel[to]!=bel[x]+1) continue;
		double add=dfs(to,min(lim,p[i].cap));
		res+=add;lim-=add;
		p[i].cap-=add;p[i^1].cap+=add;
		//printf("x=%d to=%d add=%lf\n",x,to,add);
		if(lim<eps) break;
	}
	if(res<eps) bel[x]=-1;
	return res;
}
void dinic(){
	double flow(0),tmp(0);
	while(bfs()){
		while((tmp=dfs(s,inf))>eps) flow+=tmp;
		if(flow>=inf){
			puts("-1");return;
		}
	}
	printf("%lf\n",flow);
	return;
}
int id[90][90];
int c[90];
double ans;
double f[90][90][90],pp[90][90][90],val[90][90];
void init(){
	ans=0;
	tot=0;
	for(int i=1;i<=n;i++){
		for(int j=1;j<=m+1;j++) id[i][j]=++tot;
	}
	s=++tot;t=++tot;
	memset(fi,-1,sizeof(fi));cnt=-1;
}
void calc(){
	for(int i=1;i<=n;i++){
		for(int j=1;j<=m;j++){
			pp[i][j][0]=1;val[i][j]=0;
			for(int k=1;k<=num;k++) pp[i][j][k]=pp[i][j][k-1]*f[i][j][k];
			for(int k=1;k<=num;k++){
				pp[i][j][k]=pp[i][j][k]*(1-f[i][j][k+1]);
				val[i][j]+=pp[i][j][k]*c[k];
			}
			//printf("i=%d j=%d val=%lf\n",i,j,val[i][j]);
		}
	}
}

void work(){
	n=read();m=read();num=read();y=read();
	init();
	for(int i=1;i<=num;i++) c[i]=read()+c[i-1];
	for(int j=1;j<=m;j++){
		for(int i=1;i<=n;i++){
			for(int k=1;k<=num;k++) scanf("%lf",&f[i][j][k]);
		}
	}
	calc();
	for(int i=1;i<=n;i++){
		add(s,id[i][1],inf);
		add(id[i][m+1],t,inf);
		for(int j=1;j<=m;j++){
			add(id[i][j],id[i][j+1],val[i][j]);//add(id[i][j+1],id[i][j],inf);
		}
	}
	for(int i=1;i<=y;i++){
		int x=read(),y=read(),k=read();
		for(int j=max(1,1-k);j<=m+1;j++) add(id[y][j],id[x][min(j+k,m+1)],inf);
	}
	dinic();
	
	return;
}
signed main(){
	#ifndef ONLINE_JUDGE
	//freopen("a.in","r",stdin);
	//freopen("a.out","w",stdout);
	#endif
	int T=read();
	while(T--) work();
	return 0;
}
/*
1
3 2 4 1
10 10 10 20
0.3 0.5 0.7 0.4
0.2 0.6 0.2 0.2
0.7 0.1 0.8 0.2
0.5 0.5 0.5 0.5
0.2 0.5 0.3 0.6
0.3 0.5 0.4 0.1
2 3 1
*/
 
  • 6
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值