「题解」决斗 fight

Posted Lu_Anlai

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了「题解」决斗 fight相关的知识,希望对你有一定的参考价值。

本文将同步发布于:

题目

题意简述

一场决斗比赛,有 \\(n\\) 个人围成一个圈,逆时针编号为 \\(1,2,\\cdots,n\\),每个人开枪有一个命中率 \\(\\frac{p_i}{100}\\)

首先 \\(1\\) 号选手开枪,然后逆时针下一个还活着的人继续,如此循环,直到只剩下一个人,他就赢得了这场决斗比赛。

每个人都知道所有人的命中率,每个人都会选择最优策略(成为赢家)开枪,被轮到时不能不开枪或者故意不命中,若有多个最优策略则随机选择。

求出每个人的胜率 \\(\\frac{\\texttt{ans}_i}{100}\\),输出 \\(\\texttt{ans}_i\\),保留 \\(2\\) 位小数。

\\(n\\leq 13\\)

题解

状态设计

考虑设 \\(f_{\\mathbb{S},i,j}\\) 表示,当前活着的人的集合为 \\(\\mathbb{S}\\),轮到 \\(i\\) 开枪(还没开呢),\\(j\\) 的胜率是多少,则答案为 \\(\\texttt{ans}_i=100f_{\\mathbb{U},1,i}\\),初始状态为 \\(f_{\\{i\\},i,i}=1,f_{\\{i\\},i,j(j\\neq i)}=0\\)

朴素的最优策略

设活着的人的集合为 \\(\\mathbb{S}\\) 时,\\(i\\) 的下一个为 \\(\\operatorname{next}(\\mathbb{S},i)\\)

对于状态 \\((\\mathbb{S},i)\\),我们考虑求出 \\(i\\) 的最优策略。

此时 \\(i\\) 获胜的概率为 \\(f_{\\mathbb{S},i,i}\\),假设他瞄准 \\(k\\),则他此时获胜的概率为

\\[p_if_{\\mathbb{S}-\\{k\\},\\operatorname{next}(\\mathbb{S}-\\{k\\},i),i}+(1-p_i)f_{\\mathbb{S},\\operatorname{next}(\\mathbb{S},i),i} \\]

显然,最优策略集合就是选择使得上式最大的 \\(k\\) 构成的集合。

观察到 \\(p_i\\)\\((1-p_i)f_{\\mathbb{S},\\operatorname{next}(\\mathbb{S},i),i}\\)\\(k\\) 无关,所以最优策略集合是使得 \\(f_{\\mathbb{S}-\\{k\\},\\operatorname{next}(\\mathbb{S}-\\{k\\},i),i}\\) 的值最大的 \\(k\\) 构成的集合。

\\(f_{\\mathbb{S}-\\{k\\},\\operatorname{next}(\\mathbb{S}-\\{k\\},i),i}\\) 已知,我们可以直接枚举 \\(k\\) 求出最优策略。

状态转移方程

\\(\\mathbb{K}_{\\mathbb{S},i}\\) 为状态 \\(\\mathbb{S}\\)\\(i\\) 的最优策略集,那么我们可以得出:

\\[f_{\\mathbb{S},i,j}=\\frac{1}{\\left\\lvert\\mathbb{K}_{\\mathbb{S},i}\\right\\rvert}\\sum_{k\\in\\mathbb{K}_{\\mathbb{S},i}}\\left(p_if_{\\mathbb{S}-\\{k\\},\\operatorname{next}(\\mathbb{S}-\\{k\\},i),j}+(1-p_i)f_{\\mathbb{S},\\operatorname{next}(\\mathbb{S},i),j}\\right) \\]

状态压缩动态规划的过程中,集合 \\(\\mathbb{S}\\) 是从小到大枚举的,我们不需要考虑 \\(f_{\\mathbb{S}-\\{k\\},\\operatorname{next}(\\mathbb{S}-\\{k\\},i),j}\\) 未知的问题,然而 \\(f_{\\mathbb{S},\\operatorname{next}(\\mathbb{S},i),j}\\) 的转移却构成了一个环形结构,这警示我们需要特殊处理动态规划中的环形结构。

高斯消元

我们不妨考虑固定 \\(j,\\mathbb{S}\\),专注于 \\(i,\\operatorname{next}(\\mathbb{S}-\\{k\\},i)\\)(下面记为 \\(\\operatorname{next}(-\\{k\\},i)\\))。

这样的话,缩写式子,得到:

\\[f_{i}=\\frac{1}{\\left\\lvert\\mathbb{K}_{i}\\right\\rvert}\\sum_{k\\in\\mathbb{K}_{i}}\\left(p_if_{-\\{k\\},\\operatorname{next}(-\\{k\\},i)}+(1-p_i)f_{\\operatorname{next}(i)}\\right) \\]

提出 \\((1-p_i)f_{\\operatorname{next}(i)}\\),得到:

\\[f_{i}=(1-p_i)f_{\\operatorname{next}(i)}+\\frac{1}{\\left\\lvert\\mathbb{K}_{i}\\right\\rvert}\\sum_{k\\in\\mathbb{K}_{i}}\\left(p_if_{-\\{k\\},\\operatorname{next}(-\\{k\\},i)}\\right) \\]

移项,得到:

\\[f_{i}-(1-p_i)f_{\\operatorname{next}(i)}=\\frac{1}{\\left\\lvert\\mathbb{K}_{i}\\right\\rvert}\\sum_{k\\in\\mathbb{K}_{i}}\\left(p_if_{-\\{k\\},\\operatorname{next}(-\\{k\\},i)}\\right) \\]

这种左边是两个代解的未知数,右边是已知的值激发我们想到高斯消元。

我们用高斯消元解决即可,由于这部分矩阵比较稀疏,可以做到 \\(\\Theta(n)\\) 的消元。

过程总结

  • 我们考虑状压,设 \\(f_{\\mathbb{S},i,j}\\) 表示,当前活着的人的集合为 \\(\\mathbb{S}\\),轮到 \\(i\\) 开枪(还没开呢),\\(j\\) 的胜率是多少。
  • 我们直接枚举 \\(k\\),通过 \\(f_{\\mathbb{S}-\\{k\\},\\operatorname{next}(\\mathbb{S}-\\{k\\},i),i}\\) 求出最优策略。
  • 而后得到方程

\\[f_{\\mathbb{S},i,j}=\\frac{1}{\\left\\lvert\\mathbb{K}_{\\mathbb{S},i}\\right\\rvert}\\sum_{k\\in\\mathbb{K}_{\\mathbb{S},i}}\\left(p_if_{\\mathbb{S}-\\{k\\},\\operatorname{next}(\\mathbb{S}-\\{k\\},i),j}+(1-p_i)f_{\\mathbb{S},\\operatorname{next}(\\mathbb{S},i),j}\\right) \\]

  • 我们枚举 \\(\\mathbb{S},j\\),然后用高斯消元消除 \\(f_i,f_{\\operatorname{next}(i)}\\) 构成的环。

冷静的时间复杂度

我们最后来分析时间复杂度。

冷静分析,不难发现,时间复杂度为 \\(\\Theta\\left(\\sum\\limits_{\\mathbb{S}}\\left\\lvert S\\right\\rvert^3\\right)\\)

\\[\\begin{aligned} \\sum\\limits_{\\mathbb{S}}\\left\\lvert S\\right\\rvert^3&=\\sum_{i=0}^n\\binom{n}{i}i^3\\\\ &=[x=1]\\sum_{i=0}^n\\binom{n}{i}i^3x^i\\\\ &=[x=1]\'\\left(\\sum_{i=0}^n\\binom{n}{i}i^2x^{i+1}\\right)\\\\ &=[x=1]\'\\left(\\sum_{i=0}^n\\binom{n}{i}i^2x^{i}\\right)\\\\ &=[x=1]\'\'\\left(\\sum_{i=0}^n\\binom{n}{i}ix^{i+1}\\right)\\\\ &=[x=1]\'\'\\left(\\sum_{i=0}^n\\binom{n}{i}ix^{i}\\right)\\\\ &=[x=1]\'\'\'\\left(\\sum_{i=0}^n\\binom{n}{i}x^{i+1}\\right)\\\\ &=[x=1]\'\'\'\\left(\\sum_{i=0}^n\\binom{n}{i}x^{i}\\right)\\\\ &=[x=1]\'\'\'(x+1)^n\\\\ &=[x=1]n(n-1)(n-2)(x+1)^{n-3}\\\\ &=n(n-1)(n-2)2^{n-3}\\\\ \\end{aligned} \\]

不难发现常数因子很小,所以可过。

参考程序

#pragma GCC optimize("Ofast")
#include<bits/stdc++.h>
using namespace std;
#define reg register
typedef long long ll;

bool st;

#define getchar() (p1==p2&&(p2=(p1=buf)+fread(buf,1,1<<21,stdin),p1==p2)?EOF:*p1++)
static char buf[1<<21],*p1=buf,*p2=buf;
inline int read(void){
	reg char ch=getchar();
	reg int res=0;
	while(!isdigit(ch)) ch=getchar();
	while(isdigit(ch)) res=10*res+(ch^\'0\'),ch=getchar();
	return res;
}

inline int lowbit(reg int x){
	return x&(-x);
}

inline int lg(reg int x){
	switch(x){
		case 1<<0:return 0;
		case 1<<1:return 1;
		case 1<<2:return 2;
		case 1<<3:return 3;
		case 1<<4:return 4;
		case 1<<5:return 5;
		case 1<<6:return 6;
		case 1<<7:return 7;
		case 1<<8:return 8;
		case 1<<9:return 9;
		case 1<<10:return 10;
		case 1<<11:return 11;
		case 1<<12:return 12;
		case 1<<13:return 13;
	}
	assert(false);
	return -1;
}

const int MAXN=13;
const double eps=1e-8;

inline void Gauss(reg int n,reg double G[MAXN][MAXN+1]){
	for(reg int i=n-1;i>=1;--i){
		reg double d=G[i-1][i]/G[i][i];
		G[i-1][0]-=d*G[i][0],G[i-1][i]-=d*G[i][i],G[i-1][n]-=d*G[i][n];
	}
	G[0][n]/=G[0][0],G[0][0]=1;
	for(reg int i=1;i<n;++i){
		G[i][n]-=G[i][0]*G[0][n];
		G[i][0]=0;
	}
	return;
}

int n;
double p[MAXN];
int nex[1<<MAXN][MAXN];
double f[1<<MAXN][MAXN][MAXN];
double G[MAXN][MAXN+1];
double ans[MAXN];

inline double getVal(reg int S,reg int i,reg int del){
	return f[S^(1<<del)][nex[S^(1<<del)][i]][i];
}

bool ed;

int main(void){
	for(reg int S=0;S<(1<<MAXN);++S){
		reg int tot=0;
		static int V[MAXN+1];
		for(reg int T=S;T;T^=lowbit(T))
			V[tot++]=lg(lowbit(T));
		V[tot]=V[0];
		for(reg int i=0;i<tot;++i)
			nex[S][V[i]]=V[i+1];
	}
	reg int t=read();
	while(t--){
		n=read();
		for(reg int i=0;i<n;++i)
			p[i]=read()/100.0;
		for(reg int i=0;i<n;++i)
			f[1<<i][i][i]=1;
		for(reg int S=0;S<(1<<n);++S){
			reg int siz=__builtin_popcount(S);
			if(siz<=1)
				continue;
			vector<int> V;
			V.reserve(siz);
			for(reg int Ts=S;Ts;Ts^=lowbit(Ts))
				V.push_back(lg(lowbit(Ts)));
			vector<int> ptr[siz];
			for(reg int i=0;i<siz;++i)
				for(int j=0;j<siz;++j)
					if(i!=j){
						if(ptr[i].empty()||getVal(S,V[i],V[j])-getVal(S,V[i],V[ptr[i].front()])>eps)
							ptr[i]={j};
						else if(fabs(getVal(S,V[i],V[j])-getVal(S,V[i],V[ptr[i].front()]))<eps)
							ptr[i].push_back(j);
					}
			for(int u:V){
				for(reg int i=0;i<siz;++i){
					reg int v=V[i];
					G[i][i]=1,G[i][(i+1)%siz]=-(1-p[v]);
					for(int del:ptr[i]){
						reg int nS=S^(1<<V[del]),nxt=nex[nS][v];
						G[i][siz]+=f[nS][nxt][u];
					}
					G[i][siz]=G[i][siz]*p[v]/ptr[i].size();
				}
				Gauss(siz,G);
				for(reg int i=0;i<siz;++i)
					f[S][V[i]][u]=G[i][siz],G[i][siz]=0;
			}
		}
		for(reg int i=0;i<n;++i)
			ans[i]=fabs(100.0*f[(1<<n)-1][0][i]);
		for(reg int i=0;i<n;++i)
			printf("%.2lf%c",ans[i],i==n-1?\'\\n\':\' \');
	}
	fprintf(stderr,"%.3lf s\\n",1.0*clock()/CLOCKS_PER_SEC);
	fprintf(stderr,"%.3lf MiB\\n",(&ed-&st)/1048576.0);
	return 0;
}

以上是关于「题解」决斗 fight的主要内容,如果未能解决你的问题,请参考以下文章

Hdoj 2109.Fighting for HDU 题解

Problem F. Fighting Against Monsters dp

Codeforces Round #617 (Div. 3) D. Fight with Monsters

Codeforces 954D Fight Against Traffic(BFS 最短路)

NYOJ 110 剑客决斗

剑客决斗