• 【考试总结】20220306


    高维游走

    首先使用 库默尔定理 发现将 \(t_0\) 划分成 \(\{a_0\dots a_m\},\forall i\neq j,a_i\cap a_j=\emptyset,\forall i,a_i\subseteq t_0\cap t_i\) 才能在组合数中构成奇数

    最朴素的想法是直接 std::bitset 来对每个 \(f(i)\) 计算奇偶性,但是可以通过划分阶段来将问题简化

    逐二进制位转移是个不错的选择,设 \(g_{i,x}\) 表示已经转移了前 \(0\sim i\) 位,\(f(x)\bmod 2\) 的值,转移枚举第 \(i+1\) 位这个 \(1\) 被哪个 \(a_j\) 选走了,就可以让 \(g_{i+1,j\times2^{i+1}+x}\) 产生更新

    这时如果将 \(x\bmod 2^{i+1}\) 分组,那么 \(x+j\times 2^{i+1}\equiv x\mod 2^{i+1}\),也就是说奇偶性的变化只会在组内发生

    那么由于已经转移的权值不超过 \(2^i\) 每组非零的 \(x\) 的数量不会超过 \(m\times 2^i\),即每个组里面的元素不超过 \(m\) 个,又根据权值是 \(0/1\),可以使用二进制位压缩

    本质上 \(0/1\) 表示不相同的组是 \(\Theta(2^m)\) 数量级的,所以每个余数都开个变量非常蠢,再开桶 \(ton_S\) 表示压缩后是 \(S\) 的组的数量

    对于 \(t_0\) 不包含的位,从 \(S\bmod\ 2_i\to x \bmod\ 2^{i+1}\) 发生的变化就是商是奇数的和商是偶数的分开了,这样子的变化可以预处理

    对于 \(t_0\) 包含的位需要先枚举 同样包含这位的 \(t_i\) 把方案数变成偶数的异或掉再转移即可

    Code Display
    const int N=2000010;
    int t[N],n,m,dp[2][N],ev[N],od[N];
    signed main(){
        freopen("travel.in","r",stdin); freopen("travel.out","w",stdout);
        for(int i=1;i<=N-10;++i){
            ev[i]=(ev[i>>2]<<1)|((i>>1)&1);
            od[i]=(od[i>>2]<<1)|(i&1);
        }
        int T=read(); while(T--){
            n=read(); rep(i,0,n) t[i]=read();
            int cur=0,S=(1<<(n+1)); --S;
            dp[cur][1]=1;
            for(int i=0;i<31;++i){
                if(t[0]>>i&1){
                    for(int st=0;st<=S;++st) if(dp[cur][st]){
                        int nxt=st;
                        for(int j=1;j<=n;++j) if(t[j]>>i&1) nxt^=(st<<j);
                        dp[cur^1][ev[nxt]]+=dp[cur][st];
                        dp[cur^1][od[nxt]]+=dp[cur][st];
                        dp[cur][st]=0;
                    }
                }else{
                    for(int st=0;st<=S;++st) if(dp[cur][st]){
                        dp[cur^1][ev[st]]+=dp[cur][st];
                        dp[cur^1][od[st]]+=dp[cur][st];
                        dp[cur][st]=0;
                    }
                }
                cur^=1;
            }
            int ans=0;
            for(int i=1;i<=S;++i) ans+=dp[cur][i]*__builtin_popcount(i),dp[cur][i]=0;
            print(ans);
        }
        return 0;
    }
    

    过山车

    如果只判定合法性的做法就是将每个点拆成横向/竖向两个点并再拉一个本原点出来

    黑白染色之后本原点向源点/汇点连流量为 \(2\) 的边,同时向横向、竖向两个点都连流量为 \(2\) 的边

    根据网格图将各个点的横向/纵向点连起来看看最大流是不是点数即可

    如果附加权值考虑 “减少竖直边” 这样的求解方式,从本原点连向横向/纵向点流量为 \(2\) 边中一个带 \(0\) 权,另一个带 \(a_{i,j}\) 的权值即可

    由于是最小费用最大流,那么一定先选择两个 \(0\) 权边,也就是拐弯的方法

    Code Display
    const int N=1e5+10,inf=0x3f3f3f3f;
    struct edge{int to,nxt,lim,cst;}e[N<<2];
    int head[N],dst[N],pre[N],incf[N];
    int tot,id1[200][200],id[200][200],id2[200][200],S,T;
    int n,m,cnt=1,ban[200][200],val[200][200];
    bool inq[N];
    inline bool spfa(){
        for(int i=1;i<=tot;++i) incf[i]=0,dst[i]=inf; dst[S]=0; incf[S]=inf;
        queue<int> q; q.push(S);
        while(q.size()){
            int fr=q.front(); q.pop(); inq[fr]=0;
            for(int i=head[fr];i;i=e[i].nxt) if(e[i].lim){
                int t=e[i].to; if(dst[fr]+e[i].cst<dst[t]){
                    dst[t]=dst[fr]+e[i].cst; incf[t]=min(incf[fr],e[i].lim); pre[t]=i;
                    if(!inq[t]) inq[t]=1,q.push(t);
                }
            }
        }
        return dst[T]!=inf;   
    }
    inline void adde(int u,int v,int w,int c){
        e[++cnt]={v,head[u],w,c}; head[u]=cnt;
        return ;
    }
    inline void add(int u,int v,int w,int c){return adde(u,v,w,c),adde(v,u,0,-c);}
    signed main(){
        freopen("roller.in","r",stdin); freopen("roller.out","w",stdout);
        n=read(); m=read();
        int sum=0,block=0;
        S=++tot; T=++tot;
        rep(i,1,n) rep(j,1,m) ban[i][j]=read();
        rep(i,1,n) rep(j,1,m){
            val[i][j]=read();
            sum+=(!ban[i][j])*val[i][j];
            block+=!ban[i][j];
            if(!ban[i][j]){
                id1[i][j]=++tot,id2[i][j]=++tot,id[i][j]=++tot;
                if((i+j)&1){
                    add(S,id[i][j],2,0);
                    add(id[i][j],id1[i][j],1,0);
                    add(id[i][j],id1[i][j],1,val[i][j]);
                    add(id[i][j],id2[i][j],1,0);
                    add(id[i][j],id2[i][j],1,val[i][j]);
                }else{
                    add(id[i][j],T,2,0);
                    add(id1[i][j],id[i][j],1,0);
                    add(id1[i][j],id[i][j],1,val[i][j]);
                    add(id2[i][j],id[i][j],1,0);
                    add(id2[i][j],id[i][j],1,val[i][j]);
                }
            }
        }
        for(int i=1;i<=n;++i){
            for(int j=1;j<=m;++j) if(!ban[i][j]&&(i+j)%2){
                if(i+1<=n&&!ban[i+1][j]) add(id1[i][j],id1[i+1][j],1,0);
                if(i-1&&!ban[i-1][j]) add(id1[i][j],id1[i-1][j],1,0);
                if(j+1<=m&&!ban[i][j+1]) add(id2[i][j],id2[i][j+1],1,0);
                if(j-1&&!ban[i][j-1]) add(id2[i][j],id2[i][j-1],1,0);
            }   
        }
        int flow=0;
        while(spfa()){
            int x=T;
            while(x!=S){
                e[pre[x]].lim-=incf[T];
                e[pre[x]^1].lim+=incf[T];
                x=e[pre[x]^1].to;
            }
            flow+=incf[T]; sum-=incf[T]*dst[T];
        }
        if(flow==block) print(sum);
        else print(-1);
        return 0;
    }
    

    木棍

    将线段转化成左端点(也就是让限制的右端点 \(-k\)),同时记 \(s_i\) 表示前 \(i\) 个整点上左端点数量

    那么线段长度是 \(k\Leftrightarrow\) \(\forall l,r,s_r-s_{l}\le \lceil\frac{r-l}k\rceil\)

    对于 \(m\) 条限制中也就是 \(s_{x-1}\ge s_y-c\)\(n\) 个木棍的位置可以表达成 \(s_{b_i}\ge s_{a-1}+1\),最后根据实际含义还还有 \(s_{i}\ge s_{i+1}-1\)

    先考察 \(s_{b}\ge s_a+1\),设 \(U(l,r)\) 表示区间 \([l,r]\) 里面的线段数量,使用 \(\rm Hall\) 定理发现 \((a,b)\) 的限制需要表示为 \(s_r-s_{l-1}\ge U(l,r)\)

    \(n\) 较大时这样子的连边方式接受不了,尝试只保留 “限制区间” 的左端点\(-1\) 和右端点作为 关键点 及关键点所涉及到的边(也就是导出子图)来进行差分约束

    对于原图上的一个不全是关键点的正环,找到一个非关键点,它在环上的连边不能是 \(m\) 条限制中的边,如果有连续的 \(\ge U(l,r)\) 或者 \(\le -\lceil\frac {len}k\rceil\) 边,合并起来并不会让环的权值减小

    那么保持 \(U(l,r)\) 不变,让这个点所对应的区间缩小一定会让环上的权值不降,所以可以只对导出子图做差分约束

    在进行差分约束的过程中,观察边的构成,容易使用 \(\Theta(n+m)\) 来处理 \(s_i\le s_{i+1},s_{x-1}\ge s_y-c\)

    对于 \(s_r-s_{l-1}\ge U(l,r)\) 这是经典的扫描线问题,在我的 chkmax 式差分约束中可以表示为 \(s_r\ge \max\limits_{l<r}\{s_{l-1}+U(l,r)\}\)

    第四种 \(s_r-s_{l}\le \lceil\frac{r-l}k\rceil\Rightarrow s_l\ge s_r-\lceil\frac rk\rceil+\lceil\frac lk\rceil-(r\bmod \ k>l\bmod \ k)\) 可以将 \(i\in[1,n]\) 按照模 \(k\) 的余数排序之后,使用 \(\text {Fenwick Tree}\) 维护后缀最大值即可

    注意更新是有顺序的,在正序扫描的时候对于同余数的先都插入再查询,而倒序时要先都查询再插入,来避免漏转移或者错误的转移

    如果把线段树换成 zkw 线段树 之后感觉没有常数优化余地了,不知道 \(\text{std}\) 是怎么做的,只能把正环长度卡到 \(3000\)

    Code Display
    const int N=4010,inf=0x3f3f3f3f;
    int n,m,k,x[N],y[N],c[N],a[N],b[N];
    int num,ar[N];
    vector<int> inter[N];
    int now[N],nxt[N];
    struct Fenwick{
        int c[N];
        inline void clear(){memset(c,-0x3f,sizeof(c));}
        inline void insert(int x,int v){
            for(;x;x-=x&(-x)) ckmax(c[x],v);
            return ;
        }
        inline int query(int x){
            int Mx=0;
            for(;x<=num;x+=x&(-x)) ckmax(Mx,c[x]);
            return Mx;
        }
    }T;
    struct Seg{
    #define ls p<<1
    #define rs p<<1|1
        int Mx[N<<2],tag[N<<2],bit;
        inline void push_up(int p){Mx[p]=max(Mx[ls],Mx[rs])+tag[p];}
        inline void build(int n){
            bit=1; while(bit<=n+1) bit<<=1;
            for(int i=bit+1;i<=bit+n;++i) Mx[i]=now[i-bit],tag[i]=0;
            for(int i=bit-1;i>=1;--i) Mx[i]=max(Mx[i<<1],Mx[i<<1|1]),tag[i]=0;
            return ;
        }
        inline void upd(int st,int ed){
            st+=bit-1; ed+=bit+1;
            while(st!=ed-1){
                if(!(st&1)) Mx[st^1]++,tag[st^1]++;
                if(ed&1) Mx[ed^1]++,tag[ed^1]++;
                push_up(st>>=1); push_up(ed>>=1);
            } 
            while(st>>1) push_up(st>>=1);
            return ;
        }
        inline int query(int st,int ed){
            st+=bit-1; ed+=bit+1;
            int lef=0,rig=0;
            while(st!=ed-1){
                if(!(st&1)) ckmax(lef,Mx[st^1]);
                if(ed&1) ckmax(rig,Mx[ed^1]);
                lef+=tag[st>>=1]; rig+=tag[ed>>=1];
            }
            int Mx=max(lef,rig);
            while(st>>1) Mx+=tag[st>>=1];
            return Mx;
        }
    #undef ls
    #undef rs
    }seg;
    int quo[N],rem[N],id[N];
    signed main(){
        freopen("stick.in","r",stdin); freopen("stick.out","w",stdout);
        n=read(); m=read(); k=read(); 
        rep(i,1,n){
            a[i]=read(),b[i]=read()-k;
            ar[++num]=a[i]-1; ar[++num]=b[i];
        }
        rep(i,1,m){
            x[i]=read(),y[i]=read()-k,c[i]=read();
            ar[++num]=x[i]-1; ar[++num]=y[i];
        }
        sort(ar+1,ar+num+1); num=unique(ar+1,ar+num+1)-ar-1;
        rep(i,1,num){
            quo[i]=ar[i]/k,rem[i]=(ar[i]%k+k)%k,id[i]=i;
            while(k*quo[i]>ar[i]) quo[i]--;
        }
        sort(id+1,id+num+1,[&](const int &x,const int &y){return rem[x]<rem[y];});
        rep(i,1,n){
            a[i]=lower_bound(ar+1,ar+num+1,a[i]-1)-ar;
            b[i]=lower_bound(ar+1,ar+num+1,b[i])-ar;
            inter[b[i]].push_back(a[i]);
        }
        rep(i,1,m){
            x[i]=lower_bound(ar+1,ar+num+1,x[i]-1)-ar;
            y[i]=lower_bound(ar+1,ar+num+1,y[i])-ar;
        }
        //four kinds of edges
        // first -> s[x[i]-1]>=s[y[i]]+c;
        // second-> s[i]<=s[i+1]
        // third -> s[i]>=s[j]-(i-j+k-1)/k
        // fourth-> scaning line
        memset(now,-0x3f,sizeof(now)); now[0]=0;
        memset(nxt,-0x3f,sizeof(nxt));
        for(int turn=1;turn<=num;++turn){
            nxt[0]=now[0];
            for(int i=1;i<=num;++i) nxt[i]=max(nxt[i-1],now[i]);
            //second type
            for(int i=1;i<=m;++i) ckmax(nxt[x[i]],now[y[i]]-c[i]);
            //first type
            seg.build(num);
            for(int i=1;i<=num;++i){
                for(auto t:inter[i]) seg.upd(1,t);
                if(i-1) ckmax(nxt[i],seg.query(1,i-1));
            }
            //scaning line
            T.clear();
            for(int i=1;i<=num;++i){
                int rig=i;
                while(rig<num&&rem[id[rig+1]]==rem[id[i]]) ++rig;
                for(int j=i;j<=rig;++j) T.insert(id[j],now[id[j]]-quo[id[j]]);   
                for(int j=i;j<=rig;++j) ckmax(nxt[id[j]],T.query(id[j]+1)+quo[id[j]]);
                i=rig;
            }
            T.clear();
            for(int i=num;i>=1;--i){
                int rig=i;
                while(rig>1&&rem[id[rig-1]]==rem[id[i]]) --rig;
                for(int j=i;j>=rig;--j) ckmax(nxt[id[j]],T.query(id[j]+1)+quo[id[j]]-1);
                for(int j=i;j>=rig;--j) T.insert(id[j],now[id[j]]-quo[id[j]]);
                i=rig;
            }
            //third type
            bool updd=0;
            for(int i=1;i<=num;++i) if(now[i]!=nxt[i]){updd=1; break;}
            if(!updd) break;
            if(turn==num) puts("No"),exit(0);
            for(int i=1;i<=num;++i) now[i]=nxt[i],nxt[i]=-inf;
        }
        puts("Yes");
        return 0;
    }
    

  • 相关阅读:
    length()与trim()函数用法
    软件测试面试题集锦
    数据库索引介绍
    sum 函数语法与应用
    报表测试方法与注意事项
    添加、编辑、删除功能测试点
    登陆测试思路总结
    查询功能测试点总结
    case 函数语法与使用
    js获取地址栏上的Id值
  • 原文地址:https://www.cnblogs.com/yspm/p/15977579.html
Copyright © 2020-2023  润新知