【状态】(数论)

【状态】(数论)

一般数字本身的范围很小->直接以数字本身作为状态
线性dp去转移

Small Operations

https://codeforces.com/contest/2114/problem/F

思路

一个是乘一个是除->尝试缩短为1个操作
->x和y都除到1找最短路线
->如何找最短路线?线性dp因数

代码

bool cmpll(ll a,ll b){return a<b;}
const ll INF=1e10;
//把x和y都变成1:考虑乘法/除法->考虑乘法
//问题转化为一个数化为x个比k小的数相乘 求x最小值
ll x,y,k;
vector<ll> get_divisors(ll n){
	vector<ll> res;
	for(ll i=1;i<=n/i;i++){
		if(n%i==0){
			res.push_back(i);
			//注意这里要判断 如果两个相同就只加一个 
			if(i!=n/i) res.push_back(n/i);
		}
	}
	sort(res.begin(),res.end(),cmpll);
	return res;
}
ll get_ans(ll num){
    if(num==1) return 0;//已经是最终状态了
    vector<ll> divs=get_divisors(num);
    int l=divs.size();
    //对因数进行dp:dp[i]表示从数字divs[i]变到1的最少操作次数
    /*【状态转移】
    大数可以被小数整除 divs[i] % divs[j] == 0
    可通过一次操作从divs[i]变成divs[j]
    */
    //特殊情况:divs[j]从大到小遍历:若divs[i]/divs[j]>k->后面的也一定会>k
    vector<ll> dp(l+1,INF);
    dp[0]=0;
    //divs[0]都是1 是最终状态
    for(int i=1;i<l;i++){
        for(int j=i-1;j>=0;j--){
            if(divs[i]/divs[j]>k) break;
            if(divs[i]%divs[j]==0){
                dp[i]=min(dp[i],dp[j]+1);
            }
        }
    }
    //divs[l-1] 最后一个数都是num本身
    if(dp[l-1]==INF) return -1;
    else return dp[l-1];
}
ll gcd(ll a,ll b){
    return b?gcd(b,a%b):a;
}
void solve(){
    cin>>x>>y>>k;
    ll g=gcd(x,y);
    x=x/g;
    y=y/g;
    ll ans1=get_ans(x);
    ll ans2=get_ans(y);
    if(ans1==-1 || ans2==-1) cout<<"-1"<<endl;
    else cout<<(ans1+ans2)<<endl;
}

Gellyfish and Flaming Peony

https://codeforces.com/contest/2116/problem/C

思路

首先特判数组中只有1个数->答案为0
统计公共gcd:若数组中已经有数是公共gcd了->直接转化
否则找1->先除公共gcd 接下来一定可以造出1
设状态:一个数通过操作到达1的最小次数->bfs去进行状态转移

代码

const int N=5010;
int n;
int gcd(int a,int b){
	return b?gcd(b,a%b):a;
}
void solve(){
    cin>>n;
    vector<int> a(n+1,0);
    for(int i=1;i<=n;i++) cin>>a[i];
    int g=a[1];
    if(n==1){
        cout<<0<<endl;
        return;
    }
    for(int i=1;i<=n;i++){
        g=gcd(g,a[i]);
    }
    int cnt=0;
    for(int i=1;i<=n;i++){
        if(a[i]==g){
            cnt++;
        }
        a[i]/=g;
    }
    if(cnt){
        cout<<n-cnt<<endl;
        return;
    }
    //找一个数通过操作到达1的最小次数 然后统计整个数组的最小次数->bfs
    vector<int> dp(N,-1);//对数本身(<=5000)往后找
    queue<int> q;
    for(int i=1;i<=n;i++){
        if(dp[a[i]]==-1){
            dp[a[i]]=0;//有的数
            q.push(a[i]);
        }
    }
    int ans=INF_INT;
    while(q.size()){
        int t=q.front();
        q.pop();
        if(t==1){
            ans=dp[t];
            break;
        }
        for(int i=1;i<=n;i++){
            int p=gcd(t,a[i]);
            if(dp[p]==-1){
                dp[p]=dp[t]+1;
                q.push(p);
            }
        }
    }
    if(ans==INF_INT) ans=0;
    cout<<ans+n-1<<endl;
}
posted @ 2025-05-29 22:05  White_ink  阅读(6)  评论(0)    收藏  举报