四月好题

typewriter

题意

\(n\) 个字符集,第 \(i\) 个字符集为 \(S_i\)。可以选择任意一个字符集,然后用这个字符集的字符打出长为 \(l\) 的字符串。求最后能够打出多少种字符串。\(n\le 18,l\le 10^9,S_i\) 只包含小写字母。

解法

由于 \(n=18\) 而字符集大小为 \(26\),故而直接使用容斥的方式计算所有可能的字符串数量即可(比看任一个字符集是否是某个字符集的子集更优)。

代码

点此查看代码
#include <bits/stdc++.h>
using namespace std;
#define ll long long
const int md=998244353;
int n,l,i,j,v,p,q,msk;
int m[20],k[20];
char s[30];
ll x;
ll Pow(ll d,int z){
    ll ret=1;
    do{
        if(z&1){ret*=d;if(ret>=md) ret%=md;}
        d*=d;if(d>=md) d%=md;
    }while(z>>=1);
    return ret;
}
int main(){
    scanf("%d%d",&n,&l);
    for(i=0;i<n;++i){
        scanf("%s",s+1);
        m[i]=strlen(s+1);
        for(j=1;j<=m[i];++j) k[i]|=(1<<(s[j]-'a'));
    }
    const int siz=(1<<n)-1;
    for(i=1;i<=siz;++i){
        msk=67108863;v=-1;q=0;
        for(p=0;p<n;++p){
            if(i&(1<<p)){
                msk&=k[p];
                v=-v;
            }
        }
        for(;msk;msk^=(msk&-msk)) ++q;
        x+=Pow(q,l)*v;
        if(x<0) x+=md;
        if(x>=md) x-=md;
    }
    printf("%lld",x);
    return 0;
}

Game On Tree 3

题意

有两个人和一棵大小为 \(n\) 的树,根节点为 \(1\),非根节点上均有一个非负权值,其中 \(i\) 点的权值为 \(a_i\)。后手有一枚在 \(1\) 结点的棋子。这两个人可以在树上轮流操作。

先手每次操作后可以将任意一个节点上的 \(a\) 值改为 \(0\),而后手可以将棋子从此节点移动到其任意一个子结点上。

后手可以在其某次移动完棋子后立即结束操作,而在棋子移到叶子节点上时则必须结束操作。

先手会最小化操作结束时棋子所在的节点上的 \(a\) 值,后手会最大化这个值。

求先后手采取最优操作后棋子所在处的 \(a\) 值。\(n\le 2\cdot 10^5\)

解法

考虑后手能否走到某个不小于某个值 \(v\) 的点。

此时可以令 \(a\) 值小于 \(v\) 的点作为黑点,其他点作为白点。这样问题转化成先手每次可以将某个白点变黑,而后手需要走到白点上才能取胜,否则先手取胜。

\(dp_i\) 为先手至少需要在后手到 \(i\) 点之前,使得后手不能在 \(i\) 树上走到白点时在 \(i\) 树上变黑的白点数。显然有 \(dp_i=\max(1,\sum_{j\in son_i}dp_j)-[a_i<v]\)。若后手走到 \(i\) 点,先手进行操作后,\(i\) 树上变黑的白点数小于 \(\sum_{j\in son_i}dp_j\),则先手肯定能找到 \(i\) 的一个子节点 \(j\) 满足 \(j\) 树上变黑的白点数小于 \(dp_j\),先手不能取胜。同时若后手在 \(i\) 点时,先手能再次在 \(i\) 树上变黑一个白点,满足 \(i\) 不存在一个子节点 \(j\),其中 \(j\) 满足 \(j\) 树上变黑的白点数小于 \(dp_j\)。如果 \(i\) 点是白点,则必须事先把 \(i\) 点变黑。

如果根节点的 \(dp\) 值非 \(0\),则后手一定能走到一个白点上,先手不能必胜;否则先手必胜。

若后手能够到达 \(a\) 值不小于 \(v\) 的点,则 \(\forall w\le v\),其一定能到达 \(a\) 值不小于 \(w\) 的点。可以二分处理,求出答案。

代码

点此查看代码
#include <bits/stdc++.h>
using namespace std;
const int maxn=200010;
int n,i,u,v,t,s,m,l,r;
int h[maxn],a[maxn],dp[maxn];
struct edge{int to,nxt;}E[maxn<<1];
void dfs(const int &p,const int &f){
    int lp,to,sm=0;
    for(lp=h[p];lp;lp=E[lp].nxt){
        to=E[lp].to;
        if(to==f) continue;
        dfs(to,p);
        sm+=dp[to];
    }
    --sm;
    if(sm<0) sm=0;
    if(a[p]>m) ++sm;
    dp[p]=sm;
}
int main(){
    scanf("%d",&n);
    for(i=2;i<=n;++i){
        scanf("%d",a+i);
        r=max(r,a[i]);
    }
    for(i=1;i<n;++i){
        scanf("%d%d",&u,&v);
        E[++t]={v,h[u]};h[u]=t;
        E[++t]={u,h[v]};h[v]=t;
    }
    while(l<r){
        m=(l+r)>>1;
        dfs(1,0);
        if(dp[1]) l=m+1;
        else r=m;
    }
    printf("%d",l);
    return 0;
}

01? Queries

题意

有一个长为 \(n\)\(01?\)\(s\),其中 \(?\) 可以看作 \(0\)\(1\)。同时有 \(q\) 次修改,每次修改串中的一个字符,求串中的不同子序列个数模 \(998244353\)\(n,q\le 10^5\)

解法

考虑无修改时的做法。

\(dp_{i,0}\) 表示 \(s_1\sim s_i\) 中以 \(0\) 结尾的不同子串个数,\(dp_{i,1}\) 表示 \(s_1\sim s_i\) 中以 \(1\) 结尾的不同子串个数。考虑 \(dp_{i-1}\)\(dp_i\) 的转移。

为了方便,我们只考虑 \(dp_i\) 中的子串均以 \(s_i\) 结尾的情况(如果不以 \(s_i\) 结尾,则可以把结尾换成 \(s_i\),不会造成其他影响)。显然,若只这样考虑,则 \(dp_{i,s_i}\) 即为 \(s_1\sim s_{i-1}\) 的所有不同子串数(包括空串),即 \(dp_{i-1,0}+dp_{i-1,1}+1\)\(dp_{i,!s_i}\) 即为 \(dp_{i-1,!s_i}\)。若 \(s_i=\;?\),则 \(dp_{i,0}=dp_{i,1}=dp_{i-1,0}+dp_{i-1,1}+1\)

考虑使用矩阵快速幂对修改后的 \(dp\) 值进行计算。可以发现可以用矩阵乘法优化如下:

\[\begin{bmatrix}dp_{i,0}&dp_{i,1}&1\end{bmatrix}=\begin{bmatrix}dp_{i-1,0} &dp_{i-1,1}&1\end{bmatrix}\times\begin{cases}\begin{bmatrix}1&0&0\\1&1&0\\1&0&1\end{bmatrix}&s_i=0\\\begin{bmatrix}1&1&0\\0&1&0\\0&1&1\end{bmatrix}&s_i=1\\\begin{bmatrix}1&1&0\\1&1&0\\1&1&1\end{bmatrix}&s_i=?\\\end{cases}\]

边界条件即为 \(dp_{0,0}=dp_{0,1}=0\)。使用线段树维护矩阵即可。

代码

点此查看代码
#include <bits/stdc++.h>
using namespace std;
const int maxn=100010;
const int md=998244353;
char s[maxn];
int n,q,u,x,_i,_j,_k;
struct seg{
    long long mat[3][3];
    int l,r,m;
}tr[maxn<<2];
#define l(p) tr[p].l
#define r(p) tr[p].r
#define m(p) tr[p].m
#define ls(p) p<<1
#define rs(p) p<<1|1
#define mat(p,a,b) tr[p].mat[a][b]
inline void Assign(const int &p){
    if(s[l(p)]=='0'){
        mat(p,0,1)=mat(p,0,2)=
        mat(p,1,2)=mat(p,2,1)=0;
        mat(p,1,0)=mat(p,2,0)=
        mat(p,0,0)=mat(p,1,1)=mat(p,2,2)=1;
    }
    else if(s[l(p)]=='1'){
        mat(p,0,2)=mat(p,1,0)=
        mat(p,1,2)=mat(p,2,0)=0;
        mat(p,0,0)=mat(p,2,2)=
        mat(p,0,1)=mat(p,1,1)=mat(p,2,1)=1;
    }
    else{
        mat(p,0,2)=mat(p,1,2)=0;
        mat(p,0,0)=mat(p,0,1)=
        mat(p,1,0)=mat(p,1,1)=
        mat(p,2,0)=mat(p,2,1)=mat(p,2,2)=1;
    }
}
inline void Pushup(const int &p){
    for(_i=0;_i<3;++_i){
        for(_j=0;_j<3;++_j){
            mat(p,_i,_j)=mat(ls(p),_i,0)*mat(rs(p),0,_j);
            mat(p,_i,_j)+=mat(ls(p),_i,1)*mat(rs(p),1,_j);
            mat(p,_i,_j)+=mat(ls(p),_i,2)*mat(rs(p),2,_j);
            if(mat(p,_i,_j)>=md) mat(p,_i,_j)%=md;
        }
    }
}
void Build(const int p,const int l,const int r){
    l(p)=l;r(p)=r;
    m(p)=(l+r)>>1;
    if(l(p)==r(p)){
        Assign(p);
        return;
    }
    Build(ls(p),l,m(p));
    Build(rs(p),m(p)+1,r);
    Pushup(p);
}
void Change(const int p,const int &x){
    if(l(p)==r(p)){
        Assign(p);
        return;
    }
    if(x<=m(p)) Change(ls(p),x);
    else Change(rs(p),x);
    Pushup(p);
}
int main(){
    scanf("%d%d%s",&n,&q,s+1);
    Build(1,1,n);
    while(q--){
        scanf("%d",&u);
        scanf(" %c",s+u);
        Change(1,u);
        x=mat(1,2,0)+mat(1,2,1);
        if(x>=md) x-=md;
        printf("%d\n",x);
    } 
    return 0;
}

MinimizOR

题意

有一个长为 \(n\) 的数组 \(a\),同时有 \(q\) 次询问,每次给出一个区间 \([l,r]\),询问 \(\min_{i,j\in[l,r],i\ne j}a_i\cup a_j\)\(n,q\le 10^5,0\le a_i<2^{30}\)

解法

可以猜测 \(a\) 中的 \(\min_{i,j\in a}i\text{ or }j\) 中的 \(i,j\) 均在 \(a\) 中最小的 \(\lfloor\log_2(\max a)\rfloor+1\) 个数中。

证明:设 \(a\) 中的每个数均在 \([0,2^{k-1})\) 中可以选前 \(k\) 小的数可以得出 \(\min_{i,j\in a}i\text{ or }j\),考虑 \(a\) 中每个数均在 \([0,2^k)\) 之间能否通过选择前 \(k+1\) 小的数得出 \(\min_{i,j\in a}i\text{ or }j\)

如果这 \(k+1\) 个数全在 \([0,2^{k-1})\) 中,则显然其中有 \(\min_{i,j\in a}i\text{ or }j\)。若不全在但是至少有两个在 \([0,2^{k-1})\) 中,则选择在 \([0,2^{k-1})\) 中的数一定有结果。若在 \([0,2^{k-1})\) 内的数不足两个,则必有 \(\min_{i,j\in a}i\text{ or }j\ge 2^{k-1}\),也就是说 \(\forall i,j\in a\)\(i\text{ or }j=2^{k-1}+((i\text{ mod }2^{k-1})\text{ or }(j\text{ mod }2^{k-1}))\),所以只需要将 \(a\) 中每个数模 \(2^{k-1}\) 的最小的 \(k\) 个选出。可以发现这些数中第 \(2\) 小到第 \(k+1\) 小的数模 \(2^{k-1}\) 一定是 \(a\) 中第 \(2\) 小到最大的数模 \(2^{k-1}\) 的所有值中前 \(k\) 小的数,此时再考虑第 \(1\) 小的数后,最后 \(k+1\) 个数模 \(2^{k-1}\) 的值一定包含了 \(a\) 中所有数模 \(2^{k-1}\) 的数中前 \(k\) 小的数。

所以可以直接使用线段树维护 \(a\),查询时在区间中取前 \(32\) 小的数,两两按位或即可求得答案。

代码

点此查看代码
#include <bits/stdc++.h>
using namespace std;
const int maxn=100010;
int t,n,i,j,q,lt,rt,ap,tp,ans;
int a[maxn],st[40];
struct seg{
    int l,r,m;
    int mn,mp;
}tr[maxn<<2];
#define l(p) tr[p].l
#define r(p) tr[p].r
#define m(p) tr[p].m
#define ls(p) p<<1
#define rs(p) p<<1|1
#define mn(p) tr[p].mn
#define mp(p) tr[p].mp 
inline void pushup(const int &p){
    if(mn(ls(p))<mn(rs(p))){
        mn(p)=mn(ls(p));
        mp(p)=mp(ls(p));
    }
    else{
        mn(p)=mn(rs(p));
        mp(p)=mp(rs(p));
    }
}
void build(const int p,const int l,const int r){
    l(p)=l;r(p)=r;
    m(p)=(l+r)>>1;
    if(l==r){
        mn(p)=a[l];
        mp(p)=l;
        return;
    }
    build(ls(p),l,m(p));
    build(rs(p),m(p)+1,r);
    pushup(p);
}
void change(const int p,const int &x,const int &v){
    if(l(p)==r(p)){
        mn(p)=v;
        return;
    }
    if(x<=m(p)) change(ls(p),x,v);
    else change(rs(p),x,v);
    pushup(p);
}
void query(const int p){
    if(lt<=l(p)&&rt>=r(p)){
        if(ans>mn(p)){
            ans=mn(p);
            ap=mp(p);
        }
        return;
    }
    if(lt<=m(p)) query(ls(p));
    if(rt>m(p)) query(rs(p));
}
int main(){
    scanf("%d",&t);
    while(t--){
        scanf("%d",&n);
        for(i=1;i<=n;++i) scanf("%d",a+i);
        build(1,1,n);
        scanf("%d",&q);
        while(q--){
            scanf("%d%d",&lt,&rt);
            for(i=32;i;--i){
                ans=1145141919;
                query(1);
                if(ans==1145141919) break;
                change(1,ap,1145141919);
                st[++tp]=ap;
            }
            ans=1145141919;
            for(i=1;i<tp;++i) for(j=i+1;j<=tp;++j) ans=min(ans,a[st[i]]|a[st[j]]);
            printf("%d\n",ans);
            for(i=1;i<=tp;++i) change(1,st[i],a[st[i]]);
            tp=0;
        }
    }
    return 0;
}

Cards

题意

\(n\) 张卡片,第 \(i\) 张卡片写有 \(p_i\)\(q_i\)\(p,q\) 均为 \(\{1,2,\cdots,n\}\) 排列。

现在需要选取一些卡片,使得 \(1,2,\cdots,n\) 均至少在这些卡片之一上。求方案数。\(n\le 2\cdot 10^5\)

解法

听说 GaryH 在这道题上卡了几乎整整一场比赛

考虑把写有 \(i,j\) 的卡片看成是在 \(i\)\(j\) 之间连接的无向边,则每个点的度均为 \(2\)(不考虑形成自环的点),可以把整个情境看成由若干个简单环或自环;选择卡片的方案即为选择若干条边,满足每个点连接的边至少有一条被选。

直接选择边考虑较为困难,考虑先选择所有的边再删去某些边,满足删去的边不能为自环上的边,同时删去的任意两条边不共点。令在一个大小为 \(n\) 的简单有标号环上删边的方案为 \(f_n\)(包括不删)(特别地,令 \(f_1=1\)),则 \(f_2=3\)。手推可以猜想:对于 \(\forall n\ge 3,f_n=f_{n-1}+f_{n-2}\)

证明:令在一条有 \(n\) 条边的链上进行类似的删边的方案有 \(g_n\) 种,则由某条边是否被删则 \(\forall n\ge 3,f_n=g_{n-1}+g_{n-3}\),而 \(g_n=g_{n-1}+g_{n-2}\)(具体证明可以看下图)。特别地,\(g_0=1\)

从而 \(\forall n\ge 5\),有

\[\begin{aligned}f_n&=g_{n-1}+g_{n-3}\\&=g_{n-2}+g_{n-3}+g_{n-4}+g_{n-5}\\&=(g_{n-2}+g_{n-4})+(g_{n-3}+g_{n-5})\\&=f_{n-1}+f_{n-2}\end{aligned} \]

而手推可得 \(f_3=4,f_4=7\),均满足 \(f_n=f_{n-1}+f_{n-2}\)。故而始终有 \(f_n=f_{n-1}+f_{n-2}\) 成立。

代码

点此查看代码
#include <bits/stdc++.h>
using namespace std;
#define ll long long
const int maxn=200010;
const int md=998244353;
int n,i,d,t,c;
ll ans;
bool vis[maxn];
int h[maxn],p[maxn],q[maxn],f[maxn];
struct edge{int to,nxt;}E[maxn<<1];
void dfs(const int p){
    vis[p]=1;++c;
    int lp,to;
    for(lp=h[p];lp;lp=E[lp].nxt){
        to=E[lp].to;
        if(vis[to]) continue;
        dfs(to);
    }
}
int main(){
    scanf("%d",&n);
    f[0]=1;f[1]=2;f[2]=3;f[3]=4;ans=1;
    for(i=4;i<=n;++i){
        f[i]=f[i-1]+f[i-2];
        if(f[i]>=md) f[i]-=md;
    }
    f[1]=1;
    for(i=1;i<=n;++i){
        scanf("%d",&d);
        p[d]=i;
    }
    for(i=1;i<=n;++i){
        scanf("%d",&d);
        q[d]=i;
    }
    for(i=1;i<=n;++i){
        E[++t]={p[i],h[q[i]]};h[q[i]]=t;
        E[++t]={q[i],h[p[i]]};h[p[i]]=t;
    }
    for(i=1;i<=n;++i){
        if(vis[i]) continue;
        c=0;dfs(i);
        ans*=f[c];
        if(ans>=md) ans%=md;
    }
    printf("%lld\n",ans);
    return 0;
}

Narrow Components

题意

有一个 \(3\times n\)\(01\) 矩阵。\(q\) 次询问,每次询问一个区间 \([l,r]\),求第 \(l\) 列到第 \(r\) 列有多少个只有 \(1\) 的四连通块。\(n\le 5\cdot 10^5,q\le 3\cdot 10^5\)

解法

考虑将两个矩阵拼在一起会减少多少连通块。

显然如果连接部分存在两边均为 \(1\) 的一行,则连通块个数应减少 \(1\)。如果连接处的第一行和第三行的 \(1\) 均存在,连通块个数应减少 \(2\)但是如果某一边的第一行和第三行的 \(\bold 1\) 本来就在一个连通块里,则连通块个数只需要减少 \(\bold 1\)

线段树维护上述信息即可。具体维护某个区间两边的 \(1\) 是否在一个连通块内可见代码。

p.s. 听说这个题有最多可以处理 \(10\) 行的使用线段树套并查集的解法

代码

点此查看代码
#include <bits/stdc++.h>
using namespace std;
const int maxn=500010;
int n,i,q,lp,rp,msk[maxn];
int l[maxn<<2],r[maxn<<2],m[maxn<<2];
struct seg{
    int cnt,lm,rm;
    bool cl,cr,c2;
    inline seg operator +(const seg &a){
        seg tmp;
        tmp.lm=lm;tmp.rm=a.rm;
        tmp.cnt=cnt+a.cnt;
        tmp.c2=c2&&a.c2;
        tmp.cl=cl||(c2&&a.cl);
        tmp.cr=a.cr||(a.c2&&cr);
        if(rm&a.lm){
            --tmp.cnt;
            if((((rm==5)&&(!cr))&&(((a.lm==5)&&(!a.cl))||a.cl))||
               (cr&&((a.lm==5)&&(!a.cl)))) --tmp.cnt;
        }
        return tmp;
    }
}tr[maxn<<2],ans;
#define ls(p) p<<1
#define rs(p) p<<1|1
void build(const int p,const int &lt,const int &rt){
    l[p]=lt;r[p]=rt;
    m[p]=(lt+rt)>>1;
    if(lt==rt){
        tr[p].lm=tr[p].rm=msk[lt];
        if(!msk[lt]) tr[p].cnt=0;
        else if(msk[lt]==5){
            tr[p].cnt=2;
            tr[p].c2=1;
        }
        else{
            tr[p].cnt=1;
            if(msk[lt]==7) tr[p].cl=tr[p].cr=1;
        }
        return;
    }
    build(ls(p),lt,m[p]);
    build(rs(p),m[p]+1,rt);
    tr[p]=tr[ls(p)]+tr[rs(p)];
}
void query(const int p,const int &lt,const int &rt){
    if(lt<=l[p]&&rt>=r[p]){
        if(!ans.cnt) ans=tr[p];
        else ans=ans+tr[p];
        return;
    }
    if(lt<=m[p]) query(ls(p),lt,rt);
    if(rt>m[p]) query(rs(p),lt,rt);
}
int main(){
    scanf("%d",&n);
    getchar();
    for(i=1;i<=n;++i){
        q=getchar()^'0';
        if(q) msk[i]|=1;
    }
    getchar();
    for(i=1;i<=n;++i){
        q=getchar()^'0';
        if(q) msk[i]|=2;
    }
    getchar();
    for(i=1;i<=n;++i){
        q=getchar()^'0';
        if(q) msk[i]|=4;
    }
    build(1,1,n);
    scanf("%d",&q);
    while(q--){
        scanf("%d%d",&lp,&rp);
        ans={0,0,0,0,0,0};
        query(1,lp,rp);
        printf("%d\n",ans.cnt);
    }
}

Teleporters

题意

有一个有 \(n\) 个正整数的集合,第 \(i\) 个数为 \(a_i-a_{i-1}\)\(a_0=0\))。现在可以进行若干次操作,每次操作可以取出一个数 \(x\),然后把 \(y\)\(x-y\) 两个正整数放回集合。求使得集合内所有数的平方和不大于 \(M\) 的最小操作次数。\(n\le 2\cdot 10^5,a_i\le 10^9,a_n\le M\le 10^{18}\)

解法

考虑将某个数 \(s\) 拆分 \(k\) 次对答案的最小贡献。

显然若其中拆出的数有 \(p\)\(p+2+k(k\ge 0)\),则有

\[\begin{aligned}p^2+(p+2+k)^2&=p^2+(p+k)^2+4(p+k)+4\\&=(p^2+2p+1)+((p+k)^2+2(p+k)+1)+2(k+1)\\&=(p+1)^2+(p+1+k)^2+2(k+1)\\&>(p+1)^2+(p+1+k)^2\end{aligned} \]

故而最优方案中,最后拆出来的数一定不会有相差大于 \(2\) 的数,也就是拆出的数一定是 \(s\bmod k\)\(\lfloor\frac sk\rfloor+1\)\(k-(s\bmod k)\)\(\lfloor\frac sk\rfloor\),贡献即为

\[\begin{aligned}&{\color{white}{=\ \ }}(s\bmod k)(\lfloor\frac sk\rfloor+1)^2+(k-(s\bmod k))\lfloor\frac sk\rfloor^2\\&=(s\bmod k)(\lfloor\frac sk\rfloor^2+2\lfloor\frac sk\rfloor+1)+(k-(s\bmod k))\lfloor\frac sk\rfloor^2\\&=k\lfloor\frac sk\rfloor^2+(s-k\lfloor\frac sk\rfloor)(2\lfloor\frac sk\rfloor+1)\\&=k\lfloor\frac sk\rfloor^2-2k\lfloor\frac sk\rfloor^2-k\lfloor\frac sk\rfloor+2s\lfloor\frac sk\rfloor+s\\&=-k\lfloor\frac sk\rfloor^2+(2s-k)\lfloor\frac sk\rfloor+s\end{aligned} \]

\(\lfloor\frac sk\rfloor=p,f_s(k)=-k\lfloor\frac sk\rfloor^2+(2s-k)\lfloor\frac sk\rfloor+s\),则在对应一段的斜率为 \(-p^2-p\),而之后的满足 \(\lfloor\frac sk\rfloor=p-1\) 的一段的斜率为 \(-(p-1)^2-(p-1)=-p^2+2p-1-p+1=-p^2+p\) 大于 \(-p^2-p\)。故而对于固定的 \(s\),关于 \(k\) 的函数 \(f_s(k)\) 是一段下凸函数。可以使用堆维护每个 \(f_s(k)\) 目前的斜率,每次在 \(f_s(k)\) 对应的斜率最小的 \(s\) 贪心进行操作即可。

注意:\(f_s(k)\)\(k\in N_+\),不能单纯地按上述公式计算斜率,而需要在斜率交界处重新计算 \(f_s(k+1)-f_s(k)\)

代码

注意下面 Calc 函数的效率。

点此查看代码
#include <bits/stdc++.h>
using namespace std;
const int maxn=200010;
const int INF=2147483647;
#define ll long long
int n,i,c,l,r,d,cnt;
int a[maxn];
ll m,v,ans;
inline ll Calc(const int &x,const int &y){
    const ll p=x/(y+1);
    return p*p*(y+1)+(x-(y+1)*p)*(2*p+1); 
}
inline int got(const int &x,const int &y){
    if(x<=y) return INF;
    return x/(x/(y+1));
}
struct node{
    int len,use;ll val,vax;
    inline bool operator <(const node &a)const{
        return vax<a.vax;
    }
}tmp;
priority_queue<node> q;
int main(){
    scanf("%d",&n);
    for(i=1;i<=n;++i) scanf("%d",a+i);
    scanf("%lld",&m);
    for(i=n;i;--i){
        a[i]-=a[i-1];ans+=1LL*a[i]*a[i];
        q.push((node){a[i],0,1LL*a[i]*a[i],1LL*a[i]*a[i]-Calc(a[i],1)});
    }
    while(ans>m){
        tmp=q.top();q.pop();
        c=got(tmp.len,tmp.use);
        if(c!=tmp.use+1) --c;
        v=Calc(tmp.len,c);
        if(ans-tmp.val+v>=m){
            cnt+=c-tmp.use;
            tmp.use=c;
            ans-=tmp.val-v;
            tmp.val=v;
            tmp.vax=v-Calc(tmp.len,c+1);
            q.push(tmp);
        }
        else{
            l=tmp.use;r=c;
            while(l<r){
                d=(l+r)>>1;
                if(ans-tmp.val+Calc(tmp.len,d)<=m) r=d;
                else l=d+1;
            }
            cnt+=l-tmp.use;
            break;
        }
    }
    printf("%d",cnt);
    return 0;
}

Rotate and Play Game

题意

有一个长为 \(n\) 的序列 \(a\)。两个人轮流进行若干次操作:先手可以取走 \(a\) 中任意元素,然后后手取走 \(a\) 中下标最小的元素,直到把序列取空。操作前可以将 \(a\)\(\{a_1,a_2,\cdots,a_n\}\) 变为 \(\{a_{k+1},a_{k+2},\cdots,a_n,a_1,a_2,\cdots,a_k\}\),求先手能取到的数的和的最大值和对应的任意一个合法 \(k\)\(n\le 2\cdot 10^5,a_i\le 10^9\)\(n\) 为偶数。

解法

猜想一定存在一种方案使得先手能够取到前 \(\frac n2\) 大的数。

证明:考虑把前 \(\frac n2\) 大的数看成是 \(-1\),其他数看成是 \(1\),则序列的前缀和均不小于 \(0\) 时先手能取遍前 \(\frac n2\) 大的数。此时先手可以取第一个 \(-1\)(令其所在的位置为第 \(p\) 个),此时肯定仍有序列的前缀和均不小于 \(0\),也就是当前序列的第一个数为 \(1\)。而后手取走这个 \(1\) 后,第 \(p-1\) 个数及以后的所有数均未发生改变;这之前的数(如果存在)肯定均为 \(1\),且它们的和同样不变。

至于如何构造这样的序列,可以求出序列中最小的前缀和对应的前缀(令其为 \(1\sim k\) 且这个最小前缀和为 \(S\)),则 \(k\) 即为所求答案,把序列 \(a=\{a_1,a_2,\cdots,a_n\}\) 变成 \(a'=\{a_{k+1},a_{k+2},\cdots,a_n,a_1,a_2,\cdots,a_k\}\) 即满足上述性质。由于 \(\forall j\in[k+1,n],\sum_{i=k+1}^ja_i=\sum_{i=1}^ja_i-\sum_{i=1}^ka_i\ge 0\),且 \(\sum_{i=k+1}^na_i=-\sum_{i=1}^ka_i=-S\),则 \(\forall b\in[1,k],\sum_{i=k+1}^na_i+\sum_{i=1}^ba_i\ge -S+S=0\)。故而这样构造有其前缀和均不小于 \(0\)

代码

点此查看代码
#include <bits/stdc++.h>
using namespace std;
const int maxn=200010;
int n,i,d,x;
int a[maxn],p[maxn];
long long s;
inline bool cmp(const int &y,const int &z){return a[y]<a[z];}
int main(){
    scanf("%d",&n);
    for(i=1;i<=n;++i){
        scanf("%d",a+i);
        p[i]=i;
    }
    x=n;
    sort(p+1,p+n+1,cmp);
    n>>=1;
    for(i=1;i<=n;++i) a[p[i]]=1;
    n<<=1;
    while(i<=n){
        s+=a[p[i]];
        a[p[i]]=-1;
        ++i;
    }
    for(i=1;i<n;++i){
        a[i]+=a[i-1];
        if(a[i]<a[x]) x=i;
    }
    if(x==n) x=0;
    printf("%d %lld",x,s);
    return 0;
}

P7949 左方之地 & ARC138D Differ by K Bits

题意

要求构造一个 \(0\sim 2^n-1\) 的排列 \(P\),满足相邻两个数的异或值在二进制的表示下刚好有且只有 \(k\)\(1\)。求任意一种构造方案,或判断其为无解。ARC138D:需要最后一个数和第一个数的异或值满足同样的性质。

P7949:\(n,k\le 20\)。ARC138D:\(k\le n\le 18\)

解法

首先 \(k\) 为偶数时(考虑奇偶性)或不小于 \(n\) 时显然无解。

考虑格雷码(\(k=1\) 的情况)。长为 \(n\) 的格雷码 \(S\)\(O(2^n)\) 的构造方式:第 \(i\) 项为 \(i\oplus\lfloor\frac i2\rfloor\)

证明:

如果 \(i\) 为偶数,则 \((i\oplus (i+1))_2\) 只有一个 \(1\),而 \(\lfloor\frac i2\rfloor=\lfloor\frac {i+1}2\rfloor\),故而有 \(S_i\oplus S_{i+1}=1\)

如果 \(i\) 为奇数,且 \((i+1)_2\) 末尾有 \(c\)\(0\)(显然第 \(c+1\) 位为 \(1\)),令 \(k=\lfloor\frac i{2^{c+1}}\rfloor\),则 \(i+1=(k\cdot 2^{c+1})\oplus 2^c\)\(\lfloor\frac {i+1}{2}\rfloor=(k\cdot 2^c)\oplus 2^{c-1}\)\(i=k\cdot2^{c+1}\oplus(\bigoplus_{j=0}^{c-1}2^j)\)\(\lfloor\frac i2\rfloor=k\cdot 2^c\oplus(\bigoplus_{j=0}^{c-2} 2^j)\)。则 \(S_{i+1}=(k\cdot 2^{c+1})\oplus 2^c\oplus(k\cdot 2^c)\oplus 2^{c-1}\)\(S_i=(k\cdot 2^{c+1})\oplus(\bigoplus_{j=0}^{c-1}2^j)\oplus(k\cdot 2^c)\oplus(\bigoplus_{j=0}^{c-2}2^j)=(k\cdot 2^{c+1})\oplus(k\cdot 2^c)\oplus 2^{c-1}\);从而 \(S_i\oplus S_{i+1}=2^c\)

同时若 \(\exists i,j\),且 \(S_i=S_j\),则 \(i\oplus \lfloor\frac i2\rfloor=j\oplus \lfloor\frac j2\rfloor\),故而 \(i\oplus j=\lfloor\frac i2\rfloor\oplus \lfloor\frac j2\rfloor\)。同时 \(i\oplus j=2(\lfloor\frac i2\rfloor\oplus\lfloor\frac j2\rfloor)+((i\text{ mod }2)\oplus(j\text{ mod }2))\),故而 \((\lfloor\frac i2\rfloor\oplus\lfloor\frac j2\rfloor)+((i\text{ mod }2)\oplus(j\text{ mod }2))=0\),也就是 \(\lfloor\frac i2\rfloor=\lfloor\frac j2\rfloor\)\((i\text{ mod }2)=(j\text{ mod }2)\)。从而这样只有 \(i=j\) 才有 \(S_i=S_j\)

综上,\(S\) 满足 \(\forall i\ne j,S_i\ne S_j\)\((S_i\oplus S_{i+1})_2\) 只有一个 \(1\)

所以我们有了对于任意的 \(n\) 构造 \(k=1\) 的解法。同时考虑构造 \(k=n-1\) 的方案。发现如果把奇数位的 \(S\) 值的所有位取反,则对于取反的 \(S_i\)\((S_{i-1}\oplus S_i)_2\)\((S_{i+1}\oplus S_i)_2\) 均有 \(n-1\)\(1\)。同时 \((S_{2^n-1}\oplus S_0)_2\) 有同样的性质。

考虑从 \(n=k+i\) 的合法解推到 \(n=k+i+1\) 的合法解。可以构造 \(S_{2^{k+i}-1+j}=S_{2^{k+i}-j}\oplus(2^{k-1}-1)\oplus2^{k+i}\),此时 \((S_{2^{k+i}}\oplus S_{2^{k+i}-1})_2\)\(k\)\(1\),而 \(S_{2^{k+i}+p-1}\oplus S_{2^{k+i}+p}=(S_{2^{k+i}-p}\oplus ((2^{k-1}-1)\oplus 2^{k+i}))\oplus (S_{2^{k+i}-p-1}\oplus((2^{k-1}-1)\oplus2^{k+i})=S_{2^{k+i}-p}\oplus S_{2^{k+i}-p-1}\),同样在二进制表达下有 \(k\)\(1\)。新的 \(S_{2^n-1}\oplus S_0\) 同理。同时若 \(\exists a,b\ge 2^{k+i}\),则 \(S_a=S_b\)\(S_{2^{k+i+1}-1-a}\oplus((2^{k-1}-1)\oplus 2^{k+i})=S_{2^{k+i+1}-1-b}\oplus((2^{k-1}-1)\oplus 2^{k+i})\),也就是需要 \(S_{2^{k+i+1}-1-a}=S_{2^{k+i+1}-1-b}\),而此条件满足当且仅当 \(a=b\)。综上,这样的构造方法可以得到 \(n=k+i+1\) 的合法解。

综上,可以先求 \(n=k+1\) 的合法解,然后逐级递推,即可求得答案。

代码

点此查看代码
//P7949
#include <bits/stdc++.h>
using namespace std;
int n,k,s,i,j,l,q;
int b[1048600];
int main(){
    scanf("%d%d",&n,&k);
    q=k;
    if(n==1&&k==1){
        printf("1\n0 1");
        return 0;
    }
    if(k>=n||(!(k&1))){
        printf("0\n");
        return 0;
    }
    printf("1\n");
    s=(1<<(k+1))-1;
    for(i=0;i<=s;++i){
        b[i]=i^(i>>1);
        if(i&1) b[i]^=s;
    }
    l=s;s=(1<<(k-1))-1;++k;
    while(k<n){
        j=l+1;
        for(i=l;i>=0;--i) b[j++]=(b[i]+(1<<k))^s;
        l=l<<1|1;++k;
    }
    for(i=0;i<=l;++i) printf("%d ",b[i]);
    return 0;
}

下述内容待补

Decreasing Subsequence

题意

求最长下降子序列长度不小于 \(k\) 的长为 \(n\) 的序列 \(a\) 的个数,满足 \(\forall i\in[1,n],a_i\in [0,i]\)\(\not\!\exists i,j\in[1,n],i\ne j\cap a_i=a_j\ne 0\)\(n\le 5000\)

解法

代码

点此查看代码

Beautiful Subsequences

题意

给出一个 \(1\sim n\) 的排列 \(P\),求 \(P\) 中有多少个区间 \([l,r]\) 满足 \(\max_{j=l}^rP_j-\min_{j=l}^rP_j\le r-l+k\)\(n\le 1.4\cdot 10^5,0\le k\le 3\)

解法

代码

点此查看代码

Edge Elimination

题意

给出一棵大小为 \(n\) 的无根树,可以进行若干次操作,每次操作删除一条与偶数条边相邻的边。求一种删除所有边的合法次序,或判断其为无解。\(\sum n\le 2\cdot 10^5\)

解法

代码

点此查看代码

AND-MEX Walk

题意

给定一个 \(n\) 个节点 \(m\) 条边的无向简单连通图,边有边权。我们定义一条路径(即可以重复经过同一个节点或同一条边的路径)的权值如下:设该途径按顺序经过的边的权值为 \(w_1,w_2,w_3,\cdots\),则该途径的权值为 \(\text{mex}_{i=1}\{\&_{j=1}^iw_j\}\)。给定 \(q\) 次询问,每次给定两个不同的整数 \(u,v\),求所有从节点 \(u\) 开始到节点 \(v\) 结束的路径中,路径权值的最小值。\(2\leq n\leq10^5,0\leq w<2^{30},1\leq q\leq 10^5\)

解法

代码

点此查看代码

Checker for Array Shuffling

题意

给定一个长为 \(n\) 的数组 \(a\)。若 \(a\) 的一个排列 \(b\) 可以通过 \(k\) 次将任意两个数交换位置得到 \(a\),则 \(b\) 的价值为所有合法 \(k\) 的最小值。现在给出 \(a\) 的某个排列 \(b\),判断它的价值是否为所有长为 \(n\) 的数组的最大值。若是则输出 AC ,否则输出 WA\(t\) 组数据。\(\sum n\le 2\cdot 10^5,1\le a_i,b_i \le n\).

解法

代码

点此查看代码

Cross Xor

题意

有一个 \(r\times c\) 的全 \(0\) 矩阵 \(a\),和一个 \(r\times c\)\(01?\) 矩阵 \(b\)。可以进行若干次操作,每次操作可以指定 \(i\)\(j\),然后将 \(\forall a_{x,y}(x=i\or y=j)\) 异或上 \(1\)。求 \(a\) 最终能变成的矩阵 \(c\) 中,有多少个满足 \(\forall b_{x,y}\ne\;?,c_{x,y}=b_{x,y}\)\(r,c\le 2000\)

解法

代码

点此查看代码

Zigu Zagu

题意

你有一个长度为 \(n\)\(01\)\(a\)。现在给出 \(q\) 次询问,每次询问给出两个数 \(l,r\),保证 \(1 \leq l \leq r \leq n\)。令 \(s=a_l\sim a_r\) 你可以对 \(s\) 做如下操作:

  1. 选择两个数 \(x,y\),满足 \(1 \leq x \leq y \leq |s|\)。令子串 \(t=s_x\sim s_y\),对于所有的 \(1 \leq i \leq |t|-1\),需要满足 \(t_i \neq t_{i+1}\)

  2. 删除 \(s[x,y]\)

对于每一个询问,请求出最少需要多少个操作才能把 \(s\) 变成一个空串。\(n,q\le 2\cdot 10^5\)

解法

代码

点此查看代码

Xor Cards

题意

\(n\) 张卡片,第 \(i\) 张卡片上写有 \(a_i,b_i\)。现在要选出若干卡片(记选了 \(\{c_1,\cdots,c_m\}(m\ge 1)\))且保证 \(\bigoplus_{j=1}^m a_{c_j}\le K\) 时,求 \(\max\bigoplus_{j=1}^m b_{c_j}\),或判断不存在选出卡片的合法方案。\(n\le 1000,a_i,b_i\le 2^{30}\)

解法

代码

点此查看代码

Antennas

题意

\(1\sim n\) 的数轴上有 \(n\) 座信号塔,每个整点上有一座。第 \(i\) 座信号塔和第 \(j\) 座信号塔可以传递信息当且仅当 \(|i-j|\le \min(p_i,p_j)\)。求从 \(a\)\(b\) 传递信息经过的最少信号塔数(不包括 \(a\))。\(n\le 2\cdot 10^5\)

解法

代码

点此查看代码

posted @ 2022-10-04 15:22  Fran-Cen  阅读(21)  评论(0编辑  收藏  举报