【解题报告】随便练练二(CF 2300)

本文介绍了动态规划和数据结构在解决计算机竞赛题目中的应用,如Antimatter问题、PhysicalEducationLessons问题,涉及离散化、线段树、珂朵莉树等方法。同时,文章讲解了贪心策略和状态压缩DP在TeamBuilding问题中的运用,以及莫比乌斯反演在处理约数计数问题中的作用。最后,探讨了如何快速计算满足特定条件的括号序列数量。
摘要由CSDN通过智能技术生成

A:Antimatter | CF383D

题意

  • Antimatter
    给定一个长度为 a a a 的序列 a n a_n an
    问你有多少个子区间 [ L , R ] [L,R] [L,R],让这个子区间内每个元素选择 a i a_i ai 或者 − a i -a_i ai,满足这个子区间的元素和为 0 0 0
  • 1 ≤ a i , n ≤ 1000 1\le a_i,n\le 1000 1ai,n1000
    ∑ a i ≤ 1 0 4 \sum a_i\le 10^4 ai104

思路 :DP

  • 看到 n × ∑ a i ≤ 1 0 7 n\times \sum a_i\le 10^7 n×ai107,自然会想到用 d p dp dp 记录前缀和去做
    子区间的元素值和为 0 0 0,就是问有多少个前缀和减一减等于 0 0 0
    但是这样子是 不 正 确 的 \color{red}{不正确的} 。因为我们选正与选负只取决于子区间内的元素,与子区间外的元素无关
  • 我们记录 d p [ i ] [ j ] dp[i][j] dp[i][j],表示子区间左端点为 1 ∼ i 1\sim i 1i,能否获得从某一位置开始的前缀和 j j j
    转移也很简单。一个是从之前的一个和转移过来,一个是从当前位置作为新的左端点转移过来。

代码

  • 时间复杂度: O ( n × ∑ a i ) O(n\times \sum a_i) O(n×ai)
#include<bits/stdc++.h>
using namespace std;
void show(){std::cerr << endl;}template<typename T,typename... Args>void show(T x,Args... args){std::cerr << "[ " << x <<  " ] , ";show(args...);}
#define ll long long
const int MAX = 2e4+50;
const int MOD = 1e9+7;
const ll LINF = 0x3f3f3f3f3f3f3f3f;

int aa[MAX];
ll dp[MAX][2];
const int ORI = 1e4;
const int st = 0;
int main()
{
    int n;cin >> n;
    ll ans = 0;
    for(int i = 1;i <= n;++i){
        cin >> aa[i];

        for(int j = 0;j <= 2e4;++j){
            if(j-aa[i] >= 0)   dp[j][st^1] = (dp[j][st^1] + dp[j-aa[i]][st]) % MOD  ;
            if(j+aa[i] <= 2e4) dp[j][st^1] = (dp[j][st^1] + dp[j+aa[i]][st]) % MOD;
        }
        dp[ORI+aa[i]][st^1] ++;
        dp[ORI-aa[i]][st^1] ++;
        for(int j = 0;j <= 2e4;++j){
            dp[j][st] = dp[j][st^1];
            dp[j][st^1] = 0;
        }
        ans = (ans + dp[ORI][st]) % MOD;
//        show(i,ans);
    }
    cout << ans;
    return 0;
}

B:Physical Education Lessons | CF915E

题意

  • Physical Education Lessons
    n n n 个位置,一开始全为 1 1 1。操作 q q q次。有两种操作。
    一种区间置 1 1 1,一种区间置 0 0 0。每次操作后你需要给出现在所有位置中有多少个 0 0 0
    1 ≤ n ≤ 1 0 9 1\le n\le 10^9 1n109
    1 ≤ q ≤ 3 ⋅ 1 0 5 1\le q\le 3\cdot 10^5 1q3105

思路一:离散化+线段树

  • (第一种做法)把操作序列中的所有值排个序。然后比如有 {2,3,5,10},若两个值之间不是相邻的,我们便加上一个点,管理这一段区间。复杂度 O ( 3 q log ⁡ ( 3 q ) ) O(3q\log (3q)) O(3qlog(3q)),然后就 T L E \color{red}{TLE} TLE
  • (第二种做法)我们令操作序列变为左闭右开。也就是对于区间置 [ L , R ] [L,R] [L,R],我们相当于变成区间置 [ L , R + 1 ) [L,R+1) [L,R+1)。这样,离散化之后就只多加了两个点: L , R + 1 L,R+1 L,R+1。排序后,我们每个点相当于掌管了 M [ i + 1 ] − M [ i ] M[i+1]-M[i] M[i+1]M[i] 个点。
    区间置,只要让右端点的离散化下标 − 1 -1 1即可。

代码: 800 M s 800Ms 800Ms

  • 时间复杂度: O ( 2 q log ⁡ ( 2 q ) ) O(2q\log (2q)) O(2qlog(2q))
#include<bits/stdc++.h>
using namespace std;
void show(){std::cerr << endl;}template<typename T,typename... Args>void show(T x,Args... args){std::cerr << "[ " << x <<  " ] , ";show(args...);}
#define ls (p<<1)
#define rs (p<<1|1)
#define md ((l+r)>>1)
#define ll long long
const int MAX = 6e5+50;

int cnt[MAX];
int sum[MAX*4];
int shu[MAX*4];
int tag[MAX*4];
inline void push_up(int p){
    sum[p]=sum[ls]+sum[rs];
}
inline void build(int p,int l,int r){
    tag[p] = -1;
    if(l == r){
        sum[p] = shu[p] = cnt[l];
        return;
    }
    build(ls,l,md);
    build(rs,md+1,r);
    shu[p] = shu[ls] + shu[rs];
    push_up(p);
}
inline void add(int p,int l,int r,int k){
    tag[p] = k;
    sum[p] = k * shu[p];
}
inline void push_down(int p,int l,int r){
    if(~tag[p]){
        add(ls,l,md,tag[p]);
        add(rs,md+1,r,tag[p]);
        tag[p] = -1;
    }
}
inline void update(int p,int l,int r,int ux,int uy,int k){
    if(ux <= l && uy >= r){
        add(p,l,r,k);
        return;
    }
    push_down(p,l,r);
    if(ux <= md)update(ls,l,md,ux,uy,k);
    if(uy >  md)update(rs,md+1,r,ux,uy,k);
    push_up(p);
}
struct node{
    int l,r,k;
}aa[MAX];
int M[MAX];
map<int,int>M2;
int main()
{
    int n,q;
    n = read();q = read();
    int ci = 0;
    for(int i = 1;i <= q;++i){
        aa[i].l = read();
        aa[i].r = read() + 1;
        aa[i].k = read() - 1;
        M[++ci] = aa[i].l;
        M[++ci] = aa[i].r;

    }
    M[++ci] = 1;
    M[++ci] = n + 1;
    sort(M+1,M+ci+1);
    int m = unique(M+1,M+ci+1) - (M+1);
    for(int i = 1;i <= m;++i){
        M2[M[i]] = i;
        if(i != m)cnt[i] = M[i+1] - M[i];
    }
    build(1,1,m-1);
    for(int i = 1;i <= q;++i){
        update(1,1,m-1,M2[aa[i].l],M2[aa[i].r]-1,aa[i].k);
        Print(sum[1],'\n');
    }
    Write();
    return 0;
}

思路二:珂朵莉树

  • 嗯?全是区间置?那不是珂朵莉狂喜?直角交一发裸的珂朵莉:
#include<bits/stdc++.h>
using namespace std;
void show(){std::cerr << endl;}template<typename T,typename... Args>void show(T x,Args... args){std::cerr << "[ " << x <<  " ] , ";show(args...);}
#define ls (p<<1)
#define rs (p<<1|1)
#define md ((l+r)>>1)
#define ll long long
const int MAX = 6e5+50;

struct node{
    int l,r;
    mutable int val;
    bool operator<(const node &a)const {return l<a.l;}
    node(int L,int R,ll Val):l(L),r(R),val(Val){}
    node(int L):l(L){}
};

set<node> s;
using  si = set<node>::iterator;

si split(int pos){
    si it = s.lower_bound(node(pos));
    if(it != s.end() && it->l==pos) return it;
    --it;
    int l=it->l,r=it->r;
    ll val = it->val;
    s.erase(it);
    s.insert(node(l,pos-1,val));
    return s.insert(node(pos,r,val)).first;
}

void assign(int l,int r,int val){
    si itr=split(r+1),itl=split(l);
    s.erase(itl,itr);
    s.insert(node(l,r,val));
}

void add(int l,int r,int val){
    si itr=split(r+1),itl=split(l);
    for(si it=itl;it!=itr;++it)
        it->val += val;
}

int query(int l,int r){
    si itr=split(r+1),itl=split(l);
    int res(0);
    for(si it=itl;it!=itr;++it)
        res=res+(it->r-it->l+1) * it->val;
    return res;
}
int lef[MAX],rr[MAX],kk[MAX];
int tmp[MAX];
int main() {
    int n,q;
    n = read();q = read();
    s.insert((node){1,n,1});
    for(int i = 1;i <= q;++i){
        lef[i] = read();rr[i] = read();kk[i] = read();
        assign(lef[i],rr[i],kk[i]-1);
        Print(query(1,n),'\n');
    }
    Write();
    return 0;
}
  • 但是结果是 T L E   30 \color{red}{TLE\ 30} TLE 30
    考虑到,我们每次求和都是求和所有位置的元素和。但是我们操作只操作了一部分,也就是说根据我们操作的这一段改变了多少值,我们直接就知道所有位置元素和的变化值。

代码: 300 M s 300Ms 300Ms

  • 时间复杂度: O ( q log ⁡ q ) O(q\log q) O(qlogq)
#include<bits/stdc++.h>
using namespace std;
void show(){std::cerr << endl;}template<typename T,typename... Args>void show(T x,Args... args){std::cerr << "[ " << x <<  " ] , ";show(args...);}
#define ls (p<<1)
#define rs (p<<1|1)
#define md ((l+r)>>1)
#define ll long long
const int MAX = 6e5+50;

int sum;

struct node{
    int l,r;
    mutable int val;
    bool operator<(const node &a)const {return l<a.l;}
    node(int L,int R,ll Val):l(L),r(R),val(Val){}
    node(int L):l(L){}
};

set<node> s;
using  si = set<node>::iterator;

si split(int pos){
    si it = s.lower_bound(node(pos));
    if(it != s.end() && it->l==pos) return it;
    --it;
    int l=it->l,r=it->r;
    ll val = it->val;
    s.erase(it);
    s.insert(node(l,pos-1,val));
    return s.insert(node(pos,r,val)).first;
}

void assign(int l,int r,int val){
    si itr=split(r+1),itl=split(l);
    for(si i = itl;i != itr;++i)sum -= i->val * (i->r - i->l+1);		// 直接修改 sum
    s.erase(itl,itr);
    s.insert(node(l,r,val));
    sum += val * (r - l + 1);
}

void add(int l,int r,int val){
    si itr=split(r+1),itl=split(l);
    for(si it=itl;it!=itr;++it)
        it->val += val;
}

int query(int l,int r){
    si itr=split(r+1),itl=split(l);
    int res(0);
    for(si it=itl;it!=itr;++it)
        res=res+(it->r-it->l+1) * it->val;
    return res;
}
int lef[MAX],rr[MAX],kk[MAX];
int tmp[MAX];
int main() {
    int n,q;
    n = read();q = read();
    s.insert((node){1,n,1});
    sum = n;
    for(int i = 1;i <= q;++i){
        lef[i] = read();rr[i] = read();kk[i] = read();
        assign(lef[i],rr[i],kk[i]-1);
        Print(sum,'\n');
    }
    Write();
    return 0;
}

C:Team Building | CF1316E

题意

  • Team Building
    n n n 个人,你要选 p p p 个人,每个人成为 p p p 个位置之一,你还要选 k k k 个人成为观众
    现在给定了 b i , j b_{i,j} bi,j 表示第 i i i 个人担任第 j j j 个位置的收益,和 a i a_i ai 表示第 i i i 个人担任观众的收益
    求最大收益
  • 2 ≤ n ≤ 1 0 5 2\le n\le 10^5 2n105
    1 ≤ p ≤ 7 1\le p\le 7 1p7
    1 ≤ p , p + k ≤ n 1\le p,p+k\le n 1p,p+kn
    1 ≤ a i , b i , j ≤ 1 0 9 1\le a_i,b_{i,j}\le 10^9 1ai,bi,j109

思路:贪心 + 状压DP

  • 如果 p = 1 p=1 p=1,且两个位置没有选用人的上限,那么自然根据贪心,每个人哪个位置优选哪个
  • 如果 p = 1 p=1 p=1,且第一个位置只能选一个人,第二个位置没有选用人的上限,那么记录 d p [ i ] [ 0 / 1 ] dp[i][0/1] dp[i][0/1] 表示选完了前 i i i 个人,且选了 / 没有选第一个位置的人,最大收益是多少
  • 如果 p = 1 p=1 p=1,且第一个位置只能选一个人,第二个位置只能选 k k k 个人。那该怎么办好呢?
    我们可以简单地想到。如果选定了某个人当第一个位置,那么剩下的所有人可以根据第二个位置的权值进行排序,然后贪心地选前 k k k 个当做答案。
    但是这么做肯定会 T T T 掉,考虑如何加速或者转变这个过程。我们可以先按照第二个位置的值降序排序
    那么我们可以根据现在的位置 i i i 和到底有没有选第一个位置 0 / 1 0/1 0/1 进行判断,该人的第二个位置的值是否应该加上。
  • 那么思路就很清晰了。考虑 p ≤ 7 p\le 7 p7,我们设一个 d p [ i ] [ 2 7 ] dp[i][2^7] dp[i][27] ,表示考虑完前 i i i 个人,且前 p p p 个位置选择的情况为 S S S。这样,根据 i − p o p c n t ( S ) i-popcnt(S) ipopcnt(S) 就可以判断这个人是否应该选为观众。
    注意这样内存可能会比较大,滚动数组一下即可。

代码

  • 时间复杂度: O ( p n 2 p + n log ⁡ n ) O(pn2^p+n\log n) O(pn2p+nlogn)
#include<bits/stdc++.h>
using namespace std;
void show(){std::cerr << endl;}template<typename T,typename... Args>void show(T x,Args... args){std::cerr << "[ " << x <<  " ] , ";show(args...);}
#define ll long long
const int MAX = 1e5+50;
const ll LINF = 0x3f3f3f3f3f3f3f3f;
const int st = 0;

ll dp[1<<8][2];
struct node{
    int val,oth[10];
    bool operator <(const node &ND)const{
        return val > ND.val;
    }
}aa[MAX];
int main() {
    int n,p,k;
    scanf("%d%d%d",&n,&p,&k);
    for(int i = 1;i <= n;++i){
        scanf("%d",&aa[i]);
    }
    for(int i = 1;i <= n;++i){
        for(int j = 1;j <= p;++j){
            scanf("%d",&aa[i].oth[j]);
        }
    }
    sort(aa+1,aa+1+n);
    dp[0][st] = 0;
    dp[0][st^1] = 0;
    for(int j = 1,ed = (1<<p);j < ed;++j){
        dp[j][st] = -LINF;
        dp[j][st^1] = -LINF;
    }
    for(int i = 1;i <= n;++i){
        for(int j = 0,ed = (1<<p);j < ed;++j){
            int cnt = 0;
            for(int q = 0;q < p;++q){
                if(j & (1<<q)){
                    cnt++;
                    continue;
                }
                dp[j|(1<<q)][st^1] = max(dp[j|(1<<q)][st^1],dp[j][st] + aa[i].oth[q+1]);
            }
            if(i - cnt <= k){		// 这个人可以选为观众
                dp[j][st^1] = max(dp[j][st^1],dp[j][st] + aa[i].val);
            }else{
                dp[j][st^1] = max(dp[j][st^1],dp[j][st]);
            }
        }
        for(int j = 0,ed = (1<<p);j < ed;++j){
            dp[j][st] = dp[j][st^1];
            dp[j][st^1] = -LINF;
        }
    }

    printf("%lld",dp[(1<<p)-1][st]);

    return 0;
}

D:Asterism (Hard Version) | CF1371E2

题意

  • Asterism (Hard Version)
    给你一个长度为 n n n 的数组 a n a_n an 和一个质数 p p p
    假设你一开始有数字 x x x。假设选择了 a n a_n an 的一个排列 b n b_n bn
    然后一个位置一个位置过去比较。如果 x ≥ b i x\ge b_i xbi,你就赢了他,并且你的 x = x + 1 x=x+1 x=x+1
    否则,你就输了
  • f ( x ) f(x) f(x) 表示你一开始有数字 x x x,有多少个全排列 b n b_n bn 满足你能赢所有人。
    如果 p ∤ f ( x ) p\not| f(x) pf(x),那么说明 f ( x ) f(x) f(x) 是好的。
    请输出所有好的的 f ( x ) f(x) f(x)
  • 2 ≤ p ≤ n ≤ 1 0 5 2\le p\le n\le 10^5 2pn105
    1 ≤ a i ≤ 1 0 9 1\le a_i\le 10^9 1ai109

思路

  • 首先令 m x = max ⁡ { a i } mx=\max\{a_i\} mx=max{ai}
    容易得到合法的 x x x 必须要满足 x ∈ [ m x − n + 1 , m x − 1 ] x\in[mx-n+1,mx-1] x[mxn+1,mx1]
    因为若 x ≥ m x x\ge mx xmx,那么容易得到 f ( x ) = n ! f(x)=n! f(x)=n!,肯定有 p ∣ n ! p|n! pn! 因为 p ≤ n p\le n pn
  • 我们令 c i c_i ci 表示小于等于 i i i 的数的个数。
    首先,我们的分数为 x x x,全排列的第一个位置的可选方案数有 c x c_x cx
    然后,我们赢了,第二个位置的可选方案数有 c x + 1 − 1 c_{x+1}-1 cx+11
    以此类推,我们得到:
    f ( x ) = ∏ i = x x + n − 1 c i − ( i − x ) f(x)=\prod_{i=x}^{x+n-1}c_i-(i-x) f(x)=i=xx+n1ci(ix)
    这个式子可以通过 e a s y easy easy 版的复杂度,但是这里仍然不行。
  • 考虑到,我们必须要枚举所有的 f ( x ) , x ∈ [ m x − n + 1 , m x − 1 ] f(x),x\in[mx-n+1,mx-1] f(x),x[mxn+1,mx1],所以每个 f ( x ) f(x) f(x) 的判别需快速判断是否是 p p p 的倍数,即是否存在因子 p p p
    由于 p p p 是一个质数。若 c i − ( i − x ) c_i-(i-x) ci(ix) p p p 的倍数,那么 f ( x ) f(x) f(x) 就是 p p p 的倍数了。
    c i − ( i − x ) ≡ 0 ( m o d p ) c_i-(i-x)\equiv 0\pmod p ci(ix)0(modp)

    x ≡ i − c i ( m o d p ) x\equiv i-c_i\pmod p xici(modp)
    首先我们需要预处理好所有的 c i c_i ci。注意到 a i a_i ai 特别大,且 x x x 也是比较大的。
    我们在同余式子两边同时减掉相同的数字,同余式子还是成立的。所以我们式子两边同时减去 m x − n mx-n mxn ,这样 c i c_i ci 的范围变最大为 m x − ( m x − n ) = n mx-(mx-n)=n mx(mxn)=n,用一个桶即可。
  • 然后对于第一个 f ( x ) f(x) f(x),本来是 f ( m x − n + 1 ) f(mx-n+1) f(mxn+1),偏移之后就变成了 f ( 1 ) f(1) f(1),我们便统计所有 i ∈ [ 1 , n ] i\in[1,n] i[1,n] i − c i i-c_i ici p p p 下的值是多少(注意这里 c i c_i ci 提前偏移过了),用一个桶记录。
  • 对于后面的 f ( x ) f(x) f(x),容易发现就是增加了一个 ( x + n ) − c x + n (x+n)-c_{x+n} (x+n)cx+n,减少了一个 x − c x x-c_x xcx,简单递推即可

代码

  • 时间复杂度: O ( n ) O(n) O(n)
#include<bits/stdc++.h>
using namespace std;
void show(){std::cerr << endl;}template<typename T,typename... Args>void show(T x,Args... args){std::cerr << "[ " << x <<  " ] , ";show(args...);}
#define ll long long
const int MAX = 2e5+50;
const int MOD = 1e9+7;
const ll LINF = 0x3f3f3f3f3f3f3f3f;

int mod(int a,int p){
    return (a % p + p) % p;
}

int main() {
    int n,p;
    cin >> n >> p;
    vector<int>aa(n),bb(2*n);
    for(int i = 0;i < n;++i){
        cin >> aa[i];
    }
    int mx = *max_element(aa.begin(),aa.end());
    for(int i = 0;i < n;++i)
        bb[max(0,aa[i]-(mx-n))]++;
    for(int i = 1;i < 2 * n;++i)
        bb[i] += bb[i-1];

    vector<int> ff(n);
    const int ORI = mx - n;
    for(int i = 1;i <= n;++i)
        ff[mod(i+ORI-bb[i],p)]++;

    vector<int>res;
    for(int i = 1;i < n;++i){
        if(ff[mod(i+ORI,p)] == 0)res.push_back(i+ORI);
        ff[mod(i+ORI-bb[i],p)]--;
        ff[mod(n+i+ORI-bb[n+i],p)]++;
    }
    cout << res.size() << endl;
    for(auto it : res){
        cout << it << " ";
    }
    return 0;
}

E:Mike and Foam | CF547C

题意

  • Mike and Foam
    一开始是一个空多重集合。每次添加进去一个元素或者从集合中删除一个元素。
    每次操作完之后,询问有多少对元素 x , y x,y x,y,满足 gcd ⁡ ( x , y ) = 1 \gcd(x,y)=1 gcd(x,y)=1 (对 x , y x,y x,y 与对 y , x y,x y,x 算同一对)
    元素值 ≤ 5 ⋅ 1 0 5 \le 5\cdot 10^5 5105
    操作数 ≤ 2 ⋅ 1 0 5 \le 2\cdot10^5 2105

思路:莫比乌斯反演

  • 用直接的容斥,判断每个质因子是否相同,但是感觉挺复杂的…
    我们直接使用莫比乌斯反演去做,更好理解
    t m p = ∑ i = 1 n ∑ j = 1 n [ gcd ⁡ ( a i , a j ) = 1 ] \begin{aligned} tmp&=\sum_{i=1}^n \sum_{j=1}^n [\gcd(a_i,a_j)=1] \end{aligned} tmp=i=1nj=1n[gcd(ai,aj)=1]
  • 但是这个 a i a_i ai 感觉不是很好弄。弄下标不好弄,我们就弄值。
    c i c_i ci 表示有多少个数字的值为 i i i,记 n = max ⁡ { a i } n=\max\{a_i\} n=max{ai},于是我们有:
    t m p = ∑ i = 1 n ∑ j = 1 n [ gcd ⁡ ( i , j ) = 1 ] × c i c j = ∑ i = 1 n ∑ j = 1 n ∑ d ∣ gcd ⁡ ( i , j ) μ ( d ) × c i c j = ∑ d = 1 n μ ( d ) ( ∑ i = 1 ⌊ n i ⌋ c i d ) 2 \begin{aligned} tmp&=\sum_{i=1}^n \sum_{j=1}^n [\gcd(i,j)=1] \times c_ic_j\\ &=\sum_{i=1}^n \sum_{j=1}^n \sum_{d|\gcd(i,j)} \mu(d) \times c_ic_j\\ &=\sum_{d=1}^n\mu(d) \Big(\sum_{i=1}^{\lfloor \frac{n}{i}\rfloor}c_{id} \Big)^2\\ \end{aligned} tmp=i=1nj=1n[gcd(i,j)=1]×cicj=i=1nj=1ndgcd(i,j)μ(d)×cicj=d=1nμ(d)(i=1incid)2
    我们记
    f ( d ) = ∑ i = 1 ⌊ n i ⌋ c i d f(d)=\sum_{i=1}^{\lfloor \frac{n}{i}\rfloor}c_{id} f(d)=i=1incid
    所以得到 t m p = ∑ d = 1 n μ ( d ) f 2 ( d ) tmp=\sum_{d=1}^n\mu(d) f^2(d) tmp=d=1nμ(d)f2(d)
    由于对称性,最后我们的答案为 a n s = ( t m p − c 1 ) / 2 ans=(tmp-c_1)/2 ans=(tmpc1)/2
  • 我们发现,在 x ≤ 5 ⋅ 1 0 5 x\le 5\cdot 10^5 x5105 范围之内,数字最多就只有 200 200 200 个因子。所以修改的次数不会太多。我们可以使用 O ( n log ⁡ n ) O(n\log n) O(nlogn) 的埃筛预处理好所有数字的因子。
    每次只会修改一些 f ( d ) f(d) f(d),根据是增还是删,直接去修改即可。

代码

  • 时间复杂度: O ( q log ⁡ n ) O(q\log n) O(qlogn)
#include<bits/stdc++.h>
using namespace std;
void show(){std::cerr << endl;}template<typename T,typename... Args>void show(T x,Args... args){std::cerr << "[ " << x <<  " ] , ";show(args...);}
#define ll long long
const int MAX = 5e5+50;

int cnt;
int pri[MAX];
bool vis[MAX];
vector<int>V[MAX];
void shai(int n){
    for(int i = 1;i <= n;++i){
        for(int j = i;j <= n;j+=i){
            V[j].push_back(i);
        }
    }
}
int mu[MAX];
void Mu(int n){
    mu[1] = 1;
    for(int i = 2;i <= n;++i){
        if(!vis[i]){
            pri[++cnt] = i;
            mu[i] = -1;
        }
        for(int j = 1;j <= cnt && i * pri[j] <= n;++j){
            vis[i * pri[j]] = 1;
            if(i % pri[j] == 0){
                mu[i * pri[j]] = 0;
                break;
            }else{
                mu[i * pri[j]] = -mu[i];
            }
        }
    }
}

int aa[MAX];
int shu[MAX];
ll f[MAX];
bool you[MAX];
int main() {
    Mu(5e5);
    shai(5e5);

    int n,m;
    scanf("%d%d",&n,&m);
    for(int i = 1;i <= n;++i)scanf("%d",&aa[i]);
    ll ans = 0;
    for(int i = 1;i <= m;++i){
        int pos,t;scanf("%d",&pos);
        t = aa[pos];
        for(auto it : V[t]){
            int d = t / it;
            ans -= mu[d] * f[d] * f[d];
            if(!you[pos])f[d]++;
            else f[d]--;
            ans += mu[d] * f[d] * f[d];
        }

        if(you[pos])shu[t]--;
        else shu[t]++;
        you[pos] ^= 1;
        printf("%lld\n",(ans - shu[1]) / 2);
    }
    return 0;
}

F:Anton and School - 2 | CF785D

题意

  • Anton and School - 2
    我们定义常规简单括号,就是若括号序列长度为 2 n 2n 2n,那么这个括号序列前长度为 n n n 的括号都是左括号,后面 n n n 个括号都是右括号,且括号非空。
    给定一个长度为 n n n 的括号序列。问你有多少个子序列,满足子序列是常规简单括号
    答案取模 1 e 9 + 7 1e9+7 1e9+7
    1 ≤ n ≤ 2 ⋅ 1 0 5 1\le n\le 2\cdot10^5 1n2105

思路:组合数学,范德蒙德公式优化

  • 首先,我们为了让枚举不重复,让所有左括号的最右边的位置在 i i i 处。
    所以,在 1 ∼ i 1\sim i 1i 处设有 a a a 个左括号, i + 1 ∼ n i+1\sim n i+1n 处设有 b b b 个右括号。
    那么这个时候,我们到底有多少种合法的方案呢?
    首先初步的想法,就是我们括号序列长度为 2 i 2i 2i,即左边选择 i i i 个左括号,右边选择 i i i 个有括号,方案数即为:
    ∑ i = 1 n C a i × C b i \sum_{i=1}^n C_a^i \times C_b^i i=1nCai×Cbi
    怎么化简呢?这不就是之前组合数学中学过的范德蒙德公式嘛,直接这样:
    ∑ i = 1 ∞ C a i × C b i = ∑ i = 1 ∞ C a a − i × C b i = C a + b a − C a + b 0 \begin{aligned} &\sum_{i=1}^\infin C_a^i \times C_b^i\\ &=\sum_{i=1}^\infin C_a^{a-i} \times C_b^i\\ &=C_{a+b}^a-C_{a+b}^0\\ \end{aligned} i=1Cai×Cbi=i=1Caai×Cbi=Ca+baCa+b0
    但是我们没有考虑到,我们必须要选择最右边的左括号,也就是式子稍微变一下:
    ∑ i = 1 ∞ C a − 1 i − 1 × C b i = ∑ i = 1 ∞ C a − 1 a − i × C b i = C a + b − 1 a \begin{aligned} &\sum_{i=1}^\infin C_{a-1}^{i-1} \times C_b^i\\ &=\sum_{i=1}^\infin C_{a-1}^{a-i} \times C_b^i\\ &=C_{a+b-1}^a\\ \end{aligned} i=1Ca1i1×Cbi=i=1Ca1ai×Cbi=Ca+b1a

代码

  • 时间复杂度: O ( n ) O(n) O(n)
#include<bits/stdc++.h>
using namespace std;
void show(){std::cerr << endl;}template<typename T,typename... Args>void show(T x,Args... args){std::cerr << "[ " << x <<  " ] , ";show(args...);}
#define ll long long
const int MAX = 2e5+50;
const int MOD = 1e9+7;
const ll LINF = 0x3f3f3f3f3f3f3f3f;

ll qpow(ll a,ll n){/* */ll res = 1LL;while(n){if(n&1)res=res*a%MOD;a=a*a%MOD;n>>=1;}return res;}
ll qpow(ll a,ll n,ll p){a%=p;ll res = 1LL;while(n){if(n&1)res=res*a%p;a=a*a%p;n>>=1;}return res;}
ll npow(ll a,ll n){/* */ll res = 1LL;while(n){if(n&1)res=res*a;a=a*a;n>>=1;if(res<0||a<0)return 0;}return res;}
ll inv(ll a){/* */return qpow(a,MOD-2);}
ll inv(ll a,ll p){return qpow(a,p-2,p);}

ll fac[MAX],ivfac[MAX];
void init(int n){
    fac[0] = 1;
    for(int i = 1;i <= n;++i)fac[i] = fac[i-1] * i % MOD;
    ivfac[n] = inv(fac[n]);
    for(int i = n - 1;~i;--i) ivfac[i] = ivfac[i+1] * (i+1) % MOD;
}
ll C(int n,int m){
    if(m < 0 || m > n)return 0;
    return fac[n] * ivfac[m] % MOD * ivfac[n - m] % MOD;
}
int pre[MAX],suf[MAX];
char ss[MAX];
int main() {
    init(200000);
    scanf("%s",ss+1);
    int ed = strlen(ss+1);
    for(int i = 1;i <= ed;++i){
        pre[i] = pre[i-1];
        if(ss[i] == '(')pre[i]++;
    }
    for(int i = ed;i >= 1;--i){
        suf[i] = suf[i+1];
        if(ss[i] == ')')suf[i]++;
    }
    ll ans = 0;
    for(int i = 1;i <= ed;++i){
        if(ss[i] != '(' || suf[i+1] == 0)continue;
        ans = (ans + C(pre[i]+suf[i+1]-1,pre[i])) % MOD;
    }
    printf("%lld",ans);
    return 0;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值