【考试总结】2022-03-07

分裂

我使用一种调整法通过了本题,而事实证明,这样子的调整法跑得快,正确率也是非常高

具体而言,先求出来最大的 x 满足 x!n,这时候有的球的数量是 x!,调整分为两个局面

对于 球数<n 的局面,随机一个权值 x 并让它变为 x+1,消耗 x 现有的数量或者使得总球数不少于 n

对于 球数>n 的局面,随机一个权值 x 并让它变为 x1,消耗 x 现有的数量或者使得总球数不大于 n

Code Display
mt19937_64 Rand(260823);
inline int random(int l,int r){return Rand()%(r-l+1)+l;}
bool f[5010][5010];
int g[5010][5010];
int ton[400010];
signed main(){
    freopen("split.in","r",stdin); freopen("split.out","w",stdout);
    f[1][1]=g[1][1]=1;
    for(int i=1;i<5000;++i){
        for(int j=1;j<=5000;++j) if(f[i][j]){
            for(int k=1;k<=g[i][j];++k){
                int nxt=j-k+k*(i+1);
                if(nxt>5000) break;
                f[i+1][nxt]=1; ckmax(g[i+1][nxt],k*(i+1));
            }
        }
    }
    int T=read(); while(T--){
        int n=read();
        int d=1,fac=1;
        while(fac<=n/d) fac*=d,++d;
        if(fac==n){
            puts("1");
            print(d-1); print(n); putchar('\n');
        }else if(n<=5000){
            vector<pair<int,int> > ans;
            for(int i=5000;i>=1;--i) if(f[i][n]){
                int ni=i,nj=n,Go=0;
                while(ni){
                    if(g[ni][nj]!=Go) ans.emplace_back(ni,g[ni][nj]-Go);
                    Go=g[ni][nj]/ni;
                    nj=nj+Go-g[ni][nj];
                    --ni;
                }
                break;
            }
            if(ans.size()){
                print(ans.size()); putchar('\n');
                for(auto t:ans) print(t.fir),print(t.sec),putchar('\n');
            }else puts("-1");
        }else{
            ton[d-1]=fac;
            int sum=fac;
            set<int> now; now.insert(d-1);
            while(sum!=n){
                int Del=-1,ins=-1;
                if(sum<n){
                    for(auto t:now){
                        if(!random(0,4)) continue;
                        int Mxd=min(ton[t],1+(n-sum)/t);
                        ton[t]-=Mxd;
                        ton[t+1]+=Mxd*(t+1);
                        sum+=Mxd*t;
                        if(ton[t+1]) ins=t+1;
                        if(!ton[t]) Del=t;
                        break;
                    }
                }else{
                    for(auto t:now) if(ton[t]>t){
                        if(random(0,4)){
                            int Mxd=min(1+(sum-n)/(t-1),ton[t]/t);
                            ton[t]-=t*Mxd;
                            ton[t-1]+=Mxd;
                            if(ton[t-1]) ins=t-1;
                            sum-=Mxd*(t-1);
                            if(!ton[t]) Del=t;
                            break;
                        }
                    }
                }
                if(~Del) now.erase(Del);
                if(~ins) now.insert(ins);
            }
            print(now.size()); putchar('\n');
            for(auto t:now) print(t),print(ton[t]),putchar('\n'),ton[t]=0;
        }
    }
    return 0;
}

未来

r 视为 0g 视为 1b 视为 2,那么一次操作可以表示为将 ai 变成 3(ai+ai+1)mod3

这样子就可以考虑每个 ij 的贡献了,如果 aiaj(j<i) 的贡献就是 (mij)

由于是在 mod3 意义下进行计算,那么就对于 (3ik),k0,3i 结果都是 0,可以忽略这些贡献

那么将 m 进行三进制分解,每次扫描走 3i 带来的贡献即可

时间复杂度 Θ(nlogm)

Code Display
const int N=5e5+10;
int a[2][N],n,m;
inline int encode(char s){
    if(s=='r') return 1;
    else if(s=='g') return 0;
    else return 2;
}
inline char decode(int s){
    if(!s) return 'g';
    else if(s==1) return 'r';
    else return 'b';
}
char s[N];
signed main(){
    freopen("future.in","r",stdin); freopen("future.out","w",stdout);
    n=read(); m=read(); scanf("%s",s);
    int cur=0;
    rep(i,0,n-1) a[cur][i]=encode(s[i]);
    while(m){
        int stp=1;
        while(stp<=m/3) stp*=3;
        while(m>=stp){
            rep(i,0,n-1) a[cur^1][i]=3-(a[cur][i]+a[cur][(i+stp)%n]),a[cur^1][i]=(a[cur^1][i]%3+3)%3;
            cur^=1;
            m-=stp;
        }
    }
    rep(i,0,n-1) putchar(decode(a[cur][i]));
    putchar('\n');
    return 0;
}

回忆

强调数据范围 n×m40,那么 min(n,m)6,令 m 为较小的一维

fi,S,s1,s2,s3,max 表示到了完成了前 i 行的统计,最后一行的状态是 S,最后一行可能产生的至多 3 个联通块大小为 s1,s2,s3,历史联通块最值是 max 的概率

这里状态表示成 m4 进制数字,0/1/2 表示属于联通块在最小表示法意义下的标号,而 3 不连通

转移枚举 i+1 行的所有状态,暴力合并得到新的最小表示法即可,使用 std::map 维护后 4 维,状态数其实是很小的,可以通过

Code Display
const int N=50;
int a[N][N],n,m,b[N][N];
map<tuple<int,int,int,int>,int> dp[N][4100];
inline vector<int> decode(int S){
    vector<int> res={0};
    for(int i=1;i<=m;++i) res.push_back(S%4),S/=4;
    return res;
}
inline int encode(vector<int> S){
    int res=0,bas=1;
    for(auto t:S) res+=bas*t,bas<<=2;
    return res;
}
struct DSU{
    int fa[20],siz[N];
    inline void init(int n){rep(i,1,2*m) siz[i]=i>m,fa[i]=i;}
    inline int find(int x){return fa[x]==x?x:fa[x]=find(fa[x]);}
    inline void merge(int x,int y){
        x=find(x); y=find(y); if(x==y) return ; 
        fa[x]=y; siz[y]+=siz[x]; 
        return ;
    }
}T;
signed main(){
    freopen("memory.in","r",stdin); freopen("memory.out","w",stdout);
    n=read(); m=read(); rep(i,1,n) rep(j,1,m) a[i][j]=read();
    if(m>n){
        rep(i,1,n) rep(j,1,m) b[j][i]=a[i][j];
        memset(a,0,sizeof(a));
        swap(n,m);
        rep(i,1,n) rep(j,1,m) a[i][j]=b[i][j];
    }
    dp[0][encode(vector<int>(m,3))][{0,0,0,0}]=1;
    int S=1<<m,U=1<<(2*m);
    for(int lin=0;lin<n;++lin){
        for(int i=0;i<S;++i){
            int per=1,ava[10]={};
            for(int j=1;j<=m;++j){
                if(i>>(j-1)&1) ava[j]=1,ckmul(per,a[lin+1][j]);
                else ckmul(per,del(1,a[lin+1][j]));
            }
            if(!per) continue;
            for(int st=0;st<U;++st){
                vector<int> ar=decode(st);
                for(auto t:dp[lin][st]){
                    int s[3]={},his; tie(s[0],s[1],s[2],his)=t.fir;
                    T.init(2*m);
                    int lst[3]={-1,-1,-1};
                    for(int e=1;e<=m;++e) if(ar[e]!=3){
                        if(~lst[ar[e]]) T.merge(e,lst[ar[e]]);
                        lst[ar[e]]=e;
                    }
                    for(int e=1;e<=m;++e) if(ar[e]!=3) T.siz[T.find(e)]=s[ar[e]];
                    map<int,int> id;
                    for(int e=1;e<=m;++e) if(ava[e]){
                        if(ar[e]!=3) T.merge(e+m,e);
                        if(ava[e-1]) T.merge(e-1+m,e+m);
                        if(ava[e+1]) T.merge(e+1+m,e+m);
                    }
                    int ndcnt=0,ns[3]={};
                    for(int e=1;e<=m;++e) if(ava[e]&&!id.count(T.find(e+m))){
                        ns[ndcnt]=T.siz[T.find(e+m)];
                        id[T.find(e+m)]=ndcnt++;
                        ckmax(his,T.siz[T.find(e+m)]);
                    }
                    vector<int> New_st;
                    for(int e=1;e<=m;++e){
                        if(!ava[e]) New_st.emplace_back(3);
                        else New_st.emplace_back(id[T.find(e+m)]);
                    }
                    ckadd(dp[lin+1][encode(New_st)][{ns[0],ns[1],ns[2],his}],mul(t.sec,per));
                }
            }
        }
    }
    int ans=0;
    for(int i=0;i<U;++i){
        for(auto t:dp[n][i]){
            int v1,v2,v3,v4;
            tie(v1,v2,v3,v4)=t.fir;
            ckadd(ans,mul(t.sec,v4));
        }
    }
    print(ans);
    return 0;
}

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