CSP校门前的树

初步思路:

  1. 预处理出340以内的素数数组[ 68 ](因为对于大于340的数,如果是合数,必然会被340以内的数整除(若不能,必大于1e5),那么判断进行质因数分解,只需逐个去除这个素数数组即可,最后如果不是1,便是最后一个素数)
  2. 对于求[al,ar)内的完美种类数,将ar-al进行因数分解,得到所有因数(不一定是质因数),然后一个一个检测,如果能够被am-ai整除,则淘汰,最后剩下的个数就是以al,ar为边界的完美种类数
  1. 设 g[i][j]表示以[i,j)为边界的方案种类数,f[i][j]表示从i到j所有方案数;终究还是错了,状态转移方程错误,没找到正确的关系;首先,f一定是由组合体和单一体组合而成,而组合体也是如此,那么对于组成f的任意一种情况,可以看做是在ij之间任意插入若干个节点进行分割,f_ij便是所有情况的和,显然这样是比较难求的,那么就要进行分解,分解成简单问题,分解动作一般就是以某个东西作为衡量,这种东西的所有情况可以代表了原问题的所有情况==》那么在这道题中,我们以最后一个分界线与结尾处组成的单一体的长度为衡量标准,此分界线前面的情况任意,那么这样(长度范围为[1 , j-i-1]+g{i,j})的确可以包含所有情况
  2. 由于区间不存在大于关系,这里应该采用长度递推,而由于i,j不会重复,那么g的求解便不会重复;长度为1的时候,g的结果可以赋给f,到后来g可以在当前步骤直接求解,而f直接采用递推即可
  3. 正如所料,超时!显然,毕竟做了那么多明显重复的操作,对于每一个obs[j]-obs[i]
    都要计算所有因数,必然超时,那么,这次我终于明白了什么叫做数论题目的打表操作,就是对于数论操作中可能会经常重复 使用的值,提前预处理出来,到用的时候直接线性查找即可,虽然在预处理的时候可能看上去似乎数据量巨大,划不来,实际上经过简单的数学分析就会发现,这样会减少很多重复性工作(思政教育:经验的确重要,但使用经验之前,还是要经过理性判断斟酌一下!)

TLE代码:

//#include<bits/stdc++.h>
#include<iostream>
#define LOCAL
using namespace std;
typedef long long ll;
//这里必须使用长整型,因为即使mod是整型,但只要出现两个数相乘,就会出现溢出

const ll maxn=1010;
const ll mod=1e9+7;
// int prm[70];int pn;
ll obs[maxn];
ll dp[maxn];

ll read(){
    ll s=0,f=1,c=getchar();
    while (c<'0' || c>'9' ) { if (c=='-') f=-1; c=getchar();}
    while (c>='0' && c<='9') {s=(s<<1)+(s<<3)+c-'0';c=getchar();}
    return f*s;
}
// 欧氏筛实现340以内素数数组
// int mark[340];
// int get_prm(){
// 	for (int i=2;i<340;++i) mark[i]=1;
// 	int k=0;
// 	for (int i=2;i<340;++i){
// 		if (mark[i]) prm[k++]=i;
// 		for (int j=0;j<k;++j){
//             if (prm[j]*i>=340) break;
// 			mark[prm[j]*i]=0;
// 			if (i%prm[j]==0) break;
// 		}
// 	}
// 	return k;
// }

ll get_g(int i,int j){
	int cont=0;
	ll dft=obs[j]-obs[i];
	if (j==i+1 && dft>1) cont++;
	// get facts
	for (int k=2;k*k<=dft;++k){
		 if (dft%k==0){
		 	int flag=1;
		 	for (int m=i+1;m<=j-1;++m){
		 		if ((obs[m]-obs[i])%k==0){ flag=0;break;}
		 	}
		 	if (flag) cont++;
		 	ll tmpk=dft/k;
		 	if (tmpk==k) continue;
            flag=1;
		 	for (int m=i+1;m<=j-1;++m){
		 		if ((obs[m]-obs[i])%tmpk==0){ flag=0;break;}
		 	}
		 	if (flag) cont++;
	    }
		cont%=mod;
	}
	return cont;
}	
	
int main(){
    #ifdef LOCAL
    freopen("input.txt","r",stdin);
    #endif
    ll n=read();
    for (int i=0;i<n;++i){
    	
    	obs[i]=read();
	}
	// pn=get_prm();	// pn --> the number of prime in [2,340);
	for (int i=1;i<n;++i){
		for (int j=1;j<i;++j){
			dp[i]=(dp[i]+dp[j]*get_g(j,i)%mod)%mod;
		}
		dp[i]=(dp[i]+get_g(0,i))%mod;
	}
	printf("%lld",dp[n-1]);
	return 0;
}

打表后通过的代码:

//#include<bits/stdc++.h>
#include<iostream>
#include<vector>
#include<cstring>
#define LOCAL
using namespace std;
typedef long long ll;
//这里必须使用长整型,因为即使mod是整型,但只要出现两个数相乘,就会出现溢出

const ll maxn=1010;
const ll mod=1e9+7;
const ll maxa=1e5+10;
// int prm[70];int pn;
ll obs[maxn];
ll dp[maxn];
vector<int> facts[maxa];
int flag[maxa];

ll read(){
    ll s=0,f=1,c=getchar();
    while (c<'0' || c>'9' ) { if (c=='-') f=-1; c=getchar();}
    while (c>='0' && c<='9') {s=(s<<1)+(s<<3)+c-'0';c=getchar();}
    return f*s;
}
	
int main(){
    #ifdef LOCAL
    freopen("input.txt","r",stdin);
    #endif
    ll n=read();
    //今日终于见识了打表的真实面目:虽然看上去处理的数据量很大,但是这种预处理避免了之后在函数中很多的
    //重复性动作
    for (int i=1;i<maxa;++i){
        for (int j=2*i;j<maxa;j+=i){
            facts[j].push_back(i);
        }
    }
    for (int i=0;i<n;++i){
    	obs[i]=read();
	}
    dp[0]=1;
	for (int i=1;i<n;++i){
        memset(flag,0,sizeof(flag));
		for (int j=i-1;j>=0;--j){
            int cont=0;
            ll dft=obs[i]-obs[j];
            int len=(int) facts[dft].size();
            for (int k=0;k<len;++k){
                if (!flag[facts[dft][k]]) {cont++;flag[facts[dft][k]]=1;}
            }
            flag[dft]=1;
			// dp[i]=(dp[i]+dp[j]*get_g(j,i)%mod)%mod;
            dp[i]=(dp[i]+dp[j]*cont%mod)%mod;
		}
		// dp[i]=(dp[i]+get_g(0,i))%mod;
	}
	printf("%lld",dp[n-1]);
	return 0;
}
上一篇:python编程:利用函数递归调用和turtle绘制树


下一篇:编程笔记:三层架构(3-tier architecture)要点