做题记录

dp

Round Subset

末尾 \(0\) 的个数只与因子中 \(5\)\(2\) 的数量有关。

\(f_{i,j,k,l}\) 表示当前为第 \(i\) 个数,选了 \(j\) 个数,有 \(k\)\(5\) 作为因子,有 \(l\)\(2\) 作为因子时可不可以选。

但是空间太大了( \(k\) 大约需要开到 800 多),可以把最后一维作为 dp 数组的含义,即 \(f_{i,j,k}\) 表示当前为第 \(i\) 个数,选了 \(j\) 个数,有 \(k\)\(5\) 作为因子时,作为因子的 \(2\) 的数量。然后就变成了一个多维背包。

但是啊!空间还是爆了,所以我们进行一个滚动数组把第一维滚了。

#include <iostream>
#include <cstring>
#define int long long
const int M = 200+10;
int n,k;
int a[M];
int f[M][8001];
int fi[M],tw[M];
int ans;
inline void makefat(int i){
    int x=a[i];
    while((x%5)==0){
        x/=5;
        fi[i]++;
    }
    while((x%2)==0){
        x/=2;
        tw[i]++;
    }
    return;
}
inline int read(){
    int num=0,fl=1;char c=getchar();
    while(c<'0'||c>'9'){
        if(c=='-') fl=-1;
        c=getchar();
    }
    while(c >='0'&&c <='9'){
        num=(num<<3)+(num<<1)+(c^48);
        c=getchar();
    }
    return num*fl;
}
signed main(){
    memset(f,-0x3f,sizeof f);
    n=read(),k=read();
    for(int i = 1;i <= n;i++) a[i]=read(),makefat(i);
    f[0][0]=0;
    for(int i = 1;i <= n;i++){
        for(int j = k;j >= 1;j--){
            for(int l = 8000;l>=fi[i];l--){
                f[j][l]=std::max(f[j][l],f[j-1][l-fi[i]]+tw[i]);
            }
        }
    }
    for(int i = 0;i <= 8000;i++){
        ans=std::max(ans,std::min(f[k][i],i));
    }
    printf("%lld",ans);
    return 0;  
}

学到了什么?:将一维转化为含义。

数据结构

线段树

P3801 红色的幻想乡

发现可以用容斥原理。

用两个线段树分别维护行上的红雾数量和列上的红雾数量。单点修改,区间求值。设有红雾的行数为 \(h\) ,有红雾的列数为 \(l\) 。根据容斥原理,可得出红雾数为 \(h\times(y_2-y_1+1)+l\times(x_2-x_1+1)-2\times l\times h\)

#include <iostream>
#define int long long
#define ls (x<<1)
#define rs (x<<1|1)
const int M = 1e5+10;
int n,m,q;
struct Seg
{
    struct Tree
    {
        int l,r,num,sum;
    }t[M<<2];
    inline void pushUp(int x)
    {
        t[x].sum=t[ls].sum+t[rs].sum;
    }
    void build(int x,int l,int r)
    {
        t[x].l=l,t[x].r=r;
        if(l==r){
            t[x].num=0;
            return ;
        }
        int mid=(l+r)>>1; 
        build(ls,l,mid);
        build(rs,mid+1,r);
        return ;
    }
    void update(int x,int l,int r)
    {
        if(t[x].l>= l&&t[x].r <=r){
            t[x].num^=1;
            t[x].sum^=1;
            return;
        }
        int mid=(t[x].l+t[x].r)>>1;
        if(mid >= l) update(ls,l,r);
        if(mid < r) update(rs,l,r);
        pushUp(x);
        return;
    }
    int getSum(int x,int l,int r)
    {
        if(t[x].l>= l&&t[x].r <=r)
            return t[x].sum;
        int mid=(t[x].l+t[x].r)>>1,ans=0;
        if(mid >= l) ans+=getSum(ls,l,r);
        if(mid < r) ans+=getSum(rs,l,r);
        return ans;
    }
}hang,lie;
inline int read(){
    int num=0,fl=1;char c=getchar();
    while(c<'0'||c>'9'){
        if(c=='-') fl=-1;
        c=getchar();
    }
    while(c >='0'&&c <='9'){
        num=(num<<3)+(num<<1)+(c^48);
        c=getchar();
    }
    return num*fl;
}
signed main(){
    n=read(),m=read(),q=read();
    hang.build(1,1,n);
    lie.build(1,1,m);
    while(q--){
        int mod=read();
        if(mod==1){
            int x=read(),y=read();
            hang.update(1,x,x);
            lie.update(1,y,y);
        }
        if(mod==2){
            int x1=read(),y1=read(),x2=read(),y2=read();
            int h=hang.getSum(1,x1,x2),l=lie.getSum(1,y1,y2);
            printf("%lld\n",h*(y2-y1+1)+l*(x2-x1+1)-2*h*l);
        }
    }
    return 0;  
}

P4513 小白逛公园

就是山海经,但是山海经不想写。

单点修改,区间求最大子段和。考虑一个区间的最大子段和可能为其左子区间的最大子段和,右子区间的最大子段和,或两个子区间各取一部分的和。

因为第一,二种情况可以转化为子问题。所以只对第三种情况分类讨论:

  1. 取左区间的最大后缀和和右区间的最大前缀和。
  2. 取左区间的区间和和右区间的最大前缀和。
  3. 取左区间的最大后缀和和右区间的区间和。

所以需要维护最大前缀和,最大后缀和,区间和与最大子段和。

注意合并时细节(?)

#include <iostream>
#define int long long
#define ls (q<<1)
#define rs (q<<1|1)
const int M = 5e5+10;
const int inf = 0x3f3f3f3f3f3f3f3f;
int n,m;
int a[M];
namespace Seg
{
    struct Tree
    {
        int lsum,rsum,sum,ans;
    }t[M<<2];
    inline void pushUp(int q)
    {
        t[q].sum=t[ls].sum+t[rs].sum;
        t[q].lsum=std::max(t[rs].lsum+t[ls].sum,t[ls].lsum);
        t[q].rsum=std::max(t[ls].rsum+t[rs].sum,t[rs].rsum);
        t[q].ans=std::max(t[rs].lsum+t[ls].rsum,std::max(t[ls].ans,t[rs].ans));
        return ;
    }
    void build(int q,int l,int r)
    {
        if(l==r){
            t[q].rsum=t[q].lsum=t[q].sum=t[q].ans=a[l];
            return ;
        }
        int mid=(l+r)>>1;
        build(ls,l,mid);
        build(rs,mid+1,r);
        pushUp(q);
        return ;
    }
    void change(int q,int l,int r,int x,int y,int num)
    {
        if(l >= x&&r <= y){
            t[q].rsum=t[q].lsum=t[q].sum=t[q].ans=num;
            return ;
        }
        int mid =(l+r)>>1;
        if(mid >= x) change(ls,l,mid,x,y,num);
        if(mid < y) change(rs,mid+1,r,x,y,num);
        pushUp(q);
        return ;
    }
    Tree getMaxSum(int q,int l,int r,int x,int y)
    {
        if(l >= x&&r <= y)
            return t[q];
        int mid = (l+r)>>1;
        bool f1=0,f2=0;
        if(mid >= x) f1=1;
        if(mid < y) f2=1;
        if(f1==1&&f2==0) return getMaxSum(ls,l,mid,x,y);
        else if(f1==0&&f2==1) return getMaxSum(rs,mid+1,r,x,y);
        else{
            Tree lt=getMaxSum(ls,l,mid,x,y),rt=getMaxSum(rs,mid+1,r,x,y),nt;
            nt.sum=lt.sum+rt.sum;
            nt.lsum=std::max(rt.lsum+lt.sum,lt.lsum);
            nt.rsum=std::max(lt.rsum+rt.sum,rt.rsum);
            nt.ans=std::max(rt.lsum+lt.rsum,std::max(lt.ans,rt.ans));
            return nt;
        }
    }
}
inline int read(){
    int num=0,fl=1;char c=getchar();
    while(c<'0'||c>'9'){
        if(c=='-') fl=-1;
        c=getchar();
    }
    while(c >='0'&&c <='9'){
        num=(num<<3)+(num<<1)+(c^48);
        c=getchar();
    }
    return num*fl;
}
signed main(){
    n=read(),m=read();
    for(int i = 1;i <= n;i++) a[i]=read();
    Seg::build(1,1,n);
    while(m--){
        int k=read(),x=read(),y=read();
        if(k==1){
            if(x>y) std::swap(x,y);
            printf("%lld\n",Seg::getMaxSum(1,1,n,x,y).ans);
        }
        else{
            Seg::change(1,1,n,x,x,y);
        }
    }
    return 0;  
}

合并合并合并合并合并合并合并合并合并合并合并合并合并合并

主席树

P4587 [FJOI2016] 神秘数

暴力做法就是遍历 \([l,r]\)

\(a_i\) 表示遍历到的下一个数,\(x\) 表示当前答案。

可以得出:

  1. \(x < a_i\) 时,无贡献。
  2. \(x \ge a_i\) 时,\(x = a_i+x+1\)

时间复杂度 \(O(nm)\) 。过不了,考虑优化。

\(\_max\) 为当前可以表示的最大值, \(\_min\) 为当前区间中数的最大值。初始 \(\_max=\_min=0\)

根据上面的发现 \(2\) 可以知道需要加入的数一定小于等于 \(\_max+1\) ,又因为小于等于 \(\_min\) 的数已经加入,所以每次加入数值位于 \([\_min+1,\_max+1]\) 区间内的数的和。设次和为 \(add\) ,每次令 \(\_min=\_max+1\) \(\_max=\_max+add\) 。当 \(add=0\) 时退出

#include <iostream>
#include <algorithm>
#define int long long
#define frer freopen("in.in","r",stdin);
#define frew freopen("out.out","w",stdout);
const int M = 1e5+10;
int n,m,maxn,cnt;
int d[M],root[M];
int _min,_max;
namespace Pseg
{
    struct Tree
    {
        int ls,rs,sum;
    }t[M<<5];
    inline void pushUp(int q)
    {
        t[q].sum=t[t[q].ls].sum+t[t[q].rs].sum;
    }
    void update(int p,int q,int l,int r,int x)
    {
       if(l==r){
            t[q].sum=t[p].sum+x;
            return ; 
        }
        t[q].ls=t[p].ls,t[q].rs=t[p].rs;
        int mid=(l+r)>>1;
        if(mid >= x) update(t[p].ls,t[q].ls=++cnt,l,mid,x);
        else update(t[p].rs,t[q].rs=++cnt,mid+1,r,x);
        pushUp(q);
        return ;
    }
    int getSum(int p,int q,int l,int r,int x,int y)
    {
        if(l >= x&&r <= y)
            return t[q].sum-t[p].sum;
        int mid=(l+r)>>1,ans=0;
        if(mid >= x) ans+=getSum(t[p].ls,t[q].ls,l,mid,x,y);
        if(mid < y) ans+=getSum(t[p].rs,t[q].rs,mid+1,r,x,y);
        return ans;
    }
}
inline int read(){
    int num=0,fl=1;char c=getchar();
    while(c<'0'||c>'9'){
        if(c=='-') fl=-1;
        c=getchar();
    }
    while(c >='0'&&c <='9'){
        num=(num<<3)+(num<<1)+(c^48);
        c=getchar();
    }
    return num*fl;
}
signed main(){
    n=read();
    for(int i = 1;i <= n;i++) d[i]=read(),maxn=std::max(d[i],maxn);
    for(int i = 1;i <= n;i++) Pseg::update(root[i-1],root[i]=++cnt,1,maxn,d[i]);
    m=read();
    while(m--){
        int x=read(),y=read();
        _min=0,_max=0;
        while(1){
            int sum;
            if(_min>=maxn) sum=0;
            else sum=Pseg::getSum(root[x-1],root[y],1,maxn,_min+1,_max+1);
            if(!sum) break;
            _min=_max+1;
            _max+=sum;
        }
        printf("%lld\n",_max+1);
    }
    return 0;  
}

数论

龙哥的问题

\(\sum\limits_{i=1}^n\gcd(i,n)\)

想到与 \(\gcd(i,n)=x\) 的数量有关。

\[\begin{aligned}C(x)=\sum\limits_{i=1}^n[\gcd(i,n)=x]\\=\sum\limits_{i=1}^\tfrac{n}{x}[\gcd(i,n)=1]\end{aligned} \]

因为 \(\varphi(x)=\sum\limits_{i=1}^n[\gcd(i,n)=1]\) ,所以 \(C(x)=\varphi(\tfrac{n}{x})\)

原式可以转化为

\[\sum\limits_{i=1}^n\gcd(i,n)=\sum\limits_{x|d}x\cdot \varphi(\tfrac{n}{x}) \]

#include <iostream>
#include <cmath>
#define int long long
int n,ans;
inline int phi(int x)
{
    int res=x;
    for(register int i = 2;i*i<=x;++i){
        if(x%i==0){
            res=res*(i-1)/i;
            while(x%i==0) x/=i;
        }
    }
    if(x>1) res=res*(x-1)/x;
    return res;
}
inline void work(int x)
{
    for(register int i = 1;i*i<x;i++){
        if(x%i==0){
            ans+=phi(x/i)*i+phi(i)*x/i;
        }
    }
    if((int)std::sqrt(x)*(int)std::sqrt(x)==x)ans+=phi(x/(int)std::sqrt(x))*(int)std::sqrt(x);
    printf("%lld",ans);
}
inline int read(){
    int num=0,fl=1;char c=getchar();
    while(c<'0'||c>'9'){
        if(c=='-') fl=-1;
        c=getchar();
    }
    while(c >='0'&&c <='9'){
        num=(num<<3)+(num<<1)+(c^48);
        c=getchar();
    }
    return num*fl;
}
signed main(){
    n=read();
    work(n);
    return 0;  
}

这算哪种题啊

种树

\(a_i\) 可只因分解为 \(a_i={p_1}^{k_1} \cdot {p_2}^{k_2}···{p_{m_i}}^{k_{m_i}}\)

\(a_i\) 的正因数个数为 \(\prod\limits_{j=1}^{m_i}k_j+1\) 。因为 \(p_j\) 一共会出现 \(k_j+1\) 次。(不选算一种)

答案可转化为 求 \(\prod\limits_{i=1}^n\prod\limits_{j=1}^{m_i}k_{i,j}+1\) 。其中 \(m_i\) 表示第 \(i\) 个数的质因子数,\(k_{i,j}\) 表示第 \(i\) 个数的第 \(j\) 个质数的指数。

可知当将一个质因子的指数加一,贡献为 \(\dfrac{\prod\limits_{i=1}^n\prod\limits_{j=1}^{m_i}k_{i,j}+1}{k+1}\) 。即指数越小贡献越大。

考虑贪心。

\(w\) 质因分解,对于每个质因数遍历一遍 \(p\) 数组,求出指数最小的质因数,乘上当前质因数。最后统一处理出答案。

需要注意质因分解不完全的情况,要最后单独弄一下。

#include <iostream>
#include <cstdio> 
#include <algorithm>
#include <cstring>
#include <cmath>
#define int long long
const int M = 1e4+10;
const int Mod = 998244353;
const int inf = 0x3f3f3f3f3f3f3f3f;
inline int MAX(int a,int b){return a>b?a:b;}
inline int MIN(int a,int b){return a<b?a:b;}
inline int ABS(int a,int b){return (a-b)>0?a-b:b-a;}
inline int read(){
    int num=0,fl=1;char c=getchar();
    while(c<'0'||c>'9'){
        if(c=='-') fl=-1;
        c=getchar();
    }
    while(c >='0'&&c <='9'){
        num=(num<<3)+(num<<1)+(c^48);
        c=getchar();
    }
    return num*fl;
}
int n,w,ans=1,t[M];
inline void input()
{
    n=read(),w=read();
    for(int i = 1;i <= n;i++) t[i]=read();
}
inline void work()
{
    for(int i = 2;i*i <= w;i++){
        while(w%i==0){
            int ch=0,minn=inf;
            w/=i;
            for(int j = 1;j <= n;j++){
                int cnt=0,x=t[j];
                while(x%i==0) x/=i,cnt++;
                if(cnt<minn) minn=cnt,ch=j;
            }
            t[ch]*=i;
        }
    }
    if(w>1){
        int ch=0,minn=inf;
        for(int i = 1;i <= n;i++){
            int cnt=0,x=t[i];
            while(x%w==0) x/=w,cnt++;
            if(cnt<minn) minn=cnt,ch=i;
        }
        t[ch]*=w;
    }
    for(int i = 1;i <= n;i++){
        for(int j = 2;j*j<=t[i];j++){
            int cnt=0;
            if(t[i]%j==0){
                while(t[i]%j==0) t[i]/=j,cnt++;
            }
            ans=ans*(cnt+1)%Mod;
        }
        if(t[i]>1) ans=ans*2%Mod;
    }
    printf("%lld",ans);
}
signed main(){
    input();
    work();
    return 0;  
}
posted @ 2023-11-15 21:00  tkt  阅读(51)  评论(3编辑  收藏  举报