我们刚刚知道那些题的解法-4

初赛

考虑把这些数分成 n 组,每组内部比较,然后大的那些选最大,小的那些选最小。答案为 3n2

doubleint 或者 intdouble 的返回值都是 double,而 intint 会向下取整。

300iqContest2 B

题目链接

首先考虑 aiajx 这个限制不好做,考虑转化,经过分类讨论,异或有以下性质:

abcmin(ab,bc)ac

证明:

a=c 是平凡的。若 a,设 wa,c 最高的二进制下不同的一位,因为 abc,所以 bw+1 及以上的这些位置 a,b,c 都是一样的。所以异或结果这些位上全是 0。考虑位 w,显然 a0c1,而 b 不管这一位选什么总会导致 ab,bc 某一个 w 位变成 0。综上可知等式成立。

所以题目限制可以转化为相邻两个数的异或大于等于 x,于是可以设计一个 DP,设 fi 表示以 i 结尾的合法序列个数,则转移应该为:

fi=aiajx,ji1fj+1

利用 Trie 树优化可以做到 O(logV) 的转移。

int n,X,a[N],f[N];

struct Trie{
    struct Node{
        int ch[2],val;
        inline Node(){ch[0]=ch[1]=0;val=0;}
        inline int operator [] (int id){return ch[id];}
    }p[N*60];
    int tot;
    inline Trie(){
        tot=1;
    }
    inline void Insert(int x,int v){
        int now=1;
        dec(i,0,60){
            int k=(x>>i)&1;
            if(!p[now][k]) p[now].ch[k]=++tot;
            p[now].val=(p[now].val+v)%mod;
            now=p[now][k];
        }
        p[now].val=(p[now].val+v)%mod;
    }
    inline int Ask(int x){
        int ans=0,now=1;
        dec(i,0,60){
            if(!now) break;
            int xk=(X>>i)&1;
            int aik=(x>>i)&1;
            if(!xk){
                (ans+=p[p[now][aik^1]].val)%=mod;
                now=p[now][aik];
            }
            else{
                now=p[now][aik^1];
            }
        }
        ans=(ans+p[now].val)%mod;
        return ans;
    }
}tr;

signed main(){
    // freopen("my.in","r",stdin);
    // freopen("my.out","w",stdout);
    read(n);read(X);rep(i,1,n) read(a[i]);
    sort(a+1,a+n+1);
    rep(i,1,n){
        f[i]=(tr.Ask(a[i])+1)%mod;
        tr.Insert(a[i],f[i]);
    }
    int ans=0;
    rep(i,1,n){
        ans=(ans+f[i])%mod;
        // printf("f[%lld]=%lld\n",i,f[i]);
    }
    printf("%lld\n",ans);
    return 0;
}

ZR 2022.9.20 赠送赛 A

如果能处理一下两种询问,那么这道题就做完了:一棵树内所有点到某个点的路径之和。一棵树内两个点的距离之和。

容易发现在本题中,询问中任意的点都应该是题目中给定的点,且设 ab,c 组成,那么 a 中的点会在 b,c 出现,但是 b,c 中点不会在 a 中出现,是满足左边条件的题目中给定的带你。容易发现,这样的点一共有 m2 个。

考虑第一个询问,可以通过第二个询问把第一个询问规模缩小,且最多缩小 m 次,所以仅考虑第二个询问时间复杂度即可。

考虑第二个询问只可能是以后的一个点与这一层这个点之间的询问,所以总的询问个数应该是 m3 的。

所以加上记忆化,总的复杂度应该是 m3 的。

ZR 2022.9.21 赠送赛 B

场上有一个 n3 DP,设 fi,j 表示玩了 i 轮,有 j 个人还活着的概率是多少,转移式是显然的,每次枚举选了那个人,死了几个人即可。

但是发现所有人都是等价的,如果我们给每个点标上号,设题目中小 R 所在的点编号为 n,那么我们可以认为选点序列是从 1n 选,这样做实际上是钦定了小 R 一定是最后一个,那这样做的概率是 1n,最后给答案乘上这个数即可。

接下来,设 fi,j 表示考虑 iin 被攻击了 j 次的概率是多少。转移只需要考虑 i 是否已经被攻击没了。

CF812Div2E

首先考虑主对角线,也就是对角线上所有点的横纵坐标之和一样的对角线,发现每次一个物体移动都会从一个对角线移动到另一个对角线,这同时表明了不可能存在某一时刻,一个对角线上存在两个物体,也就是说,合并是不可能存在的。

现在考虑一个询问 t,x,y,显然如果 t<x+y,那么肯定是没有任何物体到达 x+y 这条对角线,所以一定是 NO,现在考虑 tx+y,那么可能到达 (x,y) 的那个位置一定是 txy 时刻被放在 (0,0) 的,所以 txy1 时刻之前的 txy 放的都可能会对这个时刻的东西的路径产生贡献。

观察可以得到,我们可以对一些球一起做,来得到 x+y 这条对角线上每个位置的东西的个数,如果 (a,b) 放了 k 个东西,那么等价于 k2 往下走,k2 往右走。这样我们只需看 txy 情况下 (x,y) 放的东西个数,和 txy+1 情况下 (x,y) 放的东西个数。

int Q,t,x,y;
int a[N][N];

inline void Calc(int x,int y,int c){
    rep(i,0,min(120ll,x+y)){
        rep(j,0,min(120ll,x+y)){
            a[i][j]=0;
        }
    }
    a[0][0]=c;
    rep(i,0,x+y-1){
        // printf("i=%lld\n",i);
        rep(j,max(i-119ll,0ll),min(119ll,i)){
            int nowx=j,nowy=i-j;
            // printf("a[%lld][%lld]=%lld\n",nowx,nowy,a[nowx][nowy]);
            if(a[nowx][nowy]){
                if(nowx<119) a[nowx+1][nowy]+=a[nowx][nowy]/2;
                if(nowy<119) a[nowx][nowy+1]+=(a[nowx][nowy]+1)/2;
                a[nowx][nowy]=0;
            }
        }
    }
}

signed main(){
    // freopen("my.in","r",stdin);
    // freopen("my.out","w",stdout);
    read(Q);
    while(Q--){
        read(t);read(x);read(y);
        if(t<x+y){puts("NO");continue;}
        Calc(x,y,t-x-y);int nowa=a[x][y];
        Calc(x,y,t-x-y+1);int nownowa=a[x][y];
        // printf("nowa=%lld nownowa=%lld\n",nowa,nownowa);
        if(nowa==nownowa){
            puts("NO");
        }
        else puts("YES");
    }
    return 0;
}

ZR 2022.9.21 C

我的 DP 是真的不行了。

考虑枚举最小的那个塞不下去的物品,那么比它小的都要放进去,比它大的,就是一个 01 背包求方案数的 DP。如果 DP 已经做不了的事情,不妨加一点限制,多是枚举一个值。

XOR

这个题没有给题目链接。题目如下:

把所有 aibi 加入 Trie 树。

首先要去掉绝对值号,考虑枚举 x 的最高位,如果是 0,那么最高位为 1 对应的子树的所有 a,b 之间的最大最小值就已经确定了,剩下的我们在子树里面暴力搜索。如果 x 最高位填 1,同样暴力搜索。然后可以接着往下递归去做。

暴力搜索怎么算贡献?只需要在每个节点上统计经过这个点的数有多少个,就可以知道在某一位选择 0 还是 1 会有多大的贡献。

显然,一个点在搜索中被访问的次数是其祖先的个数。所以总的复杂度应当是 O(nlog2V)

AGC044C

两个操作哪个都不会维护。

题解比较巧妙,考虑把所有的数放进三进制 Trie 中,第一个操作等价于我们交换每个节点的 1,2 儿子,而第二个操作需要我们把 1 儿子改成 1 儿子,1 儿子改成 2 儿子,2 儿子改成 0 儿子,然后别忘了进位,所以我们要从低位到高位建立 Trie 树。

进位的处理只需要递归到子树接着处理即可。复杂度是 log 的,而第一个操作只需要打标记和标记下传。

int pw[13],n,ans[N],cnt;
inline void Init(){
    pw[0]=1;
    rep(i,1,12) pw[i]=pw[i-1]*3;
}

struct Trie{
    struct Node{
        int ch[3],End,tag,dep;
        inline int& operator [] (const int id){return ch[id];}
    }p[N];
    int tot;
    inline Trie(){
        tot=1;Init();mset(p,0);
    }
    inline void C(int k){
        swap(p[k][1],p[k][2]);p[k].tag^=1;
    }
    inline void PushDown(int k){
        if(p[k].tag){
            rep(i,0,2) C(p[k][i]);p[k].tag=0;
        }
    }
    inline void Insert(int x){
        int now=1;
        rep(i,0,n-1){
            int k=(x/pw[i])%3;
            if(!p[now][k]) p[now][k]=++tot;
            p[p[now][k]].dep=p[now].dep+1;
            now=p[now][k];
        }
        p[now].End=x+1;
        // printf("now=%d\n",now);
        // printf("p[now].End=%d\n",p[now].End);
    }
    inline void R(){
        C(1);
    }
    inline void dfs(int k){
        if(!k) return;
        PushDown(k);
        int c2=p[k][2];
        p[k][2]=p[k][1];
        p[k][1]=p[k][0];
        p[k][0]=c2;
        dfs(p[k][0]);
    }
    inline void Inc(){
        dfs(1);
    }
    inline void Dfs(int k){
        if(!k) return;
        PushDown(k);
        rep(i,0,2) Dfs(p[k][i]);
    }
    inline void PushDownAll(){
        Dfs(1);
    }
    inline void ddfs(int k,int sum){
        // rep(i,0,2){
        //     printf("p[%d][%d]=%d\n",k,i,p[k][i]);
        // }
        if(!k) return;
        PushDown(k);
        if(!p[k][0]){
            // printf("k=%d\n",k);
            // printf("p[k].end=%d\n",p[k].End);
            assert(p[k].End);
            // printf("%d\n",p[k].End-1);
            ans[p[k].End-1]=sum;
            return;
        }
        rep(i,0,2){
            ddfs(p[k][i],sum+pw[p[k].dep]*i);
        }
    }
    inline void Print(){
        PushDownAll();
        ddfs(1,0);
    }
}tr;

int main(){
    // freopen("my.in","r",stdin);
    // freopen("my.out","w",stdout);
    read(n);
    string s;cin>>s;
    rep(i,0,pw[n]-1){
        tr.Insert(i);
    }
    // tr.Print();
    // // exit(0);
    int len=(int)s.length()-1;
    rep(i,0,len){
        if(s[i]=='S') tr.R();
        else if(s[i]=='R') tr.Inc();
        else assert(0);
    }
    tr.Print();
    rep(i,0,pw[n]-1){
        printf("%d ",ans[i]);
    }
    return 0;
}

SOJ #41

我愿称这种操作为区间覆盖加。

区间赋值是可持久化平衡树可以做的虽然我不会,但是考虑这个题是一个加法的操作。

加法!这提示我们想到卷积,考虑把一个区间 l,r 加到上面,只需要让 cntl 变成 1 然后对两个数组做差卷积即可。

但是这里并不是直上直下的加法,考虑 n105,我们自然想到分块。散块直接处理了。对于整块的贡献,考虑枚举每一个块,然后对于每一个操作计算它对这个块的贡献,维护一个 cnt 数组,然后把 cnta 数组做一个差卷积即可。

#include<bits/stdc++.h>
#define mset(a,b) memset((a),(b),sizeof((a)))
#define rep(i,l,r) for(int i=(l);i<=(r);i++)
#define dec(i,l,r) for(int i=(r);i>=(l);i--)
#define cmax(a,b) (((a)<(b))?(a=b):(a))
#define cmin(a,b) (((a)>(b))?(a=b):(a))
#define Next(k) for(int x=head[k];x;x=li[x].next)
#define vc vector
#define ar array
#define pi pair
#define fi first
#define se second
#define mp make_pair
#define pb push_back
#define N 600010
#define M 1000100
using namespace std;

typedef double dd;
typedef long double ld;
typedef long long ll;
typedef unsigned int uint;
typedef unsigned long long ull;
#define int long long
typedef pair<int,int> P;
typedef vector<int> vi;

const int INF=0x3f3f3f3f;
const dd eps=1e-9;
const int mod=1004535809;
const int g=3;

template<typename T> inline void read(T &x) {
    x=0; int f=1;
    char c=getchar();
    for(;!isdigit(c);c=getchar()) if(c == '-') f=-f;
    for(;isdigit(c);c=getchar()) x=x*10+c-'0';
    x*=f;
}

struct FastIO
{
    #define MB (1<<20)
    char ib[MB+100],*p,*q;
    char ob[MB+100],*r,stk[128];
    int tp;

    FastIO(){p=q=ib,r=ob,tp=0;}
    ~FastIO(){fwrite(ob,1,r-ob,stdout);}

    char read_char()
    {
        if(p==q)
        {
            p=ib,q=ib+fread(ib,1,MB,stdin);
            if(p==q)return 0;
        }
        return *p++;
    }
    template<typename T>
    void read_int(T& x)
    {
        char c=read_char(),l=0;
        for(x=0;!isdigit(c);c=read_char())l=c;
        for(;isdigit(c);c=read_char())x=x*10-'0'+c;
        if(l=='-')x=-x;
    }

    void write_char(char c)
    {
        if(r-ob==MB)r=ob,fwrite(ob,1,MB,stdout);
        *r++=c;
    }
    template<typename T>
    void write_int(T x) 
    {
        if(x<0)write_char('-'),x=-x;
        do stk[++tp]=x%10+'0';
        while(x/=10);
        while(tp)write_char(stk[tp--]);
    }
}IO;

int n,m,a[N],posi[N],Len,cnt[N],b[N];
int tr[N];
int A[N];

struct Ope{
    int l,r,L;
}q[M];

inline int gel(int id){return (id-1)*Len+1;}
inline int ger(int id){return min(n,id*Len);}
inline void Gettr(int len){
    rep(i,0,len-1) tr[i]=(tr[i>>1]>>1)|((i&1)?(len>>1):0);
}
inline int ksm(int a,int b,int mod){
    int res=1;while(b){if(b&1){res=1ll*res*a%mod;}a=1ll*a*a%mod;b>>=1;}return res;
}
inline int inv(int x){return ksm(x,mod-2,mod);}
inline void NTT(int *f,int n,int op){
    rep(i,0,n-1) if(i<tr[i]) swap(f[i],f[tr[i]]);
    for(int i=2;i<=n;i<<=1){
        int tg=ksm(g,(mod-1)/i,mod);
        if(op==-1) tg=inv(tg);
        for(int j=0;j<n;j+=i){
            int now=1;
            for(int k=j;k<j+i/2;k++){
                int tt=now*f[k+i/2]%mod;
                f[k+i/2]=(f[k]-tt+mod)%mod;
                f[k]=(f[k]+tt)%mod;
                now=now*tg%mod;
            }
        }
    }
    if(op==-1){
        int invn=inv(n);
        rep(i,0,n-1) f[i]=f[i]*invn%mod;
    }
}

signed main(){
    // freopen("my.in","r",stdin);
    // freopen("my.out","w",stdout);
    IO.read_int(n);rep(i,1,n) IO.read_int(a[i]);
    IO.read_int(m);
    rep(i,1,m){
        // read(q[i].l);read(q[i].r);read(q[i].L);
        IO.read_int(q[i].l);IO.read_int(q[i].r);IO.read_int(q[i].L);
    }
    Len=ceil(sqrt(log2(n)*n)*2.0);
    rep(i,1,n){
        posi[i]=(i-1)/Len+1;
    }
    rep(i,1,posi[n]){
        int l=gel(i),r=ger(i);
    }
    rep(j,1,m){
        int bl=q[j].L,br=min(n,q[j].L+q[j].r-q[j].l);
        int blid=posi[bl];
        int brid=posi[br];
        if(blid==brid){
            rep(i,bl,br){
                b[i]+=a[i-q[j].L+q[j].l];
            }
        }
        else{
            rep(i,bl,ger(blid)){
                b[i]+=a[i-q[j].L+q[j].l];
            }
            rep(i,gel(brid),br){
                b[i]+=a[i-q[j].L+q[j].l];
            }
        }
    }
    int len=1;
    while(len<(n+1)*2) len<<=1;
    Gettr(len);
    NTT(a,len,1);
    rep(i,1,posi[n]){
        int l=gel(i),r=ger(i);
        mset(cnt,0);
        rep(j,1,m){
            int bl=q[j].L,br=min(n,q[j].L+q[j].r-q[j].l);
            int blid=posi[bl];
            int brid=posi[br];
            if(blid<i&&i<brid){
                int len=l-bl+1;
                cnt[q[j].l+len-1]++;
            }
        }
        reverse(cnt+1,cnt+n+1);
        NTT(cnt,len,1);
        rep(j,0,len) cnt[j]=cnt[j]*a[j]%mod;
        NTT(cnt,len,-1);
        rep(j,l,r){
            int val=cnt[n+1+(j-l)];
            b[j]=(b[j]+val)%mod;
        }
    }
    // rep(i,1,n) printf("%lld\n",b[i]);
    rep(i,1,n){
        // printf("b[i]=%d\n",b[i]);
        IO.write_int(b[i]);
        IO.write_char('\n');
    }
    return 0;
}

Zhengrui 2022ABDay1 A

其中 n2e5

闵可夫斯基和裸题。显然函数 f(k) 是一个凸函数。极差不好做,进行转化:分成 k 段,每段里面有一个 1,一个 1,其余都是 0,问带权和最大是多少。

考虑如何合并,需要知道最左边和最右边两个区间是否都为 1,是否都为 1,以及是不是 1,1 都有。合并时注意我们不认为只有 11 的区间是一个完整的区间,其次,有可能会出现一个区间里面没有任何贡献,要处理这种合并只需要直接把左右儿子区间传上来即可。

其余的合并都是平凡的。

int n,a[N];
vi v[N<<2][3][3],t[3][3];

#define ls(k) k<<1
#define rs(k) k<<1|1
inline void Merge(vi b,vi c,vi &d,int o){
    int n=(int)b.size()-1,m=(int)c.size()-1;
    for(int i=0,j=0;i<=n&&j<=m;){
        cmax(d[i+j+o],b[i]+c[j]);
        if(j==m||(i<n&&b[i+1]-b[i]>c[j+1]-c[j])) i++;
        else j++;
    }
}
inline void PushUp(int k){
    rep(i,0,2)rep(j,0,2){
        Merge(v[ls(k)][i][0],v[rs(k)][1][j],v[k][i][j],1);
        Merge(v[ls(k)][i][1],v[rs(k)][0][j],v[k][i][j],1);
        Merge(v[ls(k)][i][2],v[rs(k)][2][j],v[k][i][j],0);
    }
}
inline void Divid(int k,int l,int r){
    rep(i,0,2)rep(j,0,2) v[k][i][j].resize(r-l+1+1,-INF);
    if(l==r){
        v[k][2][2][0]=v[k][2][2][1]=0;
        v[k][0][2][0]=v[k][2][0][0]=-a[l];
        v[k][1][2][0]=v[k][2][1][0]=a[l];
        return;
    }
    int mid=(l+r)>>1;
    Divid(ls(k),l,mid);Divid(rs(k),mid+1,r);
    rep(i,0,2)rep(j,0,2){
        rep(o,0,mid-l+1) cmax(v[k][i][j][o],v[ls(k)][i][j][o]);
        rep(o,0,r-mid) cmax(v[k][i][j][o],v[rs(k)][i][j][o]);
    }
    PushUp(k);
}

signed main(){
    // freopen("my.in","r",stdin);
    // freopen("my.out","w",stdout);
    read(n);rep(i,1,n) read(a[i]);
    Divid(1,1,n);
    rep(i,1,n){
        printf("%lld\n",v[1][2][2][i]);
    }
    return 0;
}
posted @   NuclearReactor  阅读(6)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· 分享一个免费、快速、无限量使用的满血 DeepSeek R1 模型,支持深度思考和联网搜索!
· 基于 Docker 搭建 FRP 内网穿透开源项目(很简单哒)
· ollama系列01:轻松3步本地部署deepseek,普通电脑可用
· 25岁的心里话
· 按钮权限的设计及实现
点击右上角即可分享
微信分享提示
主题色彩