CCF-CSP 202104-4 校门外的树dp+因子数集合
Posted nefu-ljw
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了CCF-CSP 202104-4 校门外的树dp+因子数集合相关的知识,希望对你有一定的参考价值。
题目链接
http://118.190.20.162/view.page?gpid=T125
思路
对于n=2的情况,假设障碍物位置分别是a[i]和a[j](i>j),即两个障碍物相邻,中间没有障碍物阻挡,区间长度为a[i]-a[j],容易想到 a n s = f ( a [ i ] − a [ j ] ) ans=f(a[i]-a[j]) ans=f(a[i]−a[j]),其中 f ( x ) f(x) f(x)函数表示求 x x x的因子个数(注意,此处的因子包括1,但不包括 x x x自身)。
对于n>2,考虑
O
(
n
2
)
O(n^2)
O(n2)复杂度的dp,则有
d
p
[
i
]
=
∑
j
=
1
i
−
1
d
p
[
j
]
∗
c
a
l
c
(
i
,
j
)
,
i
>
j
dp[i]=\\sum_j=1^i-1 dp[j]*calc(i,j),i>j
dp[i]=∑j=1i−1dp[j]∗calc(i,j),i>j,那么
a
n
s
=
d
p
[
n
]
ans=dp[n]
ans=dp[n]。
其中
c
a
l
c
(
i
,
j
)
calc(i,j)
calc(i,j)表示下标分别为i, j(i>j)的两个障碍物之间的方案数。这里由于两个障碍物之间可能还会有若干个障碍物的阻挡,它们把[j, i]分成了若干个小区间,那么在计算时要考虑去除掉中间这若干个小区间的所有因子:具体做法是,倒序枚举j,每次将当前区间长度的所有因子加入集合,同时计入方案数,那么下次枚举更长的区间时(这次是[j, i],下次是[j-1, i]),如果出现了之前集合中加入的因子,就不计入答案。简而言之,就是用一个set集合来维护[j,i]区间中所有出现过的因子。
由于a[i]最大到1e5,那么可以预处理1e5范围内每个数的所有因子(大概总共1e6个因子,跑一遍没问题)。
100分代码
#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
const int N=1e3+10,M=1e5,mod=1e9+7;
vector<int>fac[M+10]; // 每个数的所有因子(包括1,不包括它自身)
void get_fac()
for(int i=1;i<=M/2;i++)
for(int j=2*i;j<=M;j+=i) // 2*i,...,k*i(<=M)
fac[j].push_back(i);
int a[N];
ll dp[N];
unordered_set<int>s; // 比set速度快,因为它有去重,但没有排序
ll calc(int i,int j)
int x=a[i]-a[j];
ll ret=0;
for(int it:fac[x]) // x的所有因子(包括1,不包括它自身)
if(s.count(it)==0) // 之前集合中没有it这个因子,那么这次就可以取,但下次不能取(加入集合)
ret++; // 这次可以取
s.insert(it); // 下次不能取(加入集合)
s.insert(x); // 将它自身加入集合(下次不能再取)
return ret;
int main()
ios::sync_with_stdio(false);
get_fac();
int n; cin>>n;
for(int i=1;i<=n;i++)
cin>>a[i];
dp[1]=1;
for(int i=2;i<=n;i++)
s.clear();
for(int j=i-1;j>=1;j--)
dp[i]=(dp[i]+dp[j]*calc(i,j)%mod)%mod;
printf("%lld\\n",dp[n]);
return 0;
/*
2
1 10
ans: 2
11
0 10 20 30 40 50 60 70 80 90 100
ans: 256507
*/
坑点
想清楚维护因子数集合时要不要算上两个特殊的因子:1,数本身。这里记录方案数时算上1,不算数本身;但是1和数本身都要加入到集合中,因为下次的长区间不能再取这个数了,否则就会撞上中间的障碍物。
参考
https://www.cnblogs.com/lipoicyclic/p/15020078.html
以上是关于CCF-CSP 202104-4 校门外的树dp+因子数集合的主要内容,如果未能解决你的问题,请参考以下文章