1. 程式人生 > 其它 >【考試總結】2022-03-27

【考試總結】2022-03-27

博弈論

詐 騙 大 師

嘗試計算每個棋子放在某個點最多能帶來的等著數量,使用 \(\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;
}