牛客练习赛29 题解

版权声明:本文为博主原创文章,未经博主允许不得转载。 https://blog.csdn.net/weixin_37517391/article/details/83188619

牛客练习赛29

A. 可持久化动态图上树状数组维护01背包

题解

这题跟标题没有任何关系…

贪心的使得负数删除的时候下标尽可能大,然后正数的时候下标尽可能小.

观察到每个数下标最大的时候就是它的初始下标,下标的最小值是1.

然后贪心一下就好了.

代码

#include <iostream>
using namespace std;
long long a[1000007];
int main(){
    ios::sync_with_stdio(false);
    int n;
    cin >> n;
    long long sum = 0;
    for(int i = 1;i <= n;++i){
        cin >> a[i];
        if(a[i] >= 0) sum += a[i];
        else sum += a[i] * i;
    }
    cout << sum << endl;
}

B. 列队

题解

有点迷,WA了还没改出来…QAQ

C.枇杷

题解

还没看题

D.禁止动规

题解

根据裴蜀定理,当存在一些x的组合,使得它们的gcd为1的时候, p 1 x 1 + p 2 x 2 + . . . + p n x n = 1 p_1x_1 + p_2x_2 + ... + p_nx_n = 1 ,方程一定有解.

因此我们就求 g c d ( x 1 , x 2 , . . . , x n ) = 1 gcd(x_1,x_2,...,x_n) = 1 的方案数即可

f ( x ) f(x) 表示 g c d ( x 1 , x 2 , . . . , x n ) = x gcd(x_1,x_2,...,x_n) = x 的方案数

然后莫比乌斯反演

( m x ) n = x d f ( d ) (\lfloor \frac{m}{x} \rfloor )^n = \sum_{x|d}{f(d)}

得到

f ( x ) = x d μ ( d x ) ( m d ) n f(x) = \sum_{x|d}{\mu(\frac{d}{x})*(\lfloor \frac{m}{d} \rfloor )^n}

答案就是
f ( 1 ) = d = 1 m μ ( d ) ( m d ) n f(1) = \sum_{d=1}^{m}\mu(d)*(\lfloor \frac{m}{d} \rfloor )^n

( m d ) (\lfloor \frac{m}{d} \rfloor ) 分块,然后杜教筛求莫比乌斯函数前缀和就可以了.

代码

#include <iostream>
#include <algorithm>
#include <cstring>
#include <unordered_map>
#include <map>
#define pr(x) std::cout << #x << ':' << x << std::endl
#define rep(i,a,b) for(int i = a;i <= b;++i)
#define clr(x) memset(x,0,sizeof(x))
#define setinf(x) memset(x,0x3f,sizeof(x))


typedef long long ll;
typedef unsigned long long ull;
const int N = 10000000;
ull mu[N+10];int prime[N+10],pcnt,zhi[N+10];
void sieve(){
    pcnt = 0;
    mu[1] = zhi[1] = 1;
    for(int i = 2;i <= N;++i){
        if(!zhi[i]) mu[i] = -1,prime[pcnt++] = i;
        for(int j = 0;j < pcnt && prime[j]*i <= N;++j){
            zhi[i*prime[j]] = 1;
            if(i % prime[j] == 0){
                mu[i*prime[j]] = 0;
                break;
            }
            else{
                mu[i*prime[j]] = -mu[i];
            }
        }
    }
    for(int i = 1;i <= N;++i) mu[i] += mu[i-1];
}
std::unordered_map<ll,ull> rec,vis;
ull Mu(ll x){
    if(x <= N) return mu[x];
    if(vis[x]) return rec[x];
    ull res = 1,now = x,nxt;
    while(now >= 2){
        nxt = x/(x/now+1);
        res -= (now - nxt) * Mu(x/now);
        now = nxt;
    }
    vis[x] = 1;
    return rec[x] = res;
}
ll n,m;
ull mod_pow(ull x,ll n) {
	ull res = 1;
	while(n) {
		if(n & 1)
			res *= x;
		x *= x;
		n >>= 1;
	}
	return res;
}	
int main() {
	sieve();
	unsigned long long ans = 0;
	std::cin >> n >> m;
	ll nxt;
	for(ll x = m;x >= 1;x = nxt) {
		nxt = m/(m/x+1);
		ans += mod_pow(m/x,n) * (ull)(Mu(x) - Mu(nxt));
	}
	std::cout << ans << std::endl;
	return 0;
}

E. 位运算?位运算!

题解

补过去了,用这种做法发现被卡常,然后优化了一下常数+快速读入,用了2400ms的样子过掉了.

又是一个新套路,之前不会的新套路.

考虑到要进行区间与区间或还要进行区间求和,那么我们就必须将位拆开来看.

n n 个数拆成 20 20 颗线段树,第 i i 颗线段树维护这 n n 个数上第 i i 位的信息,线段树区间 [ l , r ] [l,r] 表示 [ l , r ] [l,r] 区间内的数第 i i 位上有多少个 1 1 .
这样求和就很方便了,按位算贡献即可.

或操作,就相当于把指定线段树的指定区间覆盖成 1 1 .与操作就相当于把指定线段树的指定区间覆盖成 0 0 .这里在线段树上打 l a z y lazy 标记即可.

现在问题在于,如何进行循环移位.

我们发现循环移位就相当于线段树之间轮换儿子.

我们可以递归的找到这 20 20 个线段树在该区间下所包含的(完整线段最多 l o g n logn 段),然后将它们进行轮换.

代码

#include <iostream>
#include <algorithm>
#include <vector>
#include <cstdio>
#include <cstring>
#include <assert.h>
#define pr(x) std::cout << #x << ':' << x << std::endl
#define rep(i,a,b) for(int i = a;i <= b;++i)
const int N = 200007;
int a[N],root[20],tot;
struct node{
    int sum,lazy,lch,rch;
    node(int sum = 0,int lazy = 0,int lch = 0,int rch = 0):sum(sum),lazy(lazy),lch(lch),rch(rch){}
}ns[N*80];
void tag(int o,int len,int lazy) {
    if(!o) return ;
    if(lazy == 1)
        ns[o].sum = len;
    else
        ns[o].sum = 0;
    ns[o].lazy = lazy;
}
void pushdown(int o,int l,int r) {
    if(ns[o].lazy) {
        int mid = (l + r) >> 1;
        tag(ns[o].lch,mid-l+1,ns[o].lazy);
        tag(ns[o].rch,r-mid,ns[o].lazy);
        ns[o].lazy = 0;
    }
}
void set(int o,int l,int r,int cl,int cr,int lazy){
    if(r < cl || cr < l)
        return ;
    if(cl <= l && r <= cr){
        tag(o,r-l+1,lazy);
        return ;
    }
    int mid = (l + r) >> 1;
    pushdown(o,l,r);
    set(ns[o].lch,l,mid,cl,cr,lazy);
    set(ns[o].rch,mid+1,r,cl,cr,lazy);
    ns[o].sum = ns[ns[o].lch].sum + ns[ns[o].rch].sum;
}
 
int query(int o,int l,int r,int ql,int qr) {
    if(r < ql || qr < l)
        return 0;
    if(ql <= l && r <= qr)
        return ns[o].sum;
    int mid = (l + r) >> 1;
    pushdown(o,l,r);
    int lch = query(ns[o].lch,l,mid,ql,qr);
    int rch = query(ns[o].rch,mid+1,r,ql,qr);
    return lch + rch;
}
 
void build(int &o,int l,int r,int p) {
    if(!o)
        o = ++ tot;
    if(l == r) {
        ns[o] = node((a[l]>>p)&1,0,0,0);
        return ;
    }
    int mid = (l + r) >> 1;
    build(ns[o].lch,l,mid,p);
    build(ns[o].rch,mid+1,r,p);
    ns[o].sum = ns[ns[o].lch].sum + ns[ns[o].rch].sum;
}
 
void shift(int *os[],int l,int r,int cl,int cr,int x) {
    if(r < cl || cr < l)
        return ;
    if(cl <= l && r <= cr) {
        int tmp[20];
        rep(i,0,19)    
            tmp[i] = *os[i];
        rep(i,0,19)
            *os[i] = tmp[(i+x+20)%20];
        return ;   
    }
    int mid = (l + r) >> 1;
    rep(i,0,19)
        pushdown(*os[i],l,r);
    int *lft[20],*rgt[20];
    rep(i,0,19)
        lft[i] = (&(ns[*os[i]].lch)),rgt[i] = (&(ns[*os[i]].rch));
    shift(lft,l,mid,cl,cr,x);
    shift(rgt,mid+1,r,cl,cr,x);
    rep(i,0,19)
        ns[*os[i]].sum = ns[ns[*os[i]].lch].sum + ns[ns[*os[i]].rch].sum;
}
int n,q;
inline int read() {
    char ch = getchar(); int x = 0, f = 1;
    while(ch < '0' || ch > '9') {
        if(ch == '-') f = -1;
        ch = getchar();
    } while('0' <= ch && ch <= '9') {
        x = x * 10 + ch - '0';
        ch = getchar();
    } return x * f;
}
int main () {
    n = read();q = read();
    rep(i,1,n)
        a[i] = read();
    rep(i,0,19)
        build(root[i],1,n,i);
    rep(i,1,q) {
        int op,l,r,v;
        op = read(),l = read(),r = read(),v = read();
        if(op == 1 || op == 2) {
            int *os[20];
            rep(i,0,19)
                os[i] = &root[i];  
            shift(os,1,n,l,r,v*(op == 1?1:-1));
        }  
        else if(op == 3) {
            rep(i,0,19)
                if((v >> i) & 1)
                    set(root[i],1,n,l,r,1);
        }
        else if(op == 4) {
            rep(i,0,19)
                if(((v >> i) & 1) == 0)
                    set(root[i],1,n,l,r,-1);
        }
        else {
            long long ans = 0;
            rep(i,0,19)
                ans += query(root[i],1,n,l,r) * (1LL<<i);
            printf("%lld\n",ans);
        }
    }
}

F. 算式子

题解

对于每一个 x [ 1 , m ] x \in [1,m] ,都要计算下面的式子.
i = 1 n ( a i x + x a i ) \sum_{i=1}^{n} (\lfloor \frac{a_i}{x} \rfloor + \lfloor \frac{x}{a_i} \rfloor)

这个式子可以拆成两部分来求,即
a i x \lfloor \frac{a_i}{x} \rfloor x a i \lfloor \frac{x}{a_i} \rfloor .

第一部分容易求

x x 固定时计算介于 [ x , 2 x ) , [ 2 x , 3 x ) . . . . [x,2x),[2x,3x).... a i a_i 有多少个,讲贡献计入 a n s 1 [ x ] ans_1[x] .

第二部分就不那么直观了

我们设 a n s 2 [ x ] = i = 1 n x a i ans_2[x] = \sum_{i=1}^n \lfloor \frac{x}{a_i} \rfloor

对于每一个 a i a_i 其中 x [ a i , 2 a i ) x \in [a_i,2a_i) a n s 2 ans_2 应该得到影响 1 1 ,所以我应该给 a n s 2 [ a i , 2 a i ) ans_2[a_i,2a_i) 数组进行区间 + 1 +1 .而 x [ a i , 2 a i ) x \in [a_i,2a_i) 的应该得到影响 2 2 ,所以我应该给 a n s 2 [ 2 a i , 3 a i ) ans_2[2a_i,3a_i) 数组进行区间 + 2 +2 …依次类推…
具体实现的话,我们线段 a n s 2 ans_2 求一个差分数组 d e l t a delta ,那么我对于 a n s 2 ans_2 [ l , r ] [l,r] 区间 + d +d ,就相当于对差分数组 d e l t a [ l ] + = d , d e l t a [ r + 1 ] = d delta[l]+=d,delta[r+1]-=d ,最后,对差分数组求前缀和就可以恢复成 a n s 2 ans_2 数组.

总的时间复杂度 O ( n l o g n ) O(nlogn)

代码

#include <iostream>

const int N = 2000007;
int n,m;
long long a[N];
long long num[N],cnt[N],cnt2[N];
long long ans[N];
int main() {
	std::ios::sync_with_stdio(false);
	std::cin >> n >> m;
	for(int i = 0;i < n;++i) {
		std::cin >> a[i];
		cnt[a[i]] ++ ;
		num[a[i]] ++ ;
	}
	
	for(int i = 0;i <= m;++i) 
		cnt[i] += cnt[i-1];

	long long res = 0;

	for(int x = 1;x <= m;++x) {
		int y = 2*x;
		for(;y <= m;y += x) {
			ans[x] += (cnt[y-1] - cnt[y-x-1])*((y-1)/x);
		}
		ans[x] += (cnt[m] - cnt[y-x-1])*(cnt[m]/x);
	}
	for(int i = 1;i <= m;++i) {
		cnt2[i] += num[i];
		for(int j = 2*i;j <= m;j += i) {
			cnt2[j] += num[i];
		}
	}
	for(int x = 1;x <= m;++x) {
		cnt2[x] += cnt2[x-1];
		ans[x] += cnt2[x];
		res ^= ans[x];
	}
	std::cout << res << std::endl;
	return 0;
}

猜你喜欢

转载自blog.csdn.net/weixin_37517391/article/details/83188619