【考试总结】2022-03-06

高维游走

首先使用 库默尔定理 发现将 \(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;
}

posted @ 2022-03-07 19:11  yspm  阅读(139)  评论(2编辑  收藏  举报