题目链接:
E:https://codeforces.com/contest/1551/problem/E
F:https://codeforces.com/contest/1551/problem/F
E.Fixed Points
线性DP
题目大意为给定一个序列a,求出最少需要删除其中几个数字,可以使得修改之后的序列中满足
a
[
i
]
=
=
i
a[i]==i
a[i]==i的下标数大于等于
k
k
k。
对于每一个位置
i
i
i,是否删除i只会对位置
i
i
i之后的数字产生影响,满足无后效性,考虑以原序列中的每一个下标为状态进行转移。
用数组
f
[
i
]
[
j
]
f[i][j]
f[i][j]表示原序列前
i
i
i个位置删除
j
j
j个数所能得到的最大匹配数,那么对于第
i
i
i个位置,有删与不删两种选择,分别从
f
[
i
−
1
]
[
j
−
1
]
f[i-1][j-1]
f[i−1][j−1]与
f
[
i
−
1
]
[
j
]
f[i-1][j]
f[i−1][j]转移过来,并且如果不删第
i
i
i个位置,那么
a
[
i
]
a[i]
a[i]会往前移动
j
j
j位,若
a
[
i
]
=
i
−
j
a[i]=i-j
a[i]=i−j,则对答案的贡献
+
1
+1
+1,即为:
f
[
i
]
[
j
]
=
m
a
x
(
f
[
i
−
1
]
[
j
−
1
]
,
f
[
i
−
1
]
[
j
]
+
(
a
[
i
]
=
=
i
−
j
)
)
f[i][j]=max(f[i-1][j-1],f[i-1][j]+(a[i]==i-j))
f[i][j]=max(f[i−1][j−1],f[i−1][j]+(a[i]==i−j))
最后遍历查找第一个大于等于
k
k
k的值
f
[
n
]
[
i
]
f[n][i]
f[n][i],输出
i
i
i即可,不存在输出
−
1
-1
−1。
#include<bits/stdc++.h>
#define next next_
#define last last_
#define y1 yy
#define hash hash_
#define complex complex_
#define IOS ios::sync_with_stdio(false)
using namespace std;
using ll=long long;
using ull=unsigned long long;
using pii=pair<int,int>;
using pll=pair<ll,ll>;
const ll mod=1e9+7,mod2=1e9+9;
const ull base=101;
const double pi=acos(-1.0);
int _;
int n,k;
int a[2010];
int f[2010][2010];
void work(){
scanf("%d%d",&n,&k);
for(int i=0;i<=n;i++)
for(int j=0;j<=n;j++) f[i][j]=0;
for(int i=1;i<=n;i++) scanf("%d",&a[i]);
for(int i=1;i<=n;i++){
for(int j=0;j<=i;j++){
if(j>0) f[i][j]=max(f[i-1][j-1],f[i][j]);
if(j<=i-1) f[i][j]=max(f[i][j],f[i-1][j]+(a[i]==i-j));
}
}
for(int i=0;i<=n;i++) if(f[n][i]>=k){
printf("%d\n",i);
return;
}
printf("-1\n");
}
int main(){
scanf("%d",&_);
// _=1;
while(_--){
work();
}
return 0;
}
F.Equidistant Vertices
树形DP
题目大意为给定一棵树,在其中选出
k
k
k个节点,使得
k
k
k个节点中两两之间距离均相等,求选择节点的方案数。
由于条件节点两两之间距离均相等,易得
k
=
=
2
k==2
k==2时答案即为
c
(
n
,
2
)
c(n,2)
c(n,2),
k
>
=
3
k>=3
k>=3时所选出的节点一定满足一个放射性的形状。
由于
n
n
n的范围极小,可以枚举计算每一个节点作为放射形状的中间点时的方案数,最后求和。
假设选定了节点
r
r
r作为放射中心,则其每一棵子树可以提供选择一个节点,对
r
r
r的每一个子树进行一次
d
f
s
dfs
dfs求出任意深度的节点个数,使用
f
[
i
]
[
j
]
f[i][j]
f[i][j]存储深度为
i
i
i的情况下,选择了
j
j
j棵子树时的方案数,则转移方程为
f
[
i
]
[
j
]
f[i][j]
f[i][j]=
∑
f
[
i
]
[
j
−
1
]
∗
n
u
m
[
i
]
\sum{f[i][j-1]*num[i]}
∑f[i][j−1]∗num[i],其中
n
u
m
[
i
]
num[i]
num[i]表示当前遍历的子树中深度为
i
i
i的节点个数,边界条件为
f
[
i
]
[
0
]
=
=
1
f[i][0]==1
f[i][0]==1。最后对于每个节点作为中心的情况下,使得答案加上
∑
i
=
1
n
f
[
i
]
[
k
]
\sum_{i=1}^{n}f[i][k]
∑i=1nf[i][k]即可。
#include<bits/stdc++.h>
#define next next_
#define last last_
#define y1 yy
#define hash hash_
#define complex complex_
#define IOS ios::sync_with_stdio(false)
using namespace std;
using ll=long long;
using ull=unsigned long long;
using pii=pair<int,int>;
using pll=pair<ll,ll>;
const ll mod=1e9+7,mod2=1e9+9;
const ull base=101;
const double pi=acos(-1.0);
int _;
ll n,K,ans;
ll u[210],v[210],next[210],first[110];
ll num[110],f[110][110];
void dfs(ll t,ll fa,ll dep){
num[dep]++;
ll k=first[t];
while(k!=-1){
if(v[k]!=fa) dfs(v[k],t,dep+1);
k=next[k];
}
}
void work(){
ans=0;
scanf("%lld%lld",&n,&K);
for(ll i=1;i<=n;i++) first[i]=-1;
for(ll i=1;i<n;i++){
scanf("%lld%lld",&u[2*i-1],&v[2*i-1]);
u[2*i]=v[2*i-1];v[2*i]=u[2*i-1];
next[2*i-1]=first[u[2*i-1]];
first[u[2*i-1]]=2*i-1;
next[2*i]=first[u[2*i]];
first[u[2*i]]=2*i;
}
if(K==2){
printf("%lld\n",n*(n-1)/2);
return;
}
for(ll r=1;r<=n;r++){
for(ll i=0;i<=n;i++) f[i][0]=1;
for(ll i=0;i<=n;i++)
for(ll j=1;j<=n;j++) f[i][j]=0;
ll k=first[r];
while(k!=-1){
for(ll i=0;i<=n;i++) num[i]=0;
dfs(v[k],r,1);
for(ll i=1;i<=n;i++){
if(!num[i]) break;
for(ll j=K;j>=1;j--){
f[i][j]+=f[i][j-1]*num[i]%mod;
f[i][j]%=mod;
}
}
k=next[k];
}
for(ll i=1;i<=n;i++) ans+=f[i][K],ans%=mod;
}
printf("%lld\n",ans);
}
int main(){
scanf("%d",&_);
// _=1;
while(_--){
work();
}
return 0;
}