[NOI2001] 方程的解数(折半搜索)

97 篇文章 0 订阅

[NOI2001] 方程的解数

题目描述

已知一个 n n n 元高次方程:
∑ i = 1 n k i x i p i = 0 \sum\limits_{i=1}^n k_ix_i^{p_i} = 0 i=1nkixipi=0
其中: x 1 , x 2 , … , x n x_1, x_2, \dots ,x_n x1,x2,,xn 是未知数, k 1 , k 2 , … , k n k_1,k_2, \dots ,k_n k1,k2,,kn 是系数, p 1 , p 2 , … p n p_1,p_2,…p_n p1,p2,pn 是指数。且方程中的所有数均为整数。

假设未知数 x i ∈ [ 1 , m ]   ( i ∈ [ 1 , n ] ) x_i \in [1,m] \space ( i \in [1,n]) xi[1,m] (i[1,n]),求这个方程的整数解的个数。

输入格式

第一行一个正整数 n n n,表示未知数个数。
第二行一个正整数 m m m
接下来 n n n 行,每行两个整数 k i , p i k_i,p_i ki,pi

输出格式

输出一行一个整数,表示方程解的个数。

样例 #1

样例输入 #1

3
150
1 2
-1 2
1 2

样例输出 #1

178

提示

【数据范围】

对于 100 % 100\% 100% 的数据, 1 ≤ n ≤ 6 1\le n \le 6 1n6 1 ≤ m ≤ 150 1\le m \le 150 1m150,且
∑ i = 1 n ∣ k i m p i ∣ < 2 31 \sum\limits_{i=1}^n |k_im^{p_i}| < 2^{31} i=1nkimpi<231
答案不超过 2 31 − 1 2^{31}-1 2311 p i ∈ N ∗ p_i \in \mathbb N^* piN

思路

  • 我们首先看到 n n n 很小,因此我们想到爆搜,为此,我们会写出如下代码:

30   p t s 30\ pts 30 pts

//未知数的数量很小,我们试着dfs

#include<iostream>
#include<algorithm>
#include<cmath>

using namespace std;

#define int long long

const int N = 8;

int k[N],p[N];
int n,m;
int ans;

void dfs(int u,int s){
    if(u==n+1){
        if(s==0) ans++;
        return;
    }
    
    for(int i=1;i<=m;i++){
        int t=k[u]*pow(i,p[u]);
        dfs(u+1,s+t);
    }
    
}

signed main(){
    cin>>n>>m;
    
    for(int i=1;i<=n;i++){
        cin>>k[i]>>p[i];
    }
    
    dfs(1,0);
    
    cout<<ans;
    
    return 0;
    
}
  • 因此,我们考虑优化,我们尝试折半搜索。为什么呢?因为我们发现如果直接搜索的话,且 dfs 的时间复杂度是指数级别的,如果我们折半搜索,能把指数大部分的装化成指数的系数。
  • 折半搜索都是 dfs1 来搜索,搜到的答案记录到 w w w 数组中,然后 dfs2 把这些内容取出来且看看与 w w w 数组中有多少个相同的就行了。
  • 但我们存入答案要取出的东西是什么呢?我们把原问题的式子拆成 ∑ i = 1 ⌊ n / 2 ⌋ k i ∗ x i p i = − ∑ ⌈ n / 2 ⌉ k i ∗ x i p i \sum_{i=1}^{\lfloor{n/2}\rfloor}k_i*x_i^{p_i}=-\sum_{\lceil{n/2}\rceil}k_i*x_i^{p_i} i=1n/2kixipi=n/2kixipi,因此我们 dfs2 就得找 -s,因为这个式子是负的。
  • 这边取出的时候我们是看有多少个相同的,此时就得用 upper_boundlower_bound 合起来算。

AC 代码

//未知数的数量很小,我们试着dfs

#include<iostream>
#include<algorithm>
#include<cmath>

using namespace std;

#define int long long

const int N = 10,M = 1e7+10;

int k[N],p[N];
int n,m;
int ans,tmp;
int w[M],cnt;

// void dfs(int u,int s){
//     if(u==n+1){
//         if(s==0) ans++;
//         return;
//     }
    
//     for(int i=1;i<=m;i++){
//         int t=k[u]*pow(i,p[u]);
//         dfs(u+1,s+t);
//     }
    
// }

void dfs1(int u,int s){
    if(u==tmp){
        w[++cnt]=s;
        return;
    }
    
    for(int i=1;i<=m;i++){
        dfs1(u+1,s+pow(i,p[u])*k[u]);
    }
}

void dfs2(int u,int s){
    if(u==n+1){
        int k=upper_bound(w+1,w+1+cnt,-s)-lower_bound(w+1,w+1+cnt,-s);
        ans+=k;
        return;
    }
    
    for(int i=1;i<=m;i++){
        dfs2(u+1,s+pow(i,p[u])*k[u]);
    }
}

signed main(){
    cin>>n>>m;
    
    tmp=(n>>1)+1;
    
    for(int i=1;i<=n;i++){
        cin>>k[i]>>p[i];
    }
    
    dfs1(1,0);
    
    sort(w+1,w+1+cnt);
    
    dfs2(tmp,0);
    
    cout<<ans;
    
    return 0;
    
}
  • 27
    点赞
  • 26
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

green qwq

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

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

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

打赏作者

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

抵扣说明:

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

余额充值