2020杭电第九场补题博客
一、Tree(1001)
题目链接: Tree
题目大意: 现在存在一颗n个节点的树,每个节点可以到达他的任何一个孩子节点,如果x节点可以到达y节点,就记作一个二元组(x,y),任何x节点可以到达本身这个节点,现在你可以添加一条有向边,让整棵树的二元组数量最多,求出这个数量。
解题思路: 我们知道,对于根节点来说,他能到达的节点数就是整棵树的节点数n,也就是说每个节点在不添加在这条有向边之前,所能抵达的节点数就是,该节点所代表的子树的节点数。现在我们添加一条边,那么这条边的两个端点以及这两个端点之间的所有的点,所能到达的节点数就是这个环中所有点所能到达的节点数的最大值。(这句话可能有点绕,但是应该不难理解吧),那么我们知道根节点所能到达的点的数量是最多的,所有我们知道这条边其中一个端点就是跟节点,那么如何使得这个环中的节点数量最多呢,当然就是叶子节点作为另一个端点。现在我们只需要先求出没有添加这条有向边时,每一个叶子节点到根节点的这条路径上所有节点所能到达的节点数量之和,然后求出添加这条有向边后的所对应的每个环中所有节点所能到达的节点数量之和。计算哪条路径的数量增量最大,那么我们就可以在这个叶子节点和根节点之间添加一条有向边。(具体细节看代码)
代码:
#include<stdio.h>
#include<string.h>
#include<math.h>
#include<iostream>
#include<string>
#include<sstream>
#include<algorithm>
#include<map>
#include<set>
#include<queue>
#include<stack>
#include<vector>
typedef long long ll;
using namespace std;
const ll mod = 998244353;
const int N=5e5+5;
struct edge{
int to;
};
vector<edge> V[N];
ll cnt[N];
ll Sum;
ll ans;
ll n;
void dfs(int x) //求出x节点所能到达的节点数 并保存在 cnt数组中
{
cnt[x]=1;
if(V[x].size()==0)
{
return ;
}
for(int i=0;i<V[x].size();i++)
{
int y=V[x][i].to;
dfs(y);
cnt[x]+=cnt[y];
}
}
void dfs2(int x,ll dep)
{
if(V[x].size()==0)
{
ans=max(n*dep-cnt[x],ans); //增量就是n*该叶子节点的深度减去
//从根节点到叶子节点这条路径上的所有节点所能到达的节点数之和
return ;
}
for(int i=0;i<V[x].size();i++)
{
int y=V[x][i].to;
cnt[y]+=cnt[x]; //我们把父节点的所能到达的节点数叠加到子节点上便于计算。
dfs2(y,dep+1);
}
}
int main()
{
int t;
cin>>t;
while(t--)
{
Sum=0;
ans=-1;
scanf("%d",&n);
for(int i=1;i<=n;i++)
{
cnt[i]=0;
V[i].clear();
}
for(int i=2;i<=n;i++)
{
int a;
scanf("%d",&a);
V[a].push_back(edge{i});
}
dfs(1); //求出每个节点所能抵达的个数
for(int i=1;i<=n;i++)
{
Sum+=cnt[i];
}
dfs2(1,1);
cout<<Sum+ans<<endl;
}
}
二、Slime and Stones(1003)
题目链接: Slime and Stones
题目大意: 两个人在玩游戏,有两堆石头,一堆有a个石头,一堆有b个石头,游戏规则是:每次可以从一堆石头中取任意个石头,或者从两堆石头中取石头,这种情况下,设在左边那堆石头取x个,在右边那堆石头取y个,必须满足|x-y|<=k这样的条件。轮到谁无法取石头,那么这个人就输了。
解题思路: 首先这个题和威佐夫博弈十分的相似,在威佐夫博弈中,从两堆石头中取石头的数量必须是相等的,也就是k=0的情况。首先我们假设a<b,在威佐夫博弈中第n个必败态的通项公式为
B
n
=
A
n
+
n
B_n=A_n+n
Bn=An+n,在这个题中,我知道当k=1的时候(1,3),(2,6)是必败态那么也就是说这里可以猜想出
B
n
=
A
n
+
n
∗
(
k
+
1
)
B_n=A_n+n*(k+1)
Bn=An+n∗(k+1),为这道题必败态的通项公式。那么当我们知道a,b,k怎么知道是否是必败态呢,这里我们用威佐夫博弈的公式推导过程来解答这个题。
同样的思路,我们可以求出α,但是α是一个带有k的式子(这个式子在代码中会给出),那么我们判断
A
n
A_n
An是否等于α*n就可以了,这里的n表示的是前面有多少个必败态,n=(b-a)/(k+1)。这样我们就可以判断是否为必败态了。(具体细节看代码)
代码:
#include<stdio.h>
#include<string.h>
#include<math.h>
#include<iostream>
#include<string>
#include<sstream>
#include<algorithm>
#include<map>
#include<set>
#include<queue>
#include<stack>
#include<vector>
typedef long long ll;
using namespace std;
const ll mod = 998244353;
const int N=1e5+5;
int main()
{
ll t;
ll a,b,k;
ll cnt;
cin>>t;
while(t--)
{
cin>>a>>b>>k;
if(a>b)
swap(a,b);
cnt=b-a;
double K=(1-k+sqrt(k*k+2*k+5))/(2.0); //α的值
ll ak=(ll)((cnt*1.0)/(k+1)*K);
if(cnt%(k+1)!=0) //cnt/(k+1)表示的是前面必败态的个数 因此是个整数
{
printf("1\n");
continue;
}
if(ak==a)
printf("0\n");
else
printf("1\n");
}
}