【牛客练习赛60 】A【按位推导公式】B【公式+逆元】C【子序列DP】D【扩展欧几里得算法】

题目连接

文章目录

A

思路:公式推导就行了:
∑ i = 1 n ∑ j = 1 n ( a i & a j ) = ∑ i = 1 n ∑ j = 1 n ∑ k = 0 30 2 k a i k a j k = ∑ k = 0 30 2 k ∑ i = 1 n ∑ j = 1 n a i k a j k \sum_{i=1}^n\sum_{j=1}^n(a_i\&a_j) = \sum_{i=1}^n\sum_{j=1}^n\sum_{k=0}^{30}2^ka_{ik}a_{jk} = \sum_{k=0}^{30}2^k\sum_{i=1}^n\sum_{j=1}^na_{ik}a_{jk} i=1nj=1n(ai&aj)=i=1nj=1nk=0302kaikajk=k=0302ki=1nj=1naikajk
对于 a i k a j k a_{ik}a_{jk} aikajk ,只有都是1才为1,所以统计这n个值中第k个二进制位1的个数cnt[k],后面一串就可以表示为 C(cnt[k], 2)。
= ∑ k = 0 30 2 k ( c n t [ k ] ∗ ( c n t [ k ] − 1 ) 2 ) = \sum_{k=0}^{30}2^k (\frac{cnt[k] * (cnt[k]- 1)}{2}) =k=0302k(2cnt[k](cnt[k]1))

Coding:

#include <bits/stdc++.h>
using namespace std;

typedef long long ll;
typedef pair<int, int> pii;

const int N = 1e5 + 11;
const int M = 1e6 + 11;
const int MOD = 1e9 + 7;
const int INF = 0x3f3f3f3f;

int cnt[31];
int main(int argc, char **args){
    int n;cin >>n;

    for(int i = 0; i < n ; i++){
        ll a; scanf("%lld", &a);
        for(int j = 30; j >= 0; j--){
            if(a >> j & 1) 
                cnt[j]++;
        }
    }

    ll sum = 0 ;
    for(int i = 30; i >= 0; i--){
        sum += (1 << i) * 1ll *(cnt[i] * 1ll * (cnt[i]) ); 
    }

    cout << sum <<"\n";
return 0;
}

B

思路:对于每条边,它可以包括在n-2个三角形中,这样算下来,是比答案多了一倍,除以2就行了,不过这里是乘2的逆元。

#include <bits/stdc++.h>
using namespace std;

typedef long long ll;
typedef pair<int, int> pii;

const int N = 1e5 + 11;
const int M = 1e6 + 11;
const int MOD = 998244353;

ll _pow(ll a, ll b, ll c ){
    ll s =  1, base = a % c;
    while(b){
        if(b & 1) s = (s * base) % c;
        b >>= 1;
        base = base * base % c;
    }
    return s;
}

int main(int argc, char **args){ 
    int n; cin >> n;
    vector<ll> x(n + 1), y(n +  1);
    for(int i = 1; i <= n; i++){
        scanf("%lld%lld", &x[i], &y[i]);
    }

    ll sum = 0;
    for(int i = 1; i <= n; i++){
        for(int j = 1; j <= n ;j ++){
            sum += (abs(x[i] - x[j]) + abs(y[i] - y[j])) % MOD * (n - 2) % MOD;
            sum %= MOD;
         }
    }

    cout << sum  * _pow(2, MOD - 2,  MOD)% MOD<<"\n";
return 0;
}

/*

4
0 0 
1 0
0 1
1 1

*/

C

问题:求长度为n序列中,本质不同的长度为k子序列有多少个。
思路:动态规划,对于子序列的动态规划,思路也很常见。这里定义 d p [ i ] [ j ] dp[i][j] dp[i][j] 表示截止到第 i i i 个值,本质不同的长度为 j j j 的子序列个数。
51Nod 上有一个动态规划,是求长度为n序列中,本质不同的子序列有多少个。和这道题有异曲同工之妙。

Coding:

#include <bits/stdc++.h>
using namespace std;

typedef long long ll;
typedef pair<int, int> pii;

const int N = 1e3 + 11;
const int M = 1e6 + 11;
const int MOD = 1e9 + 7;

int dp[N][N], last[N]; // last[i] 表示到目前为止,i 最后一次出现的位置。
char s[N];
int main(int argc, char **args){
    int n, k; cin >> n >> k;
    scanf("%s" , s + 1);
    int len = strlen(s + 1);
    for(int i = 0; i <= len; i++) dp[i][0] = 1;

    for(int i = 1; i <= len; i++){
        for(int j = 1; j <= k; j++){
            dp[i][j] = dp[i - 1][j] + dp[i - 1][j - 1];
            if(last[s[i]] ){ // 去重复元素
                dp[i][j] -= dp[last[s[i]] - 1][j - 1];
             }
            dp[i][j] = (dp[i][j] % MOD + MOD) % MOD;
        }
        last[s[i]] = i;
    }

    cout << dp[len][k] <<"\n";
return 0;
}

D

a ∗ x + b ∗ y + c ∗ z = k a * x + b * y + c * z = k ax+by+cz=k 的解 ( x , y , z ) (x, y, z) (x,y,z) x y z 都是正整数。。
思路:扩展欧几里得算法。其实能够推理出,要想有解,当且仅当 g c d ( a , b , c ) ∣ k gcd(a, b, c ) | k gcd(a,b,c)k, 注意这里解不一定都是正整数,还可能是包含负整数。
a ∗ x + b ∗ y = k − c ∗ z a * x + b * y = k - c * z ax+by=kcz,如果 z z z 已知的话,这个就是二元不定方程,同时该方程有解,当且仅当 g c d ( a , b ) ∣ ( k − c ∗ z ) gcd(a, b) | (k - c * z) gcd(a,b)(kcz)
这里令 d = g c d ( a , b ) d = gcd(a, b) d=gcd(a,b) k − c ∗ z = m ∗ d , ( m ∈ Z + ) k - c * z = m * d , (m\in Z^+) kcz=md,(mZ+)
移项得 m ∗ d + c ∗ z = k , ( z ∈ Z + , m ∈ Z + ) m * d + c * z = k, (z \in Z^+, m \in Z^+) md+cz=k,(zZ+,mZ+),是不是很眼熟?这个也是一个二元不定方程,同理要想有解,当且仅当 g c d ( d , c ) ∣ k = > g c d ( a , b , c ) ∣ k gcd(d, c) | k =>gcd(a,b,c) |k gcd(d,c)k=>gcd(a,b,c)k
先解出第二个方程,然后将其带回求解第一个方程。

注意:很重要的一点,因为本题目求的解都是正整数,所以求解第一个不定方程时候,要尽可能让 z z z 小, m m m 尽可能大,这样第一个方程才更容易求得正整数解(这一点也很容易想: a , b a,b a,b 都是正整数 ,如果 m ∗ d m *d md 很小的话,根本不可能有正整数解 ( x , y ) (x ,y) (x,y)的)。

补充知识
如果一个二元不定方程 a ∗ x + b ∗ y = c a * x + b * y = c ax+by=c,有特解 ( x 0 , y 0 ) (x_0 , y_0) (x0,y0),则通解为
x = x 0 + b / g c d ( a , b ) ∗ t x = x_0 + b / gcd(a, b) * t x=x0+b/gcd(a,b)t
y = y 0 − b / g c d ( a , b ) ∗ t y = y_0 - b / gcd(a, b) * t y=y0b/gcd(a,b)t , ( t > = 0 ) (t >= 0) t>=0

x = x 0 − b / g c d ( a , b ) ∗ t x = x_0 - b / gcd(a, b) * t x=x0b/gcd(a,b)t
y = y 0 + b / g c d ( a , b ) ∗ t y = y_0 + b / gcd(a, b) * t y=y0+b/gcd(a,b)t , ( t > = 0 ) (t >= 0) t>=0

更详细见代码
Coding:

#include <bits/stdc++.h>
using namespace std;
  
typedef long long ll;
typedef pair<int, int> pii;
  
const int N = 1e3 + 11;
const int M = 1e6 + 11;
const int MOD = 1e9 + 7;
  
ll exgcd(ll a, ll b, ll &x, ll &y){
    if(!b) {
        x = 1; y = 0;
        return a;
    }
    ll xx, yy;
    ll g = exgcd(b, a % b, x, y);
    ll tmp = x;
    x = y;
    y = tmp - (a / b) * y;
    return g;
}
   
ll solve (ll a, ll b, ll c, ll &x, ll &y){
    ll g = exgcd(a, b, x, y);
    x = x * (c / g); y = y * (c / g);
    if(x < 0){ // 如果x < 0, 那么y一定特别大。所以就将x 增加到 尽可能小的正数,那么 y 也会降低一些,但肯定还会是正数, 因为 c 为正数。
        ll t = ceil(-x * 1.0 / (b / g));
        x = x + b / g * t;
        y = y - a / g * t;
    }
  
    if(y < 0){// 同理
        ll t = ceil(-y * 1.0 / (a / g));
        y = y + a / g * t;
        x = x - b / g * t;
    }
  
    return g;
}
ll solve_1(ll a, ll b, ll c, ll &x, ll &y){
    ll g = exgcd(a, b, x, y);
    x = x * (c / g); y = y * (c / g);
     
    if(y < 0){ // 让 z 尽可能小,这样 m 就尽可能大了
        ll t = ceil(-y * 1.0 / (a / g));
        y = y + a / g * t;
        x = x - b / g * t;
    }
    return g;
}
int main(int argc, char **args){
    // freopen("in.txt", "r", stdin);
       
    ll a, b, c, k;
    cin >> a >> b >> c >> k;
    ll x, y, z, m;
    ll d = __gcd(a, b);
    ll g = solve_1(d, c, k, m, z);
    solve(a, b, m * d, x, y);
    cout << x << " " << y <<" " << z<<"\n";
return 0;
}
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值