Divide by Zero 2021 and Codeforces Round #714 (Div. 2) BCD题

位运算/构造 B题

题意:
给定一个长度为n(n>=2)的数组a,你可以将a进行不同的排序,
要求排序后结果满足 :
在这里插入图片描述
(1 <= i <= n-1)

比如数组长度为5,则要满足
在这里插入图片描述
每次问能满足条件的排序方式有多少种。
(种数太多要对1e9+7取模)

解析:
如果满足数组中最小的数>=2,则可以让a1=an=min,在2 - n-1之间随意排序都满足
但是还有一点,如果所有数&起来小于min,则也无解

ll t,n,m;
ll a[N];
int main(){
    scanf("%lld",&t);
    while(t--){
        scanf("%lld",&n);
        ll tmp = 1;
        ll y = -1;
        for(int i=0;i<n;i++){
            scanf("%lld",&a[i]);
            y &= a[i];
        }

        for(ll i=1;i<=n-2;i++){
            tmp = tmp * i % mod;
        }
        ll cnt = 0;
        for(int i=0;i<n;i++) cnt += (a[i] == y);
        ll res = 0;
        res = (res + cnt * (cnt - 1) % mod * tmp % mod) % mod;
        printf("%lld\n", res);
    }
    
    return 0;
}

动态规划 C题

题意:
给定n,m,n代表要修改的数,m代表修改的次数
每次修改,对n的每一位替换成对那位加1后的数,
如 1912 修改一次后变成 21023,1->2 , 9->10 , 1->2 , 2->3
求对n进行m次修改后,最后的n有几位。 (位数太大要对1e9+7取模)

解析:
dp
在这里插入图片描述

int t,n,m;
int f[M][10];
int main(){
    scanf("%d",&t);
    for(int i=0;i<=9;i++) f[0][i] = 1;
    for(int i=1;i<M;i++){
        for(int j=0;j<9;j++){
            f[i][j] = f[i-1][j+1];
        }
        f[i][9] = (f[i-1][0] + f[i-1][1]) % mod;
    }
    while(t--){
        scanf("%d%d",&n,&m);
        ll res = 0;
        while(n){
            res = (res + f[m][n % 10]) % mod;
            n /= 10;
        }
        printf("%lld\n", res);
    }
    return 0;
}

最小生成树/并查集 D题

题意:
给定长度为n(n>=2)的数组a,和一个数 p,
考虑建一个顶点为n的无向图,
指定如下规则:
① 如果 ,那么就可以在顶点i 和 j 之间建一条权值为的边
② 如果 i + 1 = j 的话,那么在顶点 i 和 j 之间可以建一条权值为p的边

注意,顶点i和j之间可以建多条边,也就是两条规则可以同时生效,当然,如果两条都不满足,则顶点i和j之间无边。
目标是:输出这个图的最小生成树的总权值。

解析:
按照kruskal算法的思路想。
按照a[i]从小到大排序,
从前往后遍历,如果当前的a[i] 比 p小,那么就 向左遍历一遍,向右遍历一遍,
比如考虑向左遍历的情况,
如果它左边的这个数能整除它,那么这条边就是图里的最短边,直接给 res 加上a[i]
之后再往左走,一旦某个数不能整除它,则之后gcd也都不成立了,直接break
往右边走也一样同理
注意如果某两个点已经被一条最短的边连接起来了,则也直接break,用vis数组存储,之所以可以这样,因为这题是成段的,所以不用并查集也可以做。

遍历的操作因为会标记已加入集合的边,所以是O(n)的
排序复杂度是O(nlogn) 所以总复杂度是O(nlogn)

int t,n,m;
int a[N];
bool vis[N];
int main(){
    scanf("%d",&t);
    while(t--){
        scanf("%d %d",&n,&m);
        vector<pii> v;
        for(int i=0;i<n;i++) {
            scanf("%d",&a[i]);
            v.push_back({a[i],i});//pair<权值,在a中的下标>
        }
        sort(v.begin(),v.end());
        ll res = 0;
        for(auto t:v){
            int value = t.first;
            int id = t.second;
            if(value >= m) break;//如果当前最小的权值都大于等于m了就直接break
            while(id > 0){
                if(vis[id-1]) break;//第i-1个点与第i个点之间已有边
                if(a[id-1] % value != 0) break;//如果不满足gcd
                vis[id - 1] = true;
                res += value;
                id--;
            }
            id = t.second;
            while(id < n-1){
                if(vis[id]) break;//第i个点与第i+1个点之间已有边
                if(a[id+1] % value != 0) break;//如果不满足gcd
                vis[id] = true;
                res += value;
                id++;
            }
        }
        for(int i=0;i<n-1;i++){
            if(!vis[i]) res += m;//如果还有没有加入集合的,就用m连接起来,res加上m
        }
        printf("%lld\n", res);
        for(int i=0;i<n-1;i++) vis[i] = 0;
    }
    
    return 0;
}
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值