2023-11-15 21:00阅读: 52评论: 3推荐: 1

做题记录

dp

Round Subset

末尾 0 的个数只与因子中 52 的数量有关。

fi,j,k,l 表示当前为第 i 个数,选了 j 个数,有 k5 作为因子,有 l2 作为因子时可不可以选。

但是空间太大了( k 大约需要开到 800 多),可以把最后一维作为 dp 数组的含义,即 fi,j,k 表示当前为第 i 个数,选了 j 个数,有 k5 作为因子时,作为因子的 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×(y2y1+1)+l×(x2x1+1)2×l×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]

ai 表示遍历到的下一个数,x 表示当前答案。

可以得出:

  1. x<ai 时,无贡献。
  2. xai 时,x=ai+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;  
}

数论

龙哥的问题

i=1ngcd(i,n)

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

C(x)=i=1n[gcd(i,n)=x]=i=1nx[gcd(i,n)=1]

因为 φ(x)=i=1n[gcd(i,n)=1] ,所以 C(x)=φ(nx)

原式可以转化为

i=1ngcd(i,n)=x|dxφ(nx)

#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;  
}

这算哪种题啊

种树

ai 可只因分解为 ai=p1k1p2k2···pmikmi

ai 的正因数个数为 j=1mikj+1 。因为 pj 一共会出现 kj+1 次。(不选算一种)

答案可转化为 求 i=1nj=1miki,j+1 。其中 mi 表示第 i 个数的质因子数,ki,j 表示第 i 个数的第 j 个质数的指数。

可知当将一个质因子的指数加一,贡献为 i=1nj=1miki,j+1k+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 @   tkt  阅读(52)  评论(3编辑  收藏  举报
点击右上角即可分享
微信分享提示
评论
收藏
关注
推荐
深色
回顶
收起