题解 舰队游戏

传送门

\(f_{i, j}\) 为在位置 \(i\)\(j\) 点血
那么有

\[f_{i, j}=\min(f_{1, h}+h-j, f_{v, j-d_v}) \]

带着这个 min 完全没法消元什么的
那怎么办呢?
考虑变元只有 \(f_{1, h}\),设它为 \(x\)
那么所有转移路径的值都可以用 \(x\) 表示成 \(kx+b\) 的形式
是在所有转移路径中最小值,所以可以建立凸包
那么这个凸包与 \(y=x\) 的交点坐标即为答案
那么可以二分这个交点
check 按逆拓扑序转移即可

点击查看代码
#include <bits/stdc++.h>
using namespace std;
#define INF 0x3f3f3f3f
#define N 110
#define fir first
#define sec second
#define pb push_back
#define ll long long
//#define int long long

char buf[1<<21], *p1=buf, *p2=buf;
#define getchar() (p1==p2&&(p2=(p1=buf)+fread(buf, 1, 1<<21, stdin)), p1==p2?EOF:*p1++)
inline int read() {
	int ans=0, f=1; char c=getchar();
	while (!isdigit(c)) {if (c=='-') f=-f; c=getchar();}
	while (isdigit(c)) {ans=(ans<<3)+(ans<<1)+(c^48); c=getchar();}
	return ans*f;
}

int n, m, h;
bool vis[N];
int deg[N], d[N], dsu[N];
pair<int, int> e[N*N];
vector<int> to[N];
inline int find(int p) {return dsu[p]==p?p:dsu[p]=find(dsu[p]);}

// namespace task2{
// 	void solve() {
// 		int sum=0;
// 		for (int i=1; i<=n; ++i) sum+=d[i];
// 		if (sum>=h) puts("-1");
// 		else printf("%.10lf\n", double(n-1));
// 	}
// }

namespace task1{
	const double eps=1e-10;
	struct matrix{
		int n, m;
		double a[110][110];
		matrix(){n=m=0; memset(a, 0, sizeof(a));}
		matrix(int x, int y) {n=x; m=y; memset(a, 0, sizeof(a));}
		void resize(int x, int y) {n=x; m=y; memset(a, 0, sizeof(a));}
		inline double* operator [] (int t) {return a[t];}
		void put() {for (int i=1; i<=n; ++i) {for (int j=1; j<=m; ++j) cout<<setw(3)<<a[i][j]<<' '; cout<<endl;}cout<<endl;}
		double gauss() {
			for (int i=1; i<=n; ++i) {
				int r=i;
				for (int j=i+1; j<=n; ++j) if (fabs(a[j][i])>fabs(a[r][i])) r=j;
				swap(a[r], a[i]);
				for (int j=1; j<=n; ++j) if (i!=j) {
					double t=a[j][i]/a[i][i];
					for (int k=i; k<=m; ++k) a[j][k]-=t*a[i][k];
				}
			}
			return a[1][n+1]/a[1][1];
		}
	}mat;
	void solve() {
		if (h<=d[n]) {puts("-1"); return ;}
		for (int i=1; i<=n; ++i) dsu[i]=i;
		for (int i=1; i<=m; ++i) dsu[find(e[i].fir)]=find(e[i].sec);
		if (find(1)!=find(n)) {puts("-1"); return ;}
		mat.resize(n, n+1);
		for (int i=1; i<n; ++i) {
			mat[i][i]=-1;
			if (deg[i]) {
				if (i!=n) mat[i][n+1]=-1;
				double t=1.0/deg[i];
				for (auto v:to[i]) mat[i][v]=t;
			}
			else mat[i][1]=1;
		}
		mat[n][n]=-1;
		// mat.put();
		printf("%.10lf\n", mat.gauss());
		// mat.put();
	}
}

namespace task{
	int hp[N];
	bool able[N];
	double f[N][N];
	void solve() {
		hp[1]=h; able[n]=1;
		for (int i=2; i<=n; ++i) hp[i]=-INF;
		for (int i=1; i<=n; ++i) for (auto v:to[i]) hp[v]=max(hp[v], hp[i]-d[v]);
		for (int i=n-1; i; --i) {
			for (auto v:to[i]) if (able[v]) able[i]=1;
			for (auto v:to[i]) if (hp[i]<=d[v]) able[i]=0;
		}
		// cout<<"able: "; for (int i=1; i<=n; ++i) cout<<able[i]<<' '; cout<<endl;
		if (!able[1]) {puts("-1"); return ;}
		double l=1, r=1000001, mid;
		for (int i=1; i<=60; ++i) {
			mid=(l+r)/2;
			// cout<<"mid: "<<mid<<endl;
			memset(f, 0x42, sizeof(f));
			for (int j=1; j<=h; ++j) f[n][j]=0;
			for (int j=n-1; j; --j) {
				for (int k=1; k<=h; ++k) {
					bool any=0; double tem=0;
					for (auto v:to[j]) {
						if (d[v]>=k) any=1;
						tem+=f[v][k-d[v]];
					}
					if (deg[j]) tem/=deg[j];
					if (j!=1) f[j][k]=min(f[j][k], h-k+mid);
					if (deg[j]&&!any) f[j][k]=min(f[j][k], tem+1);
				}
			}
			// cout<<"f: "<<f[1][h]<<endl;
			if (f[1][h]>mid) l=mid;
			else r=mid;
			// cout<<"---f---"<<endl;
			// for (int i=1; i<=n; ++i) {cout<<i<<": "; for (int j=1; j<=h; ++j) cout<<setw(15)<<f[i][j]<<' '; cout<<endl;}
		}
		if (l>1000000) puts("-1");
		else printf("%.10lf\n", l);
	}
}

signed main()
{
	freopen("kancolle.in", "r", stdin);
	freopen("kancolle.out", "w", stdout);

	n=read(); m=read(); h=read();
	bool all_diff=1;
	for (int i=1,u,v; i<=m; ++i) {
		u=read(); v=read();
		e[i]={u, v};
		++deg[u]; to[u].pb(v);
		if (vis[v]) all_diff=0;
		else vis[v]=1;
	}
	for (int i=1; i<=n; ++i) d[i]=read();
	// if (m==n-1 && all_diff) task2::solve();
	task::solve();

	return 0;
}
posted @ 2022-04-02 19:13  Administrator-09  阅读(2)  评论(0编辑  收藏  举报