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=1i1dp[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

  • 2
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 2
    评论
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

nefu-ljw

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值