20201014下午
虚树真是太潮啦!
T1 | T2 | T3 | |
---|---|---|---|
预测 | 100 | 40 | 100 |
一测 | 100 | 0 | 68 |
T1:
究极缝合,
e
x
g
c
d
exgcd
exgcd+
b
s
g
s
bsgs
bsgs,不过是板子,打就完事了。
T2:
虚树不会啊,暴力还打挂了。
然后就去学习了一下虚树,可以在树形
d
p
dp
dp中将有多次询问但总询问小于一定点数中将
n
m
nm
nm复杂度降至
m
l
o
g
k
mlogk
mlogk(
k
k
k指总询问点数)。基本思想就是将树简化,建立一颗虚树只加入关键点与关键点的
l
c
a
lca
lca,用这些模拟整棵树进行
d
p
dp
dp,具体实现通过栈来维护。
当前点
u
u
u,父边
w
w
w,本题显然有
d
p
dp
dp为当
u
u
u为资源点时
f
u
=
v
w
f_u=v_w
fu=vw,否则
f
u
=
min
(
v
w
,
∑
min
(
w
′
,
f
v
)
)
f_u=\min(v_w,\sum\min(w',f_v))
fu=min(vw,∑min(w′,fv))。那便记录
m
n
u
mn_u
mnu表示从
u
u
u到
1
1
1路径的最小值,这样即使路径压缩了也能转移,然后用虚树即可解决。
贴个虚树的代码
bool cmp(int x,int y) //按dfn排序
{
return dfn[x]<dfn[y];
}
void Insert(int v)
{
int lca=LCA(v,sta[t]);
if(lca==sta[t]) {sta[++t]=v;return;} //若当前点在子树内,直接入栈
for(;t>1&&dfn[lca]<=dfn[sta[t-1]];t--) add2(sta[t-1],sta[t]); //说明已经不在子树内了,清理子树内节点
if(lca!=sta[t]) add2(lca,sta[t]),sta[t]=lca; //若lca是新的节点,将栈顶替换
sta[++t]=v;
return;
}
void solve()
{
cnt=0;sta[t=1]=1;sort(s+1,s+m+1,cmp); //记得每次初始化
for(int i=1;i<=m;i++) Insert(s[i]),vis[s[i]]=true;
for(;t>1;t--) add2(sta[t-1],sta[t]);
printf("%lld\n",dp(1));
for(int i=1;i<=m;i++) vis[s[i]]=false;
return;
}
T3:
记忆化搜索,因为陷阱种类很少,所以可以对陷阱状态状压,对于一个陷阱有三种状态,毒陷阱,非毒陷阱,未知,所以需要三进制状压。用条件概率公式算出三进制中每个未知陷阱为有毒或无毒的概率,按这个搜就行。
#include<bits/stdc++.h>
using namespace std;
const int N=35,H=8,K=505;
const int fx[4]={1,0,-1,0},fy[4]={0,-1,0,1};
int n,m,k,h,sx,sy,p[1<<H],pow3[K];
bool vis[N][N][K][H];
double f[N][N][K][H],g[K][H];
char Map[N][N];
struct three{
public:
int x;
three(int xx=0) {x=xx;return;}
int get(int a) {return (x/pow3[a])%3;}
three change(int a,int v) {return three(x-get(a)*pow3[a]+v*pow3[a]);}
}st;
void init()
{
pow3[0]=1;
for(int i=1;i<=k;i++) pow3[i]=pow3[i-1]*3;
three S;bool flag;int tmp;
for(;S.x<pow3[k];S.x++)
{
tmp=0;
for(int i=0;i<(1<<k);i++)
{
flag=true;
for(int j=0;j<k;j++) if(S.get(j)&&S.get(j)!=((i>>j)&1)+1) {flag=false;break;}
if(!flag) continue;
tmp+=p[i];
for(int j=0;j<k;j++) if(!S.get(j)&&((i>>j)&1)) g[S.x][j]+=p[i];
}
for(int j=0;j<k;j++) if(!S.get(j)) g[S.x][j]/=1.0*tmp;
}
return;
}
bool in(int x,int y)
{
if(x>=1&&x<=n&&y>=1&&y<=m) return true;
return false;
}
double Dfs(int x,int y,three S,int hp)
{
if(vis[x][y][S.x][hp]) return f[x][y][S.x][hp];
vis[x][y][S.x][hp]=true;
if(hp<=0) return f[x][y][S.x][hp]=0;
if(Map[x][y]=='@') return f[x][y][S.x][hp]=1;
for(int i=0;i<4;i++)
{
int tx=x+fx[i],ty=y+fy[i];
if(Map[tx][ty]=='#'||!in(tx,ty)) continue;
if(Map[tx][ty]<'A'||Map[tx][ty]>'Z') {f[x][y][S.x][hp]=max(f[x][y][S.x][hp],Dfs(tx,ty,S,hp));continue;}
int tmp=Map[tx][ty]-'A';
if(S.get(tmp)==1) f[x][y][S.x][hp]=max(f[x][y][S.x][hp],Dfs(tx,ty,S,hp));
else if(S.get(tmp)==2) f[x][y][S.x][hp]=max(f[x][y][S.x][hp],Dfs(tx,ty,S,hp-1));
else f[x][y][S.x][hp]=max(f[x][y][S.x][hp],g[S.x][tmp]*Dfs(tx,ty,S.change(tmp,2),hp-1)+(1-g[S.x][tmp])*Dfs(tx,ty,S.change(tmp,1),hp));
}
return f[x][y][S.x][hp];
}
int main()
{
scanf("%d%d%d%d",&n,&m,&k,&h);
for(int i=1;i<=n;i++)
{
scanf("%s",Map[i]+1);
for(int j=1;j<=m;j++) if(Map[i][j]=='$') sx=i,sy=j;
}
for(int i=0;i<(1<<k);i++) scanf("%d",&p[i]);
init();
printf("%.3lf",Dfs(sx,sy,st,h));
return 0;
}
然后你就过了
l
u
o
g
u
luogu
luogu和
l
e
m
o
n
lemon
lemon的数据,但
A
O
J
AOJ
AOJ怎么都过不去,怎么回事呢?
事实上上面那个算法是伪的,因为可能有环的情况出现,那么便会用一个未完全更新的状态更新导致答案错误。
正确算法应该是将走平地和已知无毒的陷阱处理掉,只将走到终点与有毒陷阱作为一步,先搜出来再搜一遍, 才能避免环的情况。。。
(
A
O
J
AOJ
AOJ哪搞来的
h
a
c
k
hack
hack数据,太离谱了。
总结:
S
C
O
I
2011
SCOI2011
SCOI2011一年两道错题,出数据的早该打打了。