链接:
官网:
http://118.190.20.162/view.page?gpid=T125
Acwing:https://www.acwing.com/problem/content/description/3417/
题意:顺序给出数轴上N给不相等的点,记为序列a,首先将大区间[a[0],a[N-1]]划分成若干个子区间[li,ri),然后在子区间上面选取若干个点,要求这些点和区间端点构成等差数列。端点不能作为选取的点。问选取点的集合数量有多少。
分析:首先这是一个有限制的选择问题,我们见到的选择问题通常用dp、类dp、记忆化搜索来解决。dp和记忆化搜索的思想都是先把问题分为多个阶段的问题,每一个阶段都会对应一个状态,后面阶段的状态要利用已经求出的状态。
本题同理,首先求包含N个数的序列的方案数目这个问题划分阶段,先求包含1个数序列的方案数,然后求包含2个数的…以以此类推,最后求出包含N个数的,就是最后的答案。
于是本题第i个状态就是从第0个数第i个数的方案总数,即dp[i],一个维度就可以表示状态
之后要看怎么用之前的阶段求出各个阶段的状态值。对于第1个状态,就是第0个数到第1个数的约数个数,对于第i个状态,我们首先可以将第i个状态这个大集合划分成通过dp[i-1]求出的部分、通过dp[i-2]求出的部分…通过dp[0]求出的部分。比如在求通过第i-1个状态即dp[i-1]求出的部分时,由于第i-1之前的方案取法不会影响i-1到i的取法,所以从0到i-1的取法有dp[i-1]种,而i-1到i的取法有a[i]-a[i-1]的约数个数个,设为cnt个,对于dp[i]的贡献是cnt*dp[i-1]。
但是在求通过dp[i-2]求出的贡献值时,i-2到i的方案会不会和i-1到i的方案重合?注意,现在要将dp[i]划分成无交集,且并集为全集的子集,我们需要自己设计划分方式。我们的方向是,能写出简单的dp[i]与dp[0…i-1]关系的式子。而我们已经得到dp[i-1]的贡献是dp[i-1]*cnt,我们希望能找到0到i-2的cnt。
最后我们发现如果i-2的cnt设置成包含i-2到i的约数,同时不含i-1到i的约数的集合的元素个数,我们就可以将问题不重不漏的划分。同时题目说有障碍的地方不能种树,所以i-2到i的方案本身就不会包含i-1到i的方案使用过的约数,因为i-1到i的方案使用过的约数一定会经过端点。
暴力找约数的方法
#include <iostream>
#include<bits/stdc++.h>
typedef long long ll;
const int maxn=1000+5;
const int mod=1000000000+7;
const int maxa=100000+5;
using namespace std;
ll dp[maxn+5];
ll a[maxn+5];
vector<ll>v[maxa+10];
bool book[maxa+5];
ll solve(ll l,ll r){
ll x=a[r]-a[l];
ll cnt=0;
if(x==1){//注意,相距为1不能种树,而且之后约数1也不能再次使用,所以book[1]=1
book[1]=1;
return 0;
}
if(book[1]==0){
book[1]=1;
cnt++;
//cout<<1<<"*"<<endl;
}
for(ll i=2;i*i<=x;i++){
if(x%i==0){
if(book[i]==0){
book[i]=1;cnt++;
}
if(book[x/i]==0){
book[x/i]=1;cnt++;
}
}
}
book[x]=1;//注意,x不能被第j-1之前的利用
return cnt;
}
int main()
{
int N;
cin>>N;
for(int i=0;i<N;i++){
scanf("%lld",&a[i]);
}
dp[0]=1;
for(ll i=1;i<N;i++){
memset(book,0,sizeof book);
for(ll j=i-1;j>=0;j--){
dp[i]=(dp[i]+dp[j]*solve(j,i))%mod;
}
}
cout<<dp[N-1]<<endl;
return 0;
}
打表找约数
#include <iostream>
#include<bits/stdc++.h>
typedef long long ll;
const int maxn=1000+5;
const int mod=1000000000+7;
const int maxa=100000+5;
using namespace std;
ll dp[maxn+5];
ll a[maxn+5];
vector<ll>v[maxa+10];
bool book[maxa+5];
int main()
{
for(int i=1;i<maxa;i++){//对于所有i的倍数,i都是他们的约数
for(int j=2*i;j<maxa;j+=i){//从二倍开始,可以排除约数为自己的情况,因为有障碍的地方不能种树
v[j].push_back(i);
}
}
int N;
cin>>N;
for(int i=0;i<N;i++){
scanf("%lld",&a[i]);
}
dp[0]=1;
for(ll i=1;i<N;i++){
memset(book,0,sizeof book);//每次对dp[i-1]的划分操作对dp[i]的划分操作没有影响,要初始化标记数组
for(ll j=i-1;j>=0;j--){
ll x=a[i]-a[j];
ll cnt=0;
for(int i=0;i<v[x].size();i++){
if(book[v[x][i]]==0){//不能在障碍上种树,对于第j-1个点a[j-1]来说,a[i]-a[j]的约数的倍数是端点,
book[v[x][i]]=1;//端点是障碍,所以j-1前的约数都不能重复用
cnt++;
}
}
book[x]=1;//特别注意,虽然第j个端点不能cnt++,但是端点处不能种树,他影响第j-1个端点使得其不能利用约数a[i]-a[j]
dp[i]=(dp[i]+dp[j]*cnt)%mod;
}
}
cout<<dp[N-1]<<endl;
return 0;
}
想试一试搜索,先是栈内爆空间,然后改了set超时。。注意,这时候book不能开全局了,因为每个状态的计算是会交替进行,而不是计算完一个再计算一个所以全局的book会乱套。
#include <iostream>
#include<bits/stdc++.h>
typedef long long ll;
const int maxn=1000+5;
const int mod=1000000000+7;
const int maxa=100000+5;
using namespace std;
ll dp[maxn+5];
ll a[maxn+5];
vector<ll>v[maxa+10];
ll N;
ll dfs(ll level){
if(level>=N-1){
return 1;
}
// bool book[maxa+5];
// memset(book,0,sizeof book);//!!!!
set<ll>book;
for(ll i=level+1;i<N;i++){
if(dp[i]==0){
dp[i]=dfs(i);
}
ll cnt=0,x=a[i]-a[level];
for(ll j=0;j<v[x].size();j++){
if(find(book.begin(),book.end(),v[x][j])==book.end()){
book.insert(v[x][j]);
cnt++;
}
}
book.insert(x);
dp[level]=(dp[level]+dp[i]*cnt)%mod;
// cout<<"$"<<dp[level]<<" "<<dp[i]<<" "<<cnt<<endl;
}
return dp[level];
}
int main()
{
for(int i=1;i<maxa;i++){//对于所有i的倍数,i都是他们的约数
for(int j=2*i;j<maxa;j+=i){//从二倍开始,可以排除约数为自己的情况,因为有障碍的地方不能种树
v[j].push_back(i);
}
}
cin>>N;
for(ll i=0;i<N;i++){
scanf("%lld",&a[i]);
}
dp[0]=dfs(0);
cout<<dp[0]<<endl;
return 0;
}