「题解」决斗 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\\),则他此时获胜的概率为
显然,最优策略集合就是选择使得上式最大的 \\(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\\) 的最优策略集,那么我们可以得出:
状态压缩动态规划的过程中,集合 \\(\\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)\\))。
这样的话,缩写式子,得到:
提出 \\((1-p_i)f_{\\operatorname{next}(i)}\\),得到:
移项,得到:
这种左边是两个代解的未知数,右边是已知的值激发我们想到高斯消元。
我们用高斯消元解决即可,由于这部分矩阵比较稀疏,可以做到 \\(\\Theta(n)\\) 的消元。
过程总结
- 我们考虑状压,设 \\(f_{\\mathbb{S},i,j}\\) 表示,当前活着的人的集合为 \\(\\mathbb{S}\\),轮到 \\(i\\) 开枪(还没开呢),\\(j\\) 的胜率是多少。
- 我们直接枚举 \\(k\\),通过 \\(f_{\\mathbb{S}-\\{k\\},\\operatorname{next}(\\mathbb{S}-\\{k\\},i),i}\\) 求出最优策略。
- 而后得到方程
- 我们枚举 \\(\\mathbb{S},j\\),然后用高斯消元消除 \\(f_i,f_{\\operatorname{next}(i)}\\) 构成的环。
冷静的时间复杂度
我们最后来分析时间复杂度。
冷静分析,不难发现,时间复杂度为 \\(\\Theta\\left(\\sum\\limits_{\\mathbb{S}}\\left\\lvert S\\right\\rvert^3\\right)\\)。
不难发现常数因子很小,所以可过。
参考程序
#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的主要内容,如果未能解决你的问题,请参考以下文章
Problem F. Fighting Against Monsters dp
Codeforces Round #617 (Div. 3) D. Fight with Monsters