【考试总结】2022-03-21

Board Game

k 个非法状态在棋盘上所有可能的出现位置表示成 nm 位的二进制数,做高维前缀和来标记非法

之后是一个平凡的 SG 函数问题,不另赘述,但是复杂度过高

注意到 vis,dp 数组中的元素的值域是 {0,1} 所以考虑将相邻的 64 个数压成一个 64unsigned long long,这样子在高维前缀和的时候做一次 OR 操作就可以实现 64 个位的转移

此时仍然需要处理一个数字内部的转移,这里子母集关系只会出现在 063 之间,预处理 64unsigned long long subi 表示 063 中哪些是 i 的子集

这样子每次找到 subj&visi 就可以得到数字内部的转移了

而后面 DP 的部分也是类似的,预处理每个 063 添加一个 1 能走到内部的哪个元素,转移判定为 1 的元素个数是不是等于集合大小即可

Code Display
int n,m,lim,k;
ull vis[1<<21],dp[1<<21];
char s[30][30];
inline void Vis_set(int pos) { vis[pos >> 6] |= 1ull << (pos & 63); }
inline ull Vis(int pos) { return vis[pos >> 6] >> (pos & 63) & 1; }
namespace Sub1{
    bool vis[1<<22],dp[1<<22];
    inline int dfs(int x){
        if(vis[x]) return dp[x];
        vis[x]=1;
        int U=(lim-1)^x;
        while(U){
            int lb=U&(-U);
            if(!dfs(x^lb)) return dp[x]=1;
            U-=lb;  
        } return dp[x];
    }
    inline void main(){
        while(k--){
            int h=read(),w=read();
            rep(i,1,h) scanf("%s",s[i]+1);
            for(int dx=0;dx<=n-h;++dx){
                for(int dy=0;dy<=m-w;++dy){
                    int st=0;
                    for(int i=1;i<=h;++i){
                        for(int j=1;j<=w;++j){
                            if(s[i][j]=='1') st|=1<<((i+dx-1)*m+j+dy-1);
                        }
                    }
                    vis[st]=1;
                }
            }
        }
        lim=1<<(n*m); 
        for(int p=2;p<=lim;p<<=1){
            int len=p>>1;
            for(int k=0;k<lim;k+=p) for(int l=k;l<k+len;++l) vis[l+len]|=vis[l];
        }
        puts(dfs(0)?"Alice":"Bob");
        exit(0);
    }
}
signed main(){
    freopen("game.in","r",stdin); freopen("game.out","w",stdout);
    n=read(); m=read(); k=read();
    if(n*m<=22) Sub1::main();
    while(k--){
        int h=read(),w=read();
        rep(i,1,h) scanf("%s",s[i]+1);
        for(int dx=0;dx<=n-h;++dx){
            for(int dy=0;dy<=m-w;++dy){
                int st=0;
                for(int i=1;i<=h;++i){
                    for(int j=1;j<=w;++j){
                        if(s[i][j]=='1') st|=1<<((i+dx-1)*m+j+dy-1);
                    }
                }
                Vis_set(st);
            }
        }
    }
    vector<ull> Sub(64);
    for(int i=0;i<64;++i){
        Sub[i]|=1;
        for(int j=i;j;j=(j-1)&i) Sub[i]|=1ull<<j;
    }
    lim=1<<(n*m-6);
    for(int i=0;i<lim;++i){
        for(int j=0;j<64;++j) if(vis[i]&Sub[j]) vis[i]|=1ull<<j;
    }
    for(int p=2;p<=lim;p<<=1){
        int len=p>>1;
        for(int k=0;k<lim;k+=p) for(int l=k;l<k+len;++l) vis[l+len]|=vis[l];
    }
    for(int i=0;i<64;++i){
        Sub[i]=0;
        rep(j,0,5) if(i>>j&1) Sub[i^(1<<j)]|=1ull<<i;
    }
    vector<int> bit(64);
    rep(i,0,63) bit[i]=__builtin_popcountll(Sub[i]);
    for(int i=lim-1;i>=0;--i){
        for(int j=0;j<n*m-6;++j) if(!(i>>j&1)) dp[i]|=~dp[i^(1<<j)];
        for(int j=63;j>=0;--j) if(!(vis[i]>>j&1)){
            if(dp[i]>>j&1) continue;
            if(__builtin_popcountll(dp[i]&Sub[j])!=bit[j]) dp[i]|=1ull<<j;
        }else if(dp[i]>>j&1) dp[i]^=(1ull<<j);
    }
    puts((dp[0]&1)?"Alice":"Bob");
    return 0;
}


Difference Query

首先注意给定的序列是排列

考察 f(x,x) 做若干步逆操作得到的形式可以发现 p,xgcd(x,y)+ygcd(x,y)=2pf(x,y)=p,否则 f(x,y)=0

扫描线,对于每个 i 枚举 ai 的因子作为 gcd,再枚举 2p 作为答案,找到所有带来贡献的数字在线段树上执行区间加法即可

回答完询问之后再把每个 ai 放到其因子的标号为 aid 的桶里面即可,空间复杂度是 Θ(nlnn) 的,加一些剪枝可以获得小常数

Code Display
const int N=1e5+10;
int ans[N],n,a[N],Q,Mx[N];
vector<int> Div[N];
vector<vector<pair<int,int> > > app[N];
struct Seg{
    #define ls p<<1
    #define rs p<<1|1
    #define lson p<<1,l,mid
    #define rson p<<1|1,mid+1,r
    int fir[N<<2],len[N<<2],delt[N<<2];
    inline void build(int p,int l,int r){
        len[p]=r-l+1; if(l==r) return ;
        int mid=(l+r)>>1; build(lson); build(rson);
        return ;
    }
    inline void upd(int st,int ed,int fr,int d,int p=1,int l=1,int r=n){
        if(st<=l&&r<=ed) return fir[p]+=(l-st)*d+fr,delt[p]+=d,void();
        int mid=(l+r)>>1;
        if(st<=mid) upd(st,ed,fr,d,lson); if(ed>mid) upd(st,ed,fr,d,rson);
        return ;
    }
    inline int Query(int pos,int p=1,int l=1,int r=n){
        int res=fir[p]+delt[p]*(pos-l); if(l==r) return res;
        int mid=(l+r)>>1;
        if(pos<=mid) return res+Query(pos,lson);
        else return res+Query(pos,rson);
    }
    #undef ls
    #undef rs
    #undef lson
    #undef rson
}T;
vector<pair<int,int> >qu[N];
signed main(){
    freopen("func.in","r",stdin); freopen("func.out","w",stdout);
    n=1e5;
    rep(i,1,n){
        for(int j=i;j<=n;j+=i) Div[j].emplace_back(i);
        app[i].resize(n/i+2);
    }
    n=read();
    rep(i,1,n) a[i]=read();
    Q=read();
    for(int i=1;i<=Q;++i){
        int l=read(),r=read();
        qu[r].emplace_back(l,i);
    }
    for(int i=1;i<=n;++i){
        for(auto d:Div[a[i]]) if((a[i]/d)&1){
            int quo=a[i]/d,p=1;
            while((1<<p)<quo) ++p;
            while(Mx[d]>=(1<<p)-quo){
                for(auto &rng:app[d][(1<<p)-quo]){
                    int len=rng.sec-rng.fir+1;
                    T.upd(rng.fir,rng.sec,len*p,-p);
                    if(rng.fir!=1) T.upd(1,rng.fir-1,len*p,0);
                }
                ++p;
            }
        }
        for(auto q:qu[i]) ans[q.sec]=T.Query(q.fir);
        for(auto d:Div[a[i]]) if((a[i]/d)&1){
            int quo=a[i]/d;
            if(!app[d][quo].size()||app[d][quo].back().sec<i-1) app[d][quo].emplace_back(i,i);
            else app[d][quo].back().sec=i;
            ckmax(Mx[d],quo);
        }
    }
    rep(i,1,Q) print(ans[i]);
    return 0;
}

Minimal DFA

一个识别 Σ{0,1},LΣn,LDFA 应当有如下几个性质:

  • 可以将点分成 n+1 层,否则能识别的长度不唯一

下设 ki 表示 DFA 的第 i 层点的数量

  • kiki1×|Σ|

  • ki+1(ki+1)2

    这个是因为第 i 层的每个点有 (ki+1+1)2 种后继连接方式,+1 是不选择任何连边,但是不能两个都不连,也不能出现两个相同,所以得到解释

第二条从层数小向层数大限制,第三条从层数大到小限制,那么一个高精度类即可解决

找到第一个 ki+12kii 来进行后面两问的计算:

(下简记从某个第 i+1 层的点走到终点的路径上 |ΣE| 的乘积为其走法)

如果 ki+1ki 那么可以证明 ki+1=2ki1,将 i+1 层每个点连接出边之后 i 层的点只剩下 1 的度,最大就是再连 2ni 的走法的点,最小就是不连

否则考虑先给所有第 i+1 层的点连接出边,根据最大化/最小化需求将余下的出边分配终点,以最大化为例

开始考虑每个 i+1 层的点分配出边是涉及的 i 层点的另一个出边都连向 2ni 走法的点

从大到小枚举剩下点连出去的两条边的走法之和 v,这时候使用组合数即可得到方案 (2ni+1v),注意要减去已经走过的方案数 [v2ni](2niv2ni)

最小化同理,一开始强制连出的点不选第二个终点,后面枚举边权从小往大即可

Code Display
const int Bas=1e8;
struct node{
    vector<int> a;
    inline int size(){return a.size();}
    inline void adjust(){
        int siz=a.size();
        for(int i=0;i<siz-1;++i) a[i+1]+=a[i]/Bas,a[i]%=Bas;
        while(a[siz-1]>=Bas){
            a.emplace_back(a[siz-1]/Bas);
            a[siz-1]%=Bas;
            ++siz;
        }
        while(siz>1&&!a[siz-1]) a.pop_back(),--siz;
        return ;
    }
    inline void init(int x){
        a.clear();
        while(x) a.push_back(x%Bas),x/=Bas;
        return ;
    }
    //Attention: const node b is not available here!
    //we Use function size which may change itself though it actually don't
    bool operator <(node b)const{
        if(a.size()!=b.size()) return a.size()<b.size();
        int len=a.size();
        for(int i=len-1;~i;--i) if(a[i]^b.a[i]) return a[i]<b.a[i];
        return 0;
    }
    bool operator ==(node b)const{
        if(b.size()!=a.size()) return 0;
        int len=a.size();
        for(int i=0;i<len;++i) if(a[i]!=b.a[i]) return 0;
        return 1;
    }
    bool operator <=(node &b)const{return *this<b||*this==b;}
    node operator +(node b)const{
        node res;
        int s1=a.size(),s2=b.a.size(); res.a.resize(max(s1,s2));
        for(int i=0;i<s1;++i) res.a[i]+=a[i];
        for(int i=0;i<s2;++i) res.a[i]+=b.a[i];
        res.adjust();
        return res;
    }
    node operator -(node b)const{
        node res=*this;
        int s1=a.size(),s2=b.size();
        for(int i=0;i<s2;++i){
            res.a[i]-=b.a[i];
            if(res.a[i]<0) res.a[i]+=Bas,res.a[i+1]--;
        }
        for(int i=s2;i<s1;++i) if(res.a[i]<0) res.a[i+1]--,res.a[i]+=Bas;
        res.adjust();
        return res;
    }
    node operator *(node b)const{
        node res; res.a.resize(a.size()+b.size()-1);
        for(int i=0;i<a.size();++i) for(int j=0;j<b.size();++j) res.a[i+j]+=a[i]*b.a[j];
        res.adjust();
        return res;
    }
    bool operator !=(node &b){return !(*this==b);}
    node operator *(const int &p)const{node t; t.init(p); return *this*t;}
    node operator +(const int &p)const{node t; t.init(p); return *this+t;}
    node operator -(const int &p)const{node t; t.init(p); return *this-t;}
    inline void output(){
        int siz=a.size(); printf("%lld",a[siz-1]);
        for(int i=siz-2;~i;--i) printf("%08lld",a[i]);
        putchar(' ');
        return ;
    }
};
const int N=1010;
node A[N],pw[1030],C[1030][1030],ans;
int n,Div;
inline void solve_min(int Div){
    node sum; 
    node nds=A[Div]-A[Div+1]; Div=n-Div;
    sum=pw[1<<Div]*(1<<(Div-1));
    for(int v=1;v<=(1<<(Div+1));++v){
        node cur=C[1<<(Div+1)][v];
        if(v<=(1<<Div)){
            cur=cur-C[1<<Div][v];
        }
        if(nds<cur) cur=nds;
        sum=sum+(cur*v);
        nds=nds-cur;
        if(nds.size()==1&&nds.a[0]==0) break;
    }
    sum.output();
}
inline void solve_max(int Div){
    node sum; 
    node nds=A[Div]-A[Div+1]; Div=n-Div;
    sum=pw[1<<Div]*(1<<(Div-1))+((pw[1<<Div]-1)*(1<<Div));
    for(int v=(1<<(Div+1));v>=1;--v){
        node cur=C[1<<(Div+1)][v];
        if(v>=(1<<Div)){
            cur=cur-C[1<<Div][v-(1<<Div)];
        }
        if(nds<cur) cur=nds;
        sum=sum+(cur*v);
        nds=nds-cur;
        if(nds.size()==1&&nds.a[0]==0) break;
    }
    sum.output();
}
signed main(){
    freopen("dfa.in","r",stdin); freopen("dfa.out","w",stdout);
    n=1024;
    C[0][0].init(1); pw[0].init(1);
    for(int i=1;i<=n;++i){
        C[i][0].init(1); pw[i]=pw[i-1]*2;
        for(int j=1;j<=i;++j){
            C[i][j]=C[i-1][j]+C[i-1][j-1];
        }
    }
    n=read();
    if(n==1) puts("2 1 2"),exit(0);
    if(n==2) puts("4 2 3"),exit(0);
    A[1].init(1); A[n+1].init(1);
    for(int i=2;i<=n;++i) A[i]=A[i-1]*2;
    for(int i=n;i>=1;--i){
        node tmp=A[i+1]+1; tmp=tmp*tmp-1;
        if(A[i]<=tmp) break;
        A[i]=tmp;
    }
    for(int i=1;i<=n+1;++i) ans=ans+A[i];
    ans.output();
    for(int i=1;i<=n;++i) if(A[i]*2!=A[i+1]){Div=i; break;}
    if(A[Div]<A[Div+1]){
        (A[Div]*(1<<(n-Div))).output();
        ((A[Div]+1)*(1<<(n-Div))).output();
    }else{
        solve_min(Div);
        solve_max(Div);       
    }
    return 0;
}

posted @   没学完四大礼包不改名  阅读(97)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· 震惊!C++程序真的从main开始吗?99%的程序员都答错了
· 【硬核科普】Trae如何「偷看」你的代码?零基础破解AI编程运行原理
· 单元测试从入门到精通
· 上周热点回顾(3.3-3.9)
· winform 绘制太阳,地球,月球 运作规律
点击右上角即可分享
微信分享提示