省选联测 (7~13)

省选联测7 水题#

做过的第二道将询问分块的题。

考虑将询问分块后,处理每个查询时扫一遍前面的修改对查询造成的影响。发现可以将链分成两部分,一部分是被修改过的,一部分没有,设分界点为 mxd,发现 mxd 为查询节点与修改节点 lca 深度最大值。lca 可以预先求出整个块的。

设处理的节点为 x,我们维护几个东西 mxcol 表示颜色 col 出现的最深深度,depi 表示这个链深度为 i 的位置的颜色。对于没修改过的,我们只需要找到 mxcol[mxddx] 之间的就可以。我们可以值域分块,设 fi 表示深度为 [B(i1)+1,Bi] 的颜色有几种,这里面每个颜色只会存在与一个深度。这样就可以 O(1)O(n)

对于被染色的,我们可以直接从后往前扫,扫的时候特殊处理。

对于重构的话,意识从后往前扫修改,发现修改过的不会再修改,那么就可以 O(n)

总复杂度 O(nnlogn),可过。

不会树分块。

code
%:pragma GCC optimize(3)
%:pragma GCC optimize("Ofast")
%:pragma GCC optimize("inline")
#include<bits/stdc++.h>
using namespace std;
const int N=2*1e5+10;
int c[N];
struct asd{
    int op,u,c;
}a[N];

int head[N],nex[N*2],ver[N*2],tot=0;
void add(int x,int y){
    ver[++tot]=y,nex[tot]=head[x],head[x]=tot;
}
int fa[N],d[N],top[N],siz[N],son[N];
void dfs1(int x,int fat){
    d[x]=d[fat]+1;
    siz[x]=1;
    for(int i=head[x];i;i=nex[i]){
        int y=ver[i];
        if(y==fat) continue;
        dfs1(y,x);
        siz[x]+=siz[y];
        if(siz[son[x]]<siz[y]) son[x]=y;
    }
}
void dfs2(int x,int tp){
    top[x]=tp;
    if(son[x]) dfs2(son[x],tp);
    for(int i=head[x];i;i=nex[i]){
        int y=ver[i];
        if(y!=fa[x] && y!=son[x]){
            dfs2(y,y);
        }
    }
}
int ask_lca(int x,int y){
    int fx=top[x],fy=top[y];
    while(fx!=fy){
        if(d[fx]>d[fy]){
            x=fa[fx],fx=top[x];
        }
        else{
            y=fa[fy],fy=top[y];
        }
    }
    if(d[x]<d[y]) return x;
    else return y;
}

int B=900,I=900;
int ls[N],rs[N],pos[N];
vector<int> rk[N];
int dep[N],mx[N];
int f[N];
int ans[N];
int __lca[5000][5000];
bool flat[N],vis[N];
int kl[N];
void dfs(int x){
    dep[d[x]]=c[x];
    int mx1=mx[c[x]];
    mx[c[x]]=d[x];
    int t1;
    if(mx1){
        t1=mx1/B;
        if(mx1%B) t1++;
        f[t1]--;
    }
    int t2=d[x]/B;
    if(d[x]%B) t2++;
    f[t2]++;
    if(rk[x].size()){
        int cnt=rk[x].size();
        for(int i=0;i<cnt;i++){
            int op=rk[x][i]; 
            int mxd=0;
            for(int i=ls[pos[op]];i<op;i++){
                if(a[i].op==1){
                    int lca=__lca[i-ls[pos[op]]][op-ls[pos[op]]];
                    mxd=max(mxd,d[lca]);
                }
            }
            int sum=0;
            for(int i=1;i<=t2;i++) sum+=f[i];
            int t3=mxd/B;
            if(mxd%B) t3++;
            for(int i=1;i<=t3;i++) sum-=f[i];
            int qwq=0;
            int up=min(d[x],B*t3);
            for(int i=mxd+1;i<=up;i++){
                int col=dep[i];
                if(mx[col]<=up && !vis[col]){
                    sum++;
                    vis[col]=1;
                    kl[++qwq]=col;
                }
            }
            for(int i=1;i<=qwq;i++) vis[kl[i]]=0;
            int p=0;
            for(int i=op;i>=ls[pos[op]];i--){
                if(a[i].op==1){
                    int lca=__lca[i-ls[pos[op]]][op-ls[pos[op]]];
                    int col=a[i].c;
                    if(d[lca]<=p) continue;
                    else{
                        p=d[lca];
                        if(mx[col]<=mxd && !vis[col]){
                            sum++;
                            vis[col]=1;
                            kl[++qwq]=col;
                        }
                    }
                }
            }
            for(int i=1;i<=qwq;i++) vis[kl[i]]=0;
            ans[op]=sum;
        }
    }
    for(int i=head[x];i;i=nex[i]){
        int y=ver[i];
        if(y==fa[x]) continue;
        dfs(y);
    }
    if(mx1) f[t1]++;
    f[t2]--;
    mx[c[x]]=mx1;
    dep[x]=0;
}
inline int read(){
	int x(0);bool f(0);char ch=getchar();
	for(;ch<'0'||ch>'9';ch=getchar()) f^=ch=='-';
	for(;ch>='0'&&ch<='9';ch=getchar()) x=(x<<1)+(x<<3)+(ch^48);
	return f?x=-x:x;
}
inline void write(int x){
	x<0?x=-x,putchar('-'):0;static short Sta[50],top(0);
	do{Sta[++top]=x%10;x/=10;}while(x);
	while(top) putchar(Sta[top--]|48);
	putchar('\n');
}
signed main(){
    freopen("simple.in","r",stdin);
    freopen("simple.out","w",stdout);
    int T;
    T=read();
    for(int kkk=1;kkk<=T;kkk++){
        int n,q;
        n=read(),q=read();
        for(int i=2;i<=n;i++){
            fa[i]=read();
            add(fa[i],i);
        }
        dfs1(1,0);
        dfs2(1,1);
        for(int i=1;i<=n;i++) c[i]=read();
        for(int i=1;i<=q;i++){
            a[i].op=read();
            if(a[i].op==1) a[i].u=read(),a[i].c=read();
            else a[i].u=read();
        }
        int t=0;
        for(int i=1;i*B<=q;i++){
            ls[i]=B*(i-1)+1;
            rs[i]=B*i;
            t=i;
        }
        if(rs[t]<q) t++,ls[t]=rs[t-1]+1,rs[t]=q;
        for(int i=1;i<=t;i++)
            for(int j=ls[i];j<=rs[i];j++) 
                pos[j]=i;
        for(int op=1;op<=t;++op){
            int idx=0;
            for(int i=ls[op];i<=rs[op];++i){
                if(a[i].op==2) rk[a[i].u].push_back(i);
                for(int j=i+1;j<=rs[op];j++){
                    int x=a[i].u,y=a[j].u;
                    __lca[i-ls[op]][j-ls[op]]=__lca[j-ls[op]][i-ls[op]]=ask_lca(x,y);
                }
            }
            dfs(1);
            int qwq=0;
            for(int i=rs[op];i>=ls[op];i--){
                if(a[i].op==2) rk[a[i].u].clear();
                if(a[i].op==1){
                    int tmp=a[i].u;
                    int col=a[i].c;
                    while(tmp){
                        if(flat[tmp]) break;
                        flat[tmp]=1;
                        kl[++qwq]=tmp;
                        c[tmp]=col;
                        tmp=fa[tmp];
                    }
                }
            }
            for(int i=1;i<=qwq;i++) flat[kl[i]]=0;
        }
        for(int i=1;i<=t;i++){
            for(int j=ls[i];j<=rs[i];j++){
                if(a[j].op==2) write(ans[j]);
            }
        }
        for(int i=1;i<=tot;i++) head[i]=0;
        memset(son,0,sizeof(int)*(n+1));
        memset(d,0,sizeof(int)*(n+1));
        memset(top,0,sizeof(int)*(n+1));
        tot=0;
        
    }
}

NOIP2023模拟20联测41 红楼 ~ Eastern Dream #

给出一个长度为 n 的序列 a,有 m 次操作,格式如下:

1 x l k 对于所有满足 (i1)modxyi,将 ai 的值加 k

2 l rilrai

数组下标从 1 开始。

题解:

考虑根号分治,对于 xB 的我们发现,虽然块数多,但是循环节少,所以我们维护 fi,j 表示 modij 位上的增量,然后我们求一个前缀和 sumi,j=i=1jfx,j,对于查询这部分时,可以枚举循环节,O(1) 计算,那么这部分是 O(n)O(n)

对于 x>B,这种情况块数很少,我们可以考虑对每个块差分,发现直接暴跳复杂度也就 O(n)。对于查询可以将操作分块,先算上之前块的贡献,然后暴力扫这个块内的修改对这个查询的影响,可以 O(1) 计算,复杂度也是 O(n)O(n)

对于重构,直接做两次前缀和更新就可以。

code
#include<bits/stdc++.h>
using namespace std;
const int N=2*1e5+10;
int a[N];
struct asd{
    int op,x,y;
    int k;
    int l,r;
}b[N];
long long f[700][700];
long long sum[700][700];
long long qz[N];
long long ans[N];
int pos[N],l[N],r[N];
long long c[N],sum1[N],d[N],g[N];
inline int read(){
	int x(0);char ch=getchar();
	for(;ch<'0'||ch>'9';ch=getchar()){}
	for(;ch>='0'&&ch<='9';ch=getchar()) x=(x<<1)+(x<<3)+(ch^48);
	return x;
}
inline void write(int x){
	x<0?x=-x,putchar('-'):0;static short Sta[50],top(0);
	do{Sta[++top]=x%10;x/=10;}while(x);
	while(top) putchar(Sta[top--]|48);
	putchar('\n');
}
signed main(){
    freopen("scarlet.in","r",stdin);
    freopen("scarlet.out","w",stdout);
    int n,m;
    n=read(),m=read();
    for(int i=1;i<=n;++i){
        a[i]=read();
        qz[i]=qz[i-1]+a[i];
    }
    int B=sqrt(n);
    int t=sqrt(m);
    for(int i=1;i<=t;++i){
        l[i]=(i-1)*t+1;
        r[i]=i*t;
    }
    if(r[t]<m) t++,l[t]=r[t-1]+1,r[t]=m;
    for(int i=1;i<=t;++i)
        for(int j=l[i];j<=r[i];++j)
            pos[j]=i;
    for(int p=1;p<=m;++p){
        b[p].op=read();
        if(b[p].op==1){
            b[p].x=read(),b[p].y=read(),b[p].k=read();
            if(b[p].x<=B){
                int lim=min(b[p].x-1,b[p].y);
                for(int i=0;i<=lim;i++) f[b[p].x][i]+=b[p].k;
                sum[b[p].x][0]=f[b[p].x][0];
                for(int i=1;i<=b[p].x-1;i++) sum[b[p].x][i]=sum[b[p].x][i-1]+f[b[p].x][i];
            }
            else{
                for(int i=1;i<=n;i+=b[p].x){
                    c[i]+=b[p].k;
                    if(i+b[p].y+1>n) break;
                    c[i+b[p].y+1]-=b[p].k;
                }
            }
        }
        else{
            b[p].l=read(),b[p].r=read();
            for(int i=1;i<=B;++i){
                long long wr=sum[i][(b[p].r-1)%i]+(b[p].r-1)/i*sum[i][i-1];
                int ls=b[p].l-1;
                long long wl=sum[i][(ls-1)%i]+(ls-1)/i*sum[i][i-1];
                ans[p]+=(wr-wl);
            }
            ans[p]+=(sum1[b[p].r]-sum1[b[p].l-1]);
            for(int i=r[pos[p]-1]+1;i<p;++i){
                if(b[i].op==1){
                    if(b[i].x>B){
                        long long w=1ll*(min(b[i].x-1,b[i].y)+1)*b[i].k;
                        long long wr=1ll*min((b[p].r-1)%b[i].x+1,b[i].y+1)*b[i].k+(b[p].r-1)/b[i].x*w;
                        int ls=b[p].l-1;
                        long long wl=1ll*(min((ls-1)%b[i].x,b[i].y)+1)*b[i].k+(ls-1)/b[i].x*w;
                        ans[p]+=(wr-wl);
                    }
                }
            }
        }
        if(r[pos[p]]==p){
            for(int i=1;i<=n;++i){
                c[i]=c[i-1]+c[i];
                d[i]=d[i]+c[i];
                sum1[i]=sum1[i-1]+d[i];
            }
            memset(c,0,sizeof(long long)*(n+1));
        }
        if(b[p].op==2) printf("%lld\n",ans[p]+qz[b[p].r]-qz[b[p].l-1]);
    }
}

省选联测9 战争模拟器#

首先考虑暴力 dpfl,r,k 表示 [l,r] 最大值为 k 时的答案, gl,r,k 表示 fl,r,k 的前缀最大值,那么有转移:

fl,r,k=maxlpr{gl,p1,k+gp+1,r,k+ki(l,p),j(p,r)Qi,jc}

复杂度 O(n2Ki)

事实上我们发现并不需要保证最大值为 k,因为 p 如果不是最大值一定不是优,所以只需要

fl,r=maxlpr{fl,p1+fp+1,r+maxk{Vp,k×lippirQi,jCp,k}}

我们只需要求 Vp,k×lippirQi,jCp,k 的值,我们另 x:Vy:Ck:Qb 为所求,那么如果给 V 排个序,那么 x 单增,就可以用斜率优化,每回只需要二分凸包。

最后考虑在上面的基础上考虑改变 l,r,p 的枚举顺序,就可以使斜率单增,那么就不用二分直接扫一遍就可以。

复杂度 O(n3+nKi+KilogKi)

code
#include<bits/stdc++.h>
#define int long long
using namespace std;
const int N=1005;
const int inf=LONG_LONG_MAX-10;
int q[N][N],sum[N][N];
int K[N];
int V[N][N],C[N][N];
struct asd{
    int v,c;
}b[N][N*30];
int n;
int ask(int l,int r,int p){
    return sum[p][r]-sum[p][p-1]-sum[l-1][r]+sum[l-1][p-1];
}
int f[N][N];
int st[N][N*30];
int top[N];
bool amp(asd a,asd b){
    return a.v<b.v;
}
int X(int i,int j){
    int kl=b[i][j].v;
    return kl;
}
int Y(int i,int j){
    int kl=b[i][j].c;
    return kl;
}
signed main(){
    freopen("war.in","r",stdin);
    freopen("war.out","w",stdout);
    scanf("%lld",&n);
    for(int i=1;i<=n;++i)
        for(int j=i;j<=n;++j)
            scanf("%lld",&q[i][j]);
    for(int i=1;i<=n;i++)
        for(int j=1;j<=n;j++){
            sum[i][j]=sum[i-1][j]+sum[i][j-1]-sum[i-1][j-1]+q[i][j];
        }
    int mx=0;
    for(int i=1;i<=n;++i){
        scanf("%lld",&K[i]);
        mx=max(mx,K[i]);
        for(int j=1;j<=K[i];++j) scanf("%lld%lld",&b[i][j].v,&b[i][j].c);
        sort(b[i]+1,b[i]+K[i]+1,amp);
    }
    for(int i=1;i<=n;i++){
        for(int j=1;j<=n;j++){
            if(i<=j) f[i][j]=-inf;
            else f[i][j]=0;
        }
    }
    __int128 it=1;
    for(int i=1;i<=n;i++){
        int tp=0;
        for(int j=1;j<=K[i];j++){
            while(tp>1 && it* (Y(i,st[i][tp])-Y(i,st[i][tp-1])) * (X(i,j)-X(i,st[i][tp-1])) >= it* (Y(i,j)-Y(i,st[i][tp-1])) * (X(i,st[i][tp])-X(i,st[i][tp-1])) ) tp--;
            st[i][++tp]=j; 
        }
        top[i]=tp;
        int Q=ask(i,i,i);
        int pos=1;
        while(pos+1<=tp && it* (Y(i,st[i][pos+1])-Y(i,st[i][pos]))   <= it* Q * (X(i,st[i][pos+1])-X(i,st[i][pos]))) pos++;
        f[i][i]=b[i][st[i][pos]].v*Q-b[i][st[i][pos]].c;
    }
    for(int l=n;l>=1;l--){
        for(int p=l;p<=n;p++){
            int pos=1;
            for(int r=p;r<=n;r++){
                int Q=ask(l,r,p);
                while(pos+1<=top[p] && it* (Y(p,st[p][pos+1])-Y(p,st[p][pos]))  <= it* Q * (X(p,st[p][pos+1])-X(p,st[p][pos])) )  pos++;
                f[l][r]=max(f[l][r],f[l][p-1]+f[p+1][r]+b[p][st[p][pos]].v*Q-b[p][st[p][pos]].c);
            }
        }
    }
    printf("%lld",f[1][n]);
}

省选联测9 种树#

首先求出每个点的期望深度 depi,枚举 i 接在 j 下面。

depi=ci+j=1i1ajbi1×(depj+cj)

维护前缀可以 O(n) 转移。

u<v,枚举 uvlca,设 P(i)ilca 的概率,那么答案为

depu+depv2i1uP(i)depi

对于编号在 (x,u) 的点既有可能在 ux 也有能在 vx。 对于编号 (u,v) 的点只有可能在 vx,所以:

P(x)=axbu1×axbv1×x<i<u(1+aibi1+aibi1)×u<i<v(1+aibi1)=ax2bu1×bv1×x<i<u2ai+bi1bi1×u<i<vai+bi1bi1=ax2bu1×bv1×x<i<u2ai+bi1bi1×u<i<vbibi1=ax2bu1×su×x<i<u2ai+bi1bi1

注意 ulca 的情况特殊处理,首先求出它的概率,化简完就是 aubu,在乘上 depu,就是 lcau 的贡献。

code
#include<bits/stdc++.h>
#define int long long
using namespace std;
const int N=1e6+10;
const int mod=1e9+7;
int a[N],b[N],c[N];
int dep[N];
int mgml(int x,int p){
    int ans=1;
    while(p){
        if(p&1) ans=(ans*x)%mod;
        x=(x*x)%mod;
        p>>=1;
    }
    return ans;
}
int dlca[N];
signed main(){
    freopen("tree.in","r",stdin);
    freopen("tree.out","w",stdout);
    int n,q;
    scanf("%lld%lld",&n,&q);
    for(int i=1;i<n;i++) scanf("%lld",&a[i]),b[i]=b[i-1]+a[i];
    for(int i=1;i<=n;i++) scanf("%lld",&c[i]);
    int sum=0;
    for(int i=1;i<=n;i++){
        dep[i]=(sum*mgml(b[i-1],mod-2)%mod+c[i])%mod;
        if(i==1) dep[i]=0;
        sum=(sum+a[i]*(dep[i]+c[i])%mod)%mod;
    }
    for(int u=2;u<=n;u++){
        dlca[u]=dlca[u-1]*b[u-2]%mod*mgml(b[u],mod-2)%mod;
        dlca[u]=dlca[u]*((2*a[u-1]%mod+b[u-2])%mod)%mod*mgml(b[u-2],mod-2)%mod;
        int x=u-1;
        int px=a[x]*a[x]%mod*mgml(b[u-1]*b[u]%mod,mod-2)%mod;
        dlca[u]=(dlca[u]+px*dep[x]%mod)%mod;
    }
    for(int u=1;u<=n;u++){
        int op=a[u]*mgml(b[u],mod-2)%mod;
        dlca[u]=(dlca[u]+op*dep[u]%mod)%mod;
    }
    for(int i=1;i<=q;i++){
        int u,v;
        scanf("%lld%lld",&u,&v);
        if(u==v){
            printf("0\n");
            continue;
        }
        if(u==1){
            printf("%lld\n",dep[v]);
            continue;
        }
        if(u>v) swap(u,v);
        int ans=(dep[u]+dep[v]-2*dlca[u]%mod+2*mod)%mod;
        printf("%lld\n",ans);
    }
}

省选联测11 Giao 徽的烤鸭 #

树形 DP,设 fu,i 表示在以 u 为根的书中小于等于 i 的点全选的最大净利润。对于父子点 u,v,用 fv,j 更新 fu,i,考虑有什么限制。首先是 uv 的,有 j+1i,然后是 vu,有 ij1

所以转移为 fu,i=min(fv,i1,fv,i,fv,i+1)

code
// ubsan: undefined
// accoders
#include<bits/stdc++.h>
#define int long long
using namespace std;
const int N=5005;
int w[N],v[N];
int head[N],nex[N*2],ver[N*2],tot=0;
void add(int x,int y){
    ver[++tot]=y,nex[tot]=head[x],head[x]=tot;
}
int f[N][N];
int n;
int siz[N];
void dfs(int x,int fa){
    f[x][0]=0;
    for(int i=1;i<=n;i++){
        f[x][i]=v[i-1]-w[x];
    }
    for(int i=head[x];i;i=nex[i]){
        int y=ver[i];
        if(y==fa) continue;
        dfs(y,x);
    }
    for(int i=head[x];i;i=nex[i]){
        int y=ver[i];
        if(y==fa) continue;
        f[x][0]+=max(f[y][1],f[y][0]);
        for(int j=1;j<=n;j++) f[x][j]+=max({f[y][j],f[y][j-1],f[y][j+1]});
    }
    // for(int i=n-1;i>=1;i--) f[x][i]=max(f[x][i+1],f[x][i]);
}
signed main(){
    freopen("duck.in","r",stdin);
    freopen("duck.out","w",stdout);
    scanf("%lld",&n);
    for(int i=1;i<=n;i++) scanf("%lld",&w[i]);
    for(int i=0;i<n;i++) scanf("%lld",&v[i]);
    for(int i=1;i<n;i++){
        int x,y;
        scanf("%lld%lld",&x,&y);
        if(y==0) cerr<<"fk ";
        add(x,y),add(y,x);
    }
    dfs(1,0);
    int ans=0;
    for(int i=0;i<=n;i++)  ans=max(ans,f[1][i]);
    printf("%lld",ans);
}

省选联测11 GA Dance of Fire and Ice #

首先根据原根可以讲乘法转化为加法,因为原根有这样的性质:

假如 gp 的原根,那么 gix(modp) 对于 i[i,p1]x[i,p1] 一一对应。

所以将 x 变为 gloggx,那么两个数的乘法就变成了两个 log 的加法,而且还是一一对应的,然后可以用 bitset 优化背包,然后对于同一个 p同时操作次数次,但是加入一次操作后没有变化,那么就可以停止,复杂度 p2,可以卡过。

code


#include<bits/stdc++.h>
#define int long long
using namespace std;
const int N=1e6;
const int M=2*1e5+10;
int a[N],b[N];
int top1=0,top2=0;
int phi[N],prime[N];
bool nprime[N];
int p,n,g;
int lg[N];
bitset<M> bt; 
void get_prime(){
    phi[1]=1;
    for(int i=2;i<=p;i++){
        if(!nprime[i]){
            prime[++prime[0]]=i;
            phi[i]=i-1;
        }
        for(int j=1;j<=prime[0] && i*prime[j]<=p;j++){
            int k=i*prime[j];
            nprime[k]=1;
            if(i%prime[j]==0){
                phi[k]=prime[j]*phi[i];
                break;
            }
            else phi[k]=(prime[j]-1)*phi[i];
        }
    }
}
int gcd(int a,int b){
    if(b==0) return a;
    else return gcd(b,a%b);
}
int qpow(int x,int p,int mod){
    int ans=1;
    while(p){
        if(p&1) ans=(ans*x)%mod;
        x=(x*x)%mod;
        p>>=1;
    }
    return ans;
}
int vi[N],tp=0;
bool check(int x,int m){
    if(gcd(x,m)!=1) return 0;
    for(int i=1;i<=tp;i++){
        // cout<<qpow(x,phi[m]/vi[i],m)<<" "<<x<<" "<<phi[m]/vi[i]<<endl;
        if(qpow(x,phi[m]/vi[i],m)%m==1) return 0;
    }
    return 1;
}
int sum[N];
signed main(){
    freopen("dance.in","r",stdin);
    freopen("dance.out","w",stdout);
    scanf("%lld%lld",&p,&n);
    nprime[1]=1;
    get_prime();
    // cout<<phi[p]<<endl;
    int tmp=p;
    for(int i=2;i*i<=tmp;i++){
        if(phi[p]%i==0){
            if(!nprime[i]) vi[++tp]=i;
            if(phi[p]/i!=i && !nprime[phi[p]/i]) vi[++tp]=phi[p]/i;
        }
    }
    int t=sqrt(sqrt(p))+1;
    for(int i=2;;i++){
        if(check(i,p)){
            g=i;
            break;
        }
    }
    for(int i=1;i<=p-1;i++){
        int x=qpow(g,i,p);
        lg[x]=i;
    }
    bt[0]=1;
    int vis0=0;
    for(int i=1;i<=n;i++){
        int op,x;
        scanf("%lld%lld",&op,&x);
        x%=p;
        if(x==0){
            vis0=1;
            continue;
        }
        if(!op) bt[lg[x]]=1;
        else sum[lg[x]]++;
    }

    for(int x=1;x<p;x++){
        if(sum[x]){
            for(int j=1;j<=sum[x];j++){
                std::bitset<M> nnext = bt;
                nnext=bt|(bt>>x)|(bt<<(p-x-1));

                if (nnext == bt) {
                    break;
                } else {
                    bt = nnext;
                }
            }
        }
    }
    int ans=0;
    for(int i=0;i<p-1;i++) if(bt[i]) ans++;
    cout<<ans+(vis0==1)<<endl;

}

省选联测12 硬币#

我们考虑这个序列是 2,5,10,17,16,37,50,65,我们枚举每一位,首先第一位为 2 然后将后面带 2 因数的消掉,发现是第 (1+2k) 位,然后继续,发现是 5,我们把因数 5 消掉,发现是第 (2+5k) 位,考虑证明。

首先第 p 位满足 pp+1modx=0,那么 (p+xk)2+1 拆开后发现也可以被 x 整除。

现在考虑枚举每个数时,剩下的数都是质数。

假如枚举到一个合数 x 有一个质因数 p,那么 xkp>0 的最小的包含 p 的因数的那个数一定可以吧当前位置的 p 消掉,所以不会剩下两个质数情况。

code
%:pragma GCC optimize(3)
%:pragma GCC optimize("Ofast")
%:pragma GCC optimize("inline")
#include<bits/stdc++.h>
#define int long long
using namespace std;
const int N=1e6+100;
int ans[N];
int w[N];
signed main(){
    freopen("coins.in","r",stdin);
    freopen("coins.out","w",stdout);
    for(int i=1;i<=1e6;i++) ans[i]=w[i]=i*i+1;
    for(int i=1;i<=1e6;i++){
        if(w[i]==1) continue;
        int k=w[i];
        for(int j=i;j<=1e6;j+=k){
            while(w[j]%k==0) w[j]/=k;
            ans[j]=min(ans[j],k);
        }
        // cout<<i<<endl;
    }
    int q;
    scanf("%lld",&q);
    for(int i=1;i<=q;i++){
        int x;
        scanf("%lld",&x);
        if(ans[x]==x*x+1){
            printf("-1\n");
        }
        else{
            printf("%lld %lld\n",ans[x],(x*x+1)/ans[x]);
        }
    }
}

省选联测12 猜数#

首先有转移 fl,r=minlpr(max(fl,p1,fp+1,r)+p)

直接转移发现是 n3

但是发现对于区间 (l,r) 设关键点 k0,对于 p(l,k0)fp+1,rfl,p1 ,右侧相反。

那么我们可以先枚举 r 在枚举 l,对于左侧进行单调队列, 右侧直接取决策点就可以,因为在往右均增大。复杂度 n2

打表发现,决策点在距离右侧 nlog10n 的范围内,所以可以滚动,复杂度 O(n2log10n)

还可以打表,讲序列差分后压成 80 进制后,每个数只需要占 3 个字符,进行差分后就只占 1 个字符。

compress
#include <bits/stdc++.h>
using namespace std;
const char s[] = "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz+-";
int main() {
    freopen("out.out", "r", stdin);
    freopen("compress.out", "w", stdout);
    int x;
    while (scanf("%d,", &x) != EOF) {
        putchar(s[x % 64]);
        putchar(s[x / 64 % 64]);
        putchar(s[x / 64 / 64 % 64]);
    }
    return 0;
}
code
#include<bits/stdc++.h>
using namespace std;
const int N=50005;
const int S=5005;
const int M=5100;
int f[50001];
int g[5500][5500];
int st[N],l,r;
signed main(){
    freopen("guess.in","r",stdin);
    freopen("guess.out","w",stdout);
    int n;
    scanf("%d",&n);
    long long sum=0;
    for(int j=2;j<=n;j++){
        int p=j;
        l=1,r=0;
        int lim=max(1,j-S-1);
        int mn=INT_MAX;
        memset(g[j%M],0,sizeof(g[j%M]));
        for(int i=j-1;i>=lim;i--){
            while(p>i && g[j%M][(p+1)%M]<g[(p-1)%M][i%M]) p--;
            while(st[l]>p && l<=r) l++;
            while(l<=r && st[r]+g[j%M][(st[r]+1)%M] > i+g[j%M][(i+1)%M]) r--;
            st[++r]=i;
            g[j%M][i%M]=min(st[l]+g[j%M][(st[l]+1)%M],p+1+g[p%M][i%M]);
            mn=min(mn,max(g[j%M][(i+1)%M],f[i-1])+i);
        }
        f[j]=mn;
        sum+=f[j];
    }
    printf("%lld",sum);
}

省选联测12 猜数#

A=(c1,c2,cn) 表示每种牌的数量, f(A) 是状态 A 的答案,肯定可以贪心求出来,策略是能换就换。

然后发现 2nn!1 可以换成一张 1

不妨将每种状态都倒换成 1 号卡牌,有初始状态 P=i=1nai2i1(i1)!

这样每个状态都对应一个数,我们设 ai 表示第 i 包 卡牌全部倒换成 1 号卡牌的数量,Q 表示终止状态,那么有 :

Q=P+i=1nxiaiy×(2nn!1)

最后减去 y×(2nn!1) 是因为终止一定可以变为 2nn! 以内的状态。

裴蜀定理:设 a,b 为不都为 0 的整数,则 ax+by=d 有解的充要条件是 dgcd(a,b)

那么推广到多个数,设 d=gcd(a1,a2,...,an,2nn!1),那么有 d(QP),那么 Q=kd+P,我们要 Q 最小。

考虑怎么做:

方法一:暴力枚举 k,然后每次都计算答案,那么复杂度 O(2nn!d×n)

方法二:同余最短路,就是相当于 「至少要拼几次才能拼出模 𝐾𝑝 的数」,那么枚举要拿哪张牌直接对 x(x+fi) 连边,边权为一,那么答案就是 disPmodd。复杂度 O(nd)

然后根号分治,对于 d 小于 2nn!1 的执行方法二,反之方法一。

code




#include<bits/stdc++.h>
#define int long long
using namespace std;
const int N=20;
const int M=2*1e6;
int a[N];
int n,m;
int f[N];
int g[M];
void init(){
    f[0]=1;
    for(int i=1;i<=n;i++) f[i]=f[i-1]*i*2;
}
int ask(){
    int ans=0;
    for(int i=1;i<=n;i++) ans+=a[i]*f[i-1];
    return ans;
}
int query(int x){
    int ans=0;
    for(int i=1;i<=n;i++){
        ans+=(x%(2*i));
        x/=(2*i);
    }
    return ans;
}
void solve(){
    scanf("%lld%lld",&n,&m);
    for(int i=1;i<=n;i++) scanf("%lld",&a[i]);
    init();
    int p=ask();
    int d=f[n]-1;
    for(int i=1;i<=m;i++){
        for(int j=1;j<=n;j++) scanf("%lld",&a[j]);
        int sa=ask();
        d=__gcd(d,sa);
    }
    if(d<=sqrt(f[n])){
        queue<int> q;
        memset(g,-1,sizeof(g));
        for(int i=0;i<n;i++){
            q.push(f[i]%d);
            g[f[i]%d]=1;
        }
        while(!q.empty()){
            int x=q.front();
            q.pop();
            for(int i=0;i<n;i++){
                int v=(x+f[i])%d;
                if(g[v]==-1){
                    g[v]=g[x]+1;
                    q.push(v);
                }
            }
        }
        printf("%lld\n",min(g[p%d],query(p)));
    }
    else{
        int ans=query(p);
        for(int x=(p%d ? p%d : d);x<f[n];x+=d){
            ans=min(ans,query(x));
        }
        printf("%lld\n",ans);
    }
}
signed main(){
    freopen("card.in","r",stdin);
    freopen("card.out","w",stdout);
    solve();
}

省选联测13 树上横跳#

楼房重建的思想,首先倍增预处理出来区间最小值和区间答案,那么考虑将上下两个区间合并,首先上区间直接算入答案就可以,设上区间最小值为 lim,那么考虑下区间,加入下区间最小值大于 lim,那么直接用上区间最小值跳下区间就可以;反之我们将下区间分成 A1A2 两个区间,如果 A1 最小值大于 lim 那么递归考虑 A2;否则递归考虑 A1

code




#include<bits/stdc++.h>
#define int long long
using namespace std;
const int N=3*1e5+10;
int n,m,t;
int head[N],nex[N*2],ver[N*2],edge[N*2],tot=0;
void add(int x,int y,int w){
    ver[++tot]=y,nex[tot]=head[x],head[x]=tot,edge[tot]=w;
}
struct T2{
    int dfn[N],num=0,fa[N],siz[N];
    void dfs(int x,int fat){
        fa[x]=fat;
        dfn[x]=++num;
        siz[x]=1;
        for(int i=head[x];i;i=nex[i]){
            int y=ver[i];
            if(y==fat) continue;
            dfs(y,x);
            siz[x]+=siz[y];
        }
    }
    int mp[3005][3005];
    int anss[3005][3004];
    int st[N],top,dist[N],rk[N];
    int f[N][50],g[N][50];
    void dfs1(int x,int fat){
        st[++top]=x;
        rk[x]=top;
        for(int i=head[x];i;i=nex[i]){
            int y=ver[i];
            if(y==fat) continue;
            dist[top+1]=dist[top]+edge[i];
            dfs1(y,x);
        }
    }
    int cnt[N],tp=0;
    void init(){
        dfs(1,0);
        dfs1(1,0);
        int op=top;
        for(int i=1;i<=n;i++){
            while(tp>0 && st[cnt[tp]]>st[i]){
                f[cnt[tp]][0]=i;
                tp--;
            }
            cnt[++tp]=i;
        }
        while(tp){
            f[cnt[tp]][0]=n;
            tp--;
        } 
        for(int i=1;i<=n;i++) g[i][0]=(dist[f[i][0]]-dist[i])*st[i];
        int t=log2(n)+1;
        for(int j=1;j<=t;j++){
            for(int i=1;i<=n;i++){
                f[i][j]=f[f[i][j-1]][j-1];
                g[i][j]=g[i][j-1]+g[f[i][j-1]][j-1];
            }
        }
    }
    void solve(){
        for(int p=1;p<=m;p++){
            int x,y;
            scanf("%lld%lld",&x,&y);
            if(dfn[y]>=dfn[x] && dfn[y]<=dfn[x]+siz[x]-1){
                int fx=rk[x],fy=rk[y];
                int ans=0;
                for(int i=t;i>=0;i--){
                    if(f[fx][i]<=fy){
                        ans+=g[fx][i];
                        fx=f[fx][i];
                    }
                }
                ans+=st[fx]*(dist[fy]-dist[fx]);
                printf("%lld\n",ans);
            }
            else printf("-1\n");
        }
    }
}T;
int dep[N],dist[N],fa[N][50],mn[N][50],f[N][50];
int dfn[N],num=0,siz[N];
int merge(int lim,int x,int k){
    if(mn[x][k]>=lim) return (dist[x]-dist[fa[x][k]])*lim;
    if(fa[x][k]<lim) return f[x][k];
    if(mn[fa[x][k-1]][k-1]>=lim) return lim*(dist[fa[x][k-1]]-dist[fa[x][k]])+merge(lim,x,k-1);
    else return f[x][k]-f[fa[x][k-1]][k-1]+merge(lim,fa[x][k-1],k-1); 
}
void dfs(int x,int fat){
    dfn[x]=++num;
    siz[x]=1;
    dep[x]=dep[fat]+1;
    fa[x][0]=mn[x][0]=fat;
    for(int j=1;j<=t;j++){
        int i=x;
        fa[i][j]=fa[fa[i][j-1]][j-1];
        mn[i][j]=min(mn[i][j-1],mn[fa[i][j-1]][j-1]);
        f[i][j]=f[fa[i][j-1]][j-1]+merge(mn[fa[i][j-1]][j-1],i,j-1);
    }
    for(int i=head[x];i;i=nex[i]){
        int y=ver[i];
        if(y==fat) continue;
        dist[y]=dist[x]+edge[i];
        f[y][0]=edge[i]*x;
        dfs(y,x);
        siz[x]+=siz[y];
    }
}
struct asd{
    int x,k;
}st[N];
int solve(int x,int y){
    int top=0;
    int tmp=y;
    for(int j=t;j>=0;j--){
        if(dep[fa[tmp][j]]>=dep[x]){
            st[++top]={tmp,j};
            tmp=fa[tmp][j];
        }
    }
    int ans=f[st[top].x][st[top].k];
    int lim=mn[st[top].x][st[top].k];
    for(int i=top-1;i>=1;i--){
        ans+=merge(lim,st[i].x,st[i].k);
        lim=min(lim,mn[st[i].x][st[i].k]);
    }
    return ans;
}
int du[N];
signed main(){
    freopen("tree.in","r",stdin);
    freopen("tree.out","w",stdout);
    scanf("%lld%lld",&n,&m);
    t=log2(n)+1;
    for(int i=1;i<n;i++){
        int x,y,w;
        scanf("%lld%lld%lld",&x,&y,&w);
        add(x,y,w),add(y,x,w);
        du[x]++,du[y]++;
    }
    int op=0;
    for(int i=1;i<=n;i++){
        if(du[i]>2) op=1;
    }
    if(!op){
        T.init();
        T.solve();
        return 0;
    }
    dfs(1,0);
    for(int i=1;i<=m;i++){
        int x,y;
        scanf("%lld%lld",&x,&y);
        if(dfn[y]>=dfn[x] && dfn[y]<=dfn[x]+siz[x]-1){
            printf("%lld\n",solve(x,y));
        }
        else printf("-1\n");
    }
}

省选联测13 定价#

首先考虑暴力,那么就是假如上一位有若干一,那么这一位需要找到最高位的上一位为一这一位不能为一的位(如果没有从第零位开始),然后从那一位向上找到第一位可以位一的但上一位不为一的位,然后选它并将其下面的一全部清掉。

考虑怎么优,我们维护一个栈,里面存的是我们选的那些位置的一,每一个数维护一个 set 表示那个数可以选那些一,然后再维护一个 bitset 表示为每一位有哪些数可以选一。

将上面的过程转化为我们通过 bitset 找到如果这一位选一,那么下一个不能选一的位置是哪里,将其压入优先队列,然后到达那一位的时候从优先队列中找到与这一个数有关,位数取 max。然后这个 max 位就是最高位的上一位为一这一位不能为一的位。然后记得将栈里元素弹出加入的时候,将优先队列里的也要删除加入,所以用可删堆。

code
#include<bits/stdc++.h>
#define int long long
using namespace std;
const int N=1005;
const int M=1000005;
const int mod=1e9+7;
bitset<N> bt[M];
set<int> s[N];
struct asd{
    int op,r,c;
}a[M];
int qz[M],cnt=0;
int st[M],top=0;
unordered_map<int,int> mp;
struct qwe{
    int p,k;
    friend bool operator <(qwe a,qwe b){
        if(a.p==b.p) return a.k<b.k;
        return a.p>b.p;
    }
}b[M];
priority_queue<qwe> ads,dels;
inline void insert(qwe x){
    ads.push(x);
}
inline void dele(qwe x){
    dels.push(x);
    while(!dels.empty() && !ads.empty()  & ads.top().k==dels.top().k && ads.top().p==dels.top().p){
        ads.pop();
        dels.pop();
    }
}
inline qwe begin(){
    while(!dels.empty() && !ads.empty() && ads.top().k==dels.top().k && ads.top().p==dels.top().p){
        ads.pop();
        dels.pop();
    }
    return ads.top();
}
int siz(){
    if(ads.empty()) return 0;
    return 1;
}
inline void clear(){
    while(!dels.empty()) dels.pop();
    while(!ads.empty()) ads.pop();
}
inline int qpow(int x,int p){
    int ans=1;
    while(p){
        if(p&1) ans=(ans*x)%mod;
        x=(x*x)%mod;
        p>>=1;
    }
    return ans;
}
bool vis[M];
signed main(){
    // freopen("2-1.in","r",stdin);
    // freopen("price.out","w",stdout);
    freopen("price.in","r",stdin);
    freopen("price.out","w",stdout);
    int n,m,q;
    scanf("%lld%lld%lld",&n,&m,&q);
    for(int i=1;i<=q;i++){
        scanf("%lld",&a[i].op);
        if(a[i].op==1){
            scanf("%lld%lld",&a[i].r,&a[i].c);
            a[i].c=m-a[i].c;
            qz[++cnt]=a[i].c;
        }
    }
    sort(qz+1,qz+cnt+1);
    // cout<<cnt<<endl;
    cnt=unique(qz+1,qz+cnt+1)-(qz+1);
    // return 0;
    for(int i=1;i<=cnt;i++){
        // bt[i].set(M,1);
        mp[qz[i]]=i;
        b[i]={0,0};
    }
    for(int i=1;i<=q;i++){
        if(a[i].op==1) a[i].c=mp[a[i].c];
    }
    for(int p=1;p<=q;p++){
        if(a[p].op==1){
            if(bt[a[p].c][a[p].r]){
                s[a[p].r].erase(a[p].c);
                bt[a[p].c][a[p].r]=0;
            }
            else{
                s[a[p].r].insert(a[p].c);
                bt[a[p].c][a[p].r]=1;
            }
        }
        else{
            top=0;
            clear();
            int getans=0;
            int ans=0,anss=0;
            if(s[1].size()){
                for(int i=1;i<=n;i++){
                    int mx=0;
                    while(siz()){
                        qwe s=begin();
                        dele(s);
                        // cout<<"w "<<s.p<<" "<<dels.top().p<<" "<<ads.top().p<<" "<<i<<" "<<" "<<dels.top().k<<" "<<ads.top().k;
                        if(s.p<=i){
                            // cout<<" h ";
                            if(s.p==i) mx=max(mx,s.k);
                            b[s.k]={0,0};
                        }
                        else{
                            insert(s);
                            break;
                        }
                    }
                    auto op=s[i].lower_bound(mx);
                    for(;op!=s[i].end();op++){
                        if(!vis[*op]) break;
                    }
                    if(op==s[i].end()){
                        getans=1;
                        break;
                    }
                    while(st[top]<*op && top>=1){
                        vis[st[top]]=0;
                        if(b[st[top]].k){
                            dele(b[st[top]]);
                            b[st[top]]={0,0};
                        }
                        anss=(anss-qpow(2,qz[st[top]])+mod)%mod;
                        top--;
                    }
                    st[++top]=*op;
                    vis[st[top]]=1;
                    anss=(anss+qpow(2,qz[st[top]]))%mod;
                    int wh=(~bt[*op])._Find_next(i);
                    if(wh<=n){
                        if(b[*op].p){
                            dele(b[*op]);
                        }
                        b[*op]={wh,*op};
                        insert(b[*op]);
                    }
                    ans=(ans+anss)%mod;
                }
                if(getans==1) printf("-1\n");
                else printf("%lld\n",ans);
                while(top){
                    b[st[top]]={0,0};
                    vis[st[top]]=0;
                    top--;
                }
            }
            else{
                printf("-1\n");
            }
        }
    }
}

作者:bloss

出处:https://www.cnblogs.com/jinjiaqioi/p/17953270

版权:本作品采用「署名-非商业性使用-相同方式共享 4.0 国际」许可协议进行许可。

posted @   _bloss  阅读(22)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· 单线程的Redis速度为什么快?
· 展开说说关于C#中ORM框架的用法!
· Pantheons:用 TypeScript 打造主流大模型对话的一站式集成库
· SQL Server 2025 AI相关能力初探
· 为什么 退出登录 或 修改密码 无法使 token 失效
点击右上角即可分享
微信分享提示
more_horiz
keyboard_arrow_up dark_mode palette
选择主题
menu