• 【考试总结】20220327


    博弈论

    诈 骗 大 师

    尝试计算每个棋子放在某个点最多能带来的等着数量,使用 \(\rm DAG\) 上面 \(\rm DP\) 即可实现,注意两个人都希望在同色联通块中走的路径最长,同时走到临界点的时候都可以选择不再继续移动

    剩下的是一个 背包问题,如果选出的所有石子给 \(A,B\) 带来的收益满足 \(A\) 的等着数量大于 \(B\) 就胜利了

    Code Display
    const int N=310;
    char s[N];
    int dp[N];
    int f[2][N*N*2],out[N],n,m,a[N];
    vector<int> G[N],revG[N];
    signed main(){
        freopen("game.in","r",stdin); freopen("game.out","w",stdout);
        n=read(); m=read();
        scanf("%s",s+1);
        for(int i=1;i<=n;++i) a[i]=s[i]=='W';
        for(int i=1;i<=m;++i){
            int u=read(),v=read();
            G[u].emplace_back(v);
            revG[v].emplace_back(u);
            out[u]++;
        }
        queue<int> q;
        for(int i=1;i<=n;++i) if(!out[i]) q.push(i);
        while(q.size()){
            int fr=q.front(); q.pop();
            for(auto t:G[fr]){
                if(a[fr]==a[t]) ckmax(dp[fr],dp[t]+1);
                else ckmax(dp[fr],-dp[t]+1);
            }
            for(auto t:revG[fr]){
                if(!(--out[t])) q.push(t);
            }
        }
        for(int i=1;i<=n;++i) if(!a[i]) dp[i]=-dp[i];
        int cur=0;
        
        const int U=N*N;
        f[cur][U]=1;
        for(int i=1;i<=n;++i){
            for(int j=0;j<U*2;++j) if(f[cur][j]){
                ckadd(f[cur^1][j+dp[i]],f[cur][j]);
                ckadd(f[cur^1][j],f[cur][j]);
                f[cur][j]=0;
            }
            cur^=1;
        }
        int ans=0;
        for(int j=U+1;j<U*2;++j) ckadd(ans,f[cur][j]);
        print(ans);
        return 0;
    }
    

    排列

    将排列中二元组 \((a,b)\) 视作有向图上 \(a\to b\) ,那么题目中的限制相当于排列合法当且仅当其每个区间形成的 \(\rm DAG\) 都具有传递性

    这里传递性形式化表达为:记 \(S_i\) 表示 \(i\)\(\rm DAG\) 上可以到达的点所构成的集合,对于 \(x\in S_i,S_x\subsetneq S_x\)

    其实区间的传递性和同时满足 每个前缀具有传递性 \(\&\) 每个后缀具有传递性 是等价的,使用简单的反证法可以找到矛盾

    由于整个排列构成了一个 \(1\sim n\) 的竞赛图,那么上述限制等价于某个 \(\rm DAG\) 和它的补图具有传递性

    我们发现上述形式的传递性和 \((i,p_i)\) 对应的二维偏序是完全等价的,也就是可行的 \(\rm DAG\) 构造是且仅是找到一个排列 \(\rm P\),找到其中所有 \(i<j,a_i>a_j\) 并连边 \(a_j\to a_i\)

    此时转化为将 \(\{1,\dots n\}\) 通过 冒泡排序(也就是只能交换相邻两个元素) 变成 \(\{n,\dots 1\}\) 的方案数,可以通过康拓展开给排列标号后进行拓扑排序得到答案

    对于题目中涉及的 \(m\) 条限制,每次翻转相邻点对时进行合法性判定即可

    Code Display
    int n,lim;
    vector<vector<int> > G,perm,pos;
    vector<pair<int,int> > seq;
    vector<int> Fac,dp;
    inline int dfs(int S){
        if(~dp[S]) return dp[S];
        if(S==Fac[n]) return 1;
        int sum=0;
        vector<int> &cur=perm[S];
        vector<int> p(n+1);
        for(int i=1;i<=n;++i) p[cur[i]]=i;
        for(int i=1;i<n;++i) if(cur[i]<cur[i+1]){
            bool leg=1;
            for(auto t:G[pos[cur[i]][cur[i+1]]]) if(p[seq[t].fir]>p[seq[t].sec]){leg=0; break;}
            if(!leg) continue;
            int New_S=S;
            int o1=0,o2=1;
            for(int j=i+1;j<=n;++j) o1+=cur[j]<cur[i],o2+=cur[j]<cur[i+1];
            New_S+=(o2-o1)*Fac[n-i]-(o2-1-o1)*Fac[n-i-1];
            ckadd(sum,dfs(New_S));
        }
        return dp[S]=sum;
    }
    signed main(){
        freopen("perm.in","r",stdin); freopen("perm.out","w",stdout);
        n=read(); lim=read(); Fac.resize(n+1);
        Fac[0]=1;
        for(int i=1;i<=n;++i) Fac[i]=Fac[i-1]*i;
        dp.resize(Fac[n]+1);
        for(int i=1;i<=Fac[n];++i) dp[i]=-1;
        int m=n*(n-1)/2;
        pos.resize(n+1);
        rep(i,1,n) pos[i].resize(n+1);
        seq.resize(m+1);
        G.resize(m+1);
        int num=0;
        for(int i=1;i<=n;++i) for(int j=i+1;j<=n;++j){
            pos[i][j]=++num;
            seq[num]=make_pair(i,j);
        }
        // Label Begins
        vector<int> id(n+1);
        for(int i=1;i<=n;++i) id[i]=i;
        perm.resize(Fac[n]+1);
        int CNT=0;
        do perm[++CNT]=id; while(next_permutation(id.begin()+1,id.end()));
        // Label Finished
        for(int i=1;i<=lim;++i){
            int a=read(),b=read(),c=read(),d=read();
            G[pos[a][b]].emplace_back(pos[c][d]);
        }
        print(dfs(1)); putchar('\n');
        return 0;
    }
    

    子段和

    尝试计算将最大子段和减少到 \(k\) 的最少操作次数,设为 \(g(k)\)

    进行判定时,维护变量 \(\lim\),初值为 \(k\),以及前缀和数组 \(s_i\) :若 \(s_i>lim\) 则将后缀 \(s_i\) 都减小 \(s_i-lim\)\(\lim\leftarrow \min(\lim,s_i+k)\)

    这个过程和下述过程是等价的,仍然维护 \(\lim=k\) 和操作次数计数器 \(cnt=0\) 如果 \(\lim<s_i\)\(cnt\leftarrow s_i-\lim,\lim\leftarrow s_i\)\(\lim\leftarrow \min(\lim,s_i+k)\)

    正确性相对容易理解

    使用二分答案来得到进行不超过 \(K\) 次操作后最大子段和的值域 \([L,R]\) 那么经过一些和式变换可以发现最终问题就是求最大子段和值域 \([L,R]\) 内所有 \(w\)\(cnt(w)\) 的值,对所有 \(w\) 同时进行上面的过程:

    使用动态开点线段树维护整个过程,每个叶子节点对应一个 \(\lim(w)\) 差为 \(0/1\) 的连续 \(w\),同时需要维护最大最小值和区间 \(\lim(w)\) 的和

    注意这个过程并不需要求出来 \(cnt(w)\) 的具体值,只用计算每次得到的权值对答案的贡献之和,形式为区间长度乘 \(s_i-\) 区间和

    发现在整个过程中 \(\lim(w)\) 满足随 \(w\) 递增而不降同时 \(\lim(w)-w\) 满足不升,所以对于 ckmin,ckmax 操作可以进行线段树上二分得到需要修改的区间,那么在线段树上新建立一个叶子即可

    当然可以直接使用单调栈来代替线段树

    Code Display
    inline int qmod(int x){return (x%mod+mod)%mod;}
    const int N=2e5+10,M=100*N,Inf=2e13;
    inline int S(int l,int r){
        int v1=r-l+1,v2=l+r;
        if(v1&1) return v2/2%mod*(v1%mod)%mod;
        else return v1/2%mod*(v2%mod)%mod;
    }
    bool k[M];
    signed ls[M],rs[M];
    int sum[M],L[M],Mn[M],Mx[M],tot,rt;
    int a[N],n,K,ans,Wl,Wr;
    inline int estab(int sl,int b,int l,int r){
        int now=++tot;
        k[now]=sl; L[now]=b;
        Mn[now]=sl*l+b; Mx[now]=sl*r+b;
        sum[now]=k[now]?S(Mn[now],Mx[now]):mul(L[now]%mod,(r-l+1)%mod);
        return now;
    }
    inline void push_up(int p){
        L[p]=-Inf-1; k[p]=0;
        sum[p]=add(sum[ls[p]],sum[rs[p]]);
        Mn[p]=Inf; Mx[p]=-Inf;
        if(ls[p]){
            ckmax(Mx[p],Mx[ls[p]]);
            ckmin(Mn[p],Mn[ls[p]]);
        }
        if(rs[p]){
            ckmax(Mx[p],Mx[rs[p]]);
            ckmin(Mn[p],Mn[rs[p]]);
        }
        return ;
    }
    inline void make_son(int p,int l,int r){
        int mid=(l+r)>>1;
        if(!ls[p]) ls[p]=estab(k[p],L[p],l,mid);
        if(!rs[p]) rs[p]=estab(k[p],L[p],mid+1,r);
        return ;
    }
    inline int query_leq(int val,int p=rt,int l=Wl,int r=Wr){
        if(Mn[p]>val) return l-1;
        if(Mx[p]<=val) return r;
        if(l==r) return l;
        if(!ls[p]){
            if(k[p]) return val-L[p];
            else return r;
        }
        int mid=(l+r)>>1;
        if(Mx[ls[p]]>=val) return query_leq(val,ls[p],l,mid);
        else return query_leq(val,rs[p],mid+1,r);
    }
    inline int query_sum(int st,int ed,int p=rt,int l=Wl,int r=Wr){
        if(st<=l&&r<=ed) return sum[p];
        if(!ls[p]){
            if(!k[p]) return mul(L[p]%mod,(r-l+1)%mod);
            else return S(L[p]+max(st,l),L[p]+min(ed,r));
        }
        int mid=(l+r)>>1,res=0;
        if(st<=mid) ckadd(res,query_sum(st,ed,ls[p],l,mid));
        if(ed>mid) ckadd(res,query_sum(st,ed,rs[p],mid+1,r));
        return res;
    }
    inline int query_geq(int val,int p=rt,int l=Wl,int r=Wr){    
        if(!ls[p]){
            if(!k[p]){
                if(val+l>L[p]) return l-1;
                if(val+r<L[p]) return r;
                return L[p]-val;
            }else{
                if(L[p]<=val) return l-1;
                else return r;
            }
        }
        int mid=(l+r)>>1;
        if(Mx[ls[p]]<=val+mid) return query_geq(val,ls[p],l,mid);
        return query_geq(val,rs[p],mid+1,r);
    }
    inline void check_min(int st,int ed,int v,int p=rt,int l=Wl,int r=Wr){
        if(st<=l&&r<=ed){
            L[p]=v; Mn[p]=v+l; Mx[p]=v+r;
            sum[p]=S(Mn[p],Mx[p]);
            k[p]=!(ls[p]=rs[p]=0);
            return ;
        }
        if(!ls[p]) make_son(p,l,r);
        int mid=(l+r)>>1;
        if(st<=mid) check_min(st,ed,v,ls[p],l,mid);
        if(ed>mid) check_min(st,ed,v,rs[p],mid+1,r);
        return push_up(p);
    } // v + l
    inline void check_max(int st,int ed,int v,int p=rt,int l=Wl,int r=Wr){
        if(st<=l&&r<=ed){
            L[p]=Mn[p]=Mx[p]=v;
            sum[p]=mul(v%mod,(r-l+1)%mod);
            k[p]=ls[p]=rs[p]=0;
            return ;
        }
        if(!ls[p]) make_son(p,l,r);
        int mid=(l+r)>>1;
        if(st<=mid) check_max(st,ed,v,ls[p],l,mid);
        if(ed>mid) check_max(st,ed,v,rs[p],mid+1,r);
        return push_up(p);
    } // v
    inline int calc(int w){
        int lim=w,Aw=0;
        for(int i=1;i<=n;++i){
            Aw+=max(0ll,a[i]-lim);
            lim=max(lim,a[i]);
            ckmin(lim,a[i]+w);
        } return Aw;
    }
    signed main(){    
        freopen("seg.in","r",stdin); freopen("seg.out","w",stdout);
        n=read();
        int Mn=0,Mx=0;
        rep(i,1,n){
            a[i]=read()+a[i-1];
            ckmax(Mx,a[i]-Mn);
            ckmin(Mn,a[i]);    
        }
        K=read();
        int tar=-Inf,wl=-Inf,wr=Mx;
        while(wl<=wr){
            int mid=(wl+wr)>>1;
            if(calc(mid)>K) wl=mid+1;
            else wr=mid-1,tar=mid;
        }
        if(tar==Mx) print(mul(qmod(Mx),K)),exit(0);
        ans=qmod(tar)*((K+1)%mod)%mod-Mx;
        ans=qmod(ans);
        Wr=Mx-1,Wl=tar;
        rt=estab(1,0,Wl,Wr);
        for(int i=1;i<=n;++i){
            int pos=query_leq(a[i]);
            if(pos>=Wl) ckdel(ans,query_sum(Wl,pos));
            ckadd(ans,mul(a[i]%mod,(pos-Wl+1)%mod));
            if(pos>=Wl) check_max(Wl,pos,a[i]);
            pos=query_geq(a[i]);
            if(pos>=Wl) check_min(Wl,pos,a[i]);
        }
        print(qmod(ans));
        return 0;
    } 
    

  • 相关阅读:
    在Unix上使用管道压缩exp导出文件
    自制CPU的黑暗历程一
    Error C1189: #error: Please use the /MD switch for _AFXDLL builds
    Redis乐观锁解决高并发抢红包的问题
    PHP分页类
    汇编基础——使用nasm和bochs学习汇编
    数据同步工具DBsync
    完成端口的一些教程
    sdf
    (转)C#(WIN FORM)两个窗体间LISTVIEW值的修改
  • 原文地址:https://www.cnblogs.com/yspm/p/16064699.html
Copyright © 2020-2023  润新知