20210701暑假模拟赛
所用算法
- 栈
- 二分答案
- dfs+枚举
- 最短路+计数
- 动态规划
目录
T1.B班解密
T2.angry
T3.黑洞-luogu1444
T4.白银莲花池-usaco2007
T5.山
T6.格斗
题解
T1.解密
这道题可以用栈
也可以用数组模拟栈
,
做题时要关注局部关系
,按照插入顺序的逆顺序来删除,用栈来维护。
T2.angry
正宗到不能再正宗的二分答案
的题,
最开始判断二分的时候打错了,下面放的是正解
bool f(int aa)
{
aa+=aa;
long long sum=0,jl=1,i=1;
while(jl<=n)
{
if(x[i]-x[jl]<=aa&&i<=n)
{
i++;
}
else
{
sum++;
jl=i;
}
}
if(sum<=k) return 1;
else return 0;
}
int ll=0,rr=x[n];
while(ll+1<rr)
{
int mid=(ll+rr)/2;
if(f(mid)) rr=mid;
else ll=mid;
}
if(f(ll)) printf("%d\n",ll);
else printf("%d\n",rr);
T3.黑洞
看完数据范围
N
≤
12
N\leq12
N≤12,我那个激动啊,那不直接暴力解
先进行预处理
分析:用next数组预处理距离第i个黑洞最近的黑洞编号
scanf("%d",&n);
for(int i=1;i<=n;i++)
scanf("%d%d",&a[i].x,&a[i].y);
for(int i=1;i<=n;i++)
{
for(int j=1;j<=n;j++)
{
if(a[i].y==a[j].y&&a[i].x<a[j].x&&(a[next[i]].x>a[j].x||!next[i]))
next[i]=j;
}
}
然后用dfs枚举它所连接的那个黑洞
void dfs(int k)
{
if(k==n)
{
if(check()) ans++;
return ;
}
int xx;
for(int i=1;i<=n;i++)
if(!vis[i])
{
xx=i;
vis[xx]=1;
break;
}
for(int i=xx+1;i<=n;i++)
{
if(!vis[i])
{
vis[i]=1;
fri[xx]=i; fri[i]=xx;
dfs(k+2);
fri[xx]=0; fri[i]=0;
vis[i]=0;
}
}
vis[xx]=0;
}
最后判断是否出现环
bool check()
{
for(int i=1;i<=n;i++)
{
int x=i;
for(int j=1;j<=n;j++)//从i点开始,不断重复传送--右行--传送,因为有n个点,所以最多传送n次
x=next[fri[x]];
if(x) return 1;//若传送n次后右边依然有点,则说明死循环了
}
return 0;
}
T4.白银莲花池-usaco2007
这道题是一道很好的三合一问题
了,用的是最短路+计数
,细节较多
首先,在建图的时候要考虑三个因素:
1. 最少添加荷叶的个数
2. 在第一个条件
下,最少的跳跃次数
3. 在第二个条件下
,不同的方案数
我们先考虑如何添加最少的荷叶,最多只有
30
∗
30
30*30
30∗30个点。
建图,如果当前点可以跳的8个方向的点是荷叶,边权为
0
0
0,是水,边权为
1
1
1,其他不建边。
然后进行SPFA
,找到最短路,其累加的权值和就是当前最小莲花数
将最少莲花数
n
u
m
num
num,最少的跳跃次数
s
t
e
p
step
step,方案数
w
a
y
s
ways
ways分别存储并维护
更新思路如下:
1. 若当前状态
n
u
m
num
num大于该点
n
u
m
num
num,或当前
n
u
m
num
num等于该点
n
u
m
num
num且当前
s
t
e
p
step
step大于该点
s
t
e
p
step
step,更新当前状态
2. 若当前状态
n
u
m
num
num等于该点
n
u
m
num
num且当前
s
t
e
p
step
step等于该点
s
t
e
p
step
step将父节点的
w
a
y
s
ways
ways加到该点
w
a
y
s
ways
ways,更新当前状态
3. 若当前状态
n
u
m
num
num等于该点
n
u
m
num
num且当前
s
t
e
p
step
step小于该点
s
t
e
p
step
step,直接将该点
s
t
e
p
step
step及该点
w
a
y
s
ways
ways用当前状态更新
4. 若当前状态
n
u
m
num
num小于该点
n
u
m
num
num,更新当前状态
最后输出终点的 n u m num num, s t e p step step和 w a y s ways ways即可
T5.山
难
根据题意,必然每隔一个山才能建房子。
设 f [ i ] [ j ] [ x ] [ y ] ( i ≤ n , j ≤ i + 1 2 , x < 2 , y < 2 ) f[i][j][x][y](i\leq n,j\leq \frac{i+1}{2},x<2,y<2) f[i][j][x][y](i≤n,j≤2i+1,x<2,y<2)表示前 i i i座山中有 j j j座用来造房子,第 i − 1 i-1 i−1座山与第 i i i座山的状态分别为 x x x和 y y y( 0 0 0表示不建房子, 1 1 1反之)的最小花费
然后转移,最后输出时在几种状态中取小即可。
下面是状态转移方程
f
(
x
)
=
{
f
[
i
]
[
j
]
[
0
]
[
0
]
=
m
i
n
(
f
[
i
−
1
]
[
j
]
[
1
]
[
0
]
,
f
[
i
−
1
]
[
j
]
[
0
]
[
0
]
)
f
[
i
]
[
j
]
[
1
]
[
0
]
=
f
[
i
−
1
]
[
j
]
[
0
]
[
1
]
+
v
a
l
(
a
[
i
]
−
a
[
i
−
1
]
+
1
)
f
[
i
]
[
j
]
[
0
]
[
1
]
=
m
i
n
(
f
[
i
−
1
]
[
j
−
1
]
[
0
]
[
0
]
+
v
a
l
(
a
[
i
−
1
]
−
a
[
i
]
+
1
)
,
f
[
i
−
1
]
[
j
−
1
]
[
1
]
[
0
]
+
v
a
l
(
m
i
n
(
a
[
i
−
2
]
−
1
,
a
[
i
−
1
]
)
−
a
[
i
]
+
1
)
)
f(x)=\left\{ \begin{array}{lr} f[i][j][0][0] = min(f[i-1][j][1][0],f[i-1][j][0][0])\\ f[i][j][1][0] = f[i-1][j][0][1]+val(a[i]-a[i-1]+1)\\ f[i][j][0][1] = min(f[i-1][j-1][0][0]+val(a[i-1]-a[i]+1),f[i-1][j-1][1][0]+val(min(a[i-2]-1,a[i-1])-a[i]+1)) \end{array} \right.
f(x)=⎩⎨⎧f[i][j][0][0]=min(f[i−1][j][1][0],f[i−1][j][0][0])f[i][j][1][0]=f[i−1][j][0][1]+val(a[i]−a[i−1]+1)f[i][j][0][1]=min(f[i−1][j−1][0][0]+val(a[i−1]−a[i]+1),f[i−1][j−1][1][0]+val(min(a[i−2]−1,a[i−1])−a[i]+1))
分析:f[i][j][0][0]=min(f[i-1][j][1][0],f[i-1][j][0][0]);//这条转移显然
f[i][j][1][0]=f[i-1][j][0][1]+val(a[i]-a[i-1]+1);//因为不能挨着建,所以[1][0]只能由[0][1]转移而来
f[i][j][0][1]=min(f[i-1][j-1][0][0]+val(a[i-1]-a[i]+1),f[i-1][j-1][1][0]+val(min(a[i-2]-1,a[i-1])-a[i]+1));
//注意,如果是由[1][0]转移过来的话,那么第i-1座山已经小于a[i-2]了,不需要算两遍
//f[i][j][1][1]由题可知必定不合法,所以不考虑
最后取 f f f数组的最小值
T6.格斗
读完题后,我先注意到的是所有选手站成一个圈,所以先用 a a a数组将其展开。
for(int i=1;i<=n;i++)
for(int j=1;j<=n;j++)
{
scanf("%d",&a[i][j]);
a[i+n][j]=a[i][j];
a[i][j+n]=a[i][j];
a[i+n][j+n]=a[i][j];
}
这道题用的是区间DP
f [ i ] [ j ] f[i][j] f[i][j]表示第 i i i个人或第 j j j个人能打败 i i i和 j j j之间的人
注意:这里 f f f数组的初值不能直接用 a a a数组直接赋值
for(int i=1;i<=2*n;i++)
f[i][i+1]=true;//相邻的两人一定有一个能打败另一个
然后开始进行区间DP
for(int i=2*n-2;i>=1;i--)//左端点
for(int j=i+2;j<=2*n;j++)//右端点
for(int k=i+1;k<=j-1;k++)//中间点
if((a[i][k]==1||a[j][k]==1)&&f[i][k]==true&&f[k][j]==true)
f[i][j]=true;
分析:
i
i
i,
j
j
j两个人中有任意一个人能打败
k
k
k
i
i
i和
k
k
k两个人里有一个能战胜
i
i
i到
k
k
k中间的所有人
k
k
k和
j
j
j两个人里有一个能战胜
k
k
k到
j
j
j中间的所有人
⇒
i
i
i和
j
j
j中一定有一个人能打败
i
i
i到
j
j
j之间的所有人
最后枚举每一个人,判断 f [ i ] [ i + n ] f[i][i+n] f[i][i+n]是否为真即可