【考试总结】2022-03-26

使用经典容斥:联通块数等于亮色灯数量 \(-\) 相邻两个点都是亮点的点对数

将颜色视为点,两种不同颜色的相邻对数记作边权,变成图上问题

每次修改如果点度数不超过既定阀值 \(B\) 暴力扫描出边对边的状态进行修改,否则记录度数大的点和亮点的边权之和即可

我带了 \(\log\) 来得到小空间复杂度,不难发现这种方法大点的数量越少越好,所以块大小开到 \(B=2000\) 即可通过

谨防新型根号分治:不判断度数大于根号直接进行大点的处理

Code Display
const int N=1e5+10,B=2000;
int n,m,Q,col[N],cnt[N];
bool sta[N];
map<int,int> mp[N];
inline void ins(int x,int y){
    if(x>y) swap(x,y);
    mp[x][y]++; mp[y][x]++;
    return ;
}
int lig[N];
signed main(){
    freopen("light.in","r",stdin); freopen("light.out","w",stdout);
    n=read(); m=read(); Q=read();
    rep(i,1,n){
        col[i]=read();
        if(i!=1&&col[i]==col[i-1]) --i,--n;
        else{
            cnt[col[i]]++;
            if(i>1) ins(col[i],col[i-1]);
        }   
    }
    vector<int> nds;
    int ans=0;
    rep(i,1,m) if(cnt[i]>B) nds.emplace_back(i);
    vector<vector<pair<int,int> > >G(m+1);
    for(int i=1;i<=m;++i){
        G[i].resize(mp[i].size());
        int j=0;
        for(auto t:mp[i]) G[i][j++]=t;
    }
    while(Q--){
        int x=read(),coef=sta[x]?-1:1;
        ans+=coef*cnt[x];
        if(cnt[x]<=B){
            for(auto t:G[x]) if(sta[t.fir]) ans-=t.sec*coef;
        }else{
            ans-=coef*lig[x];
        }
        sta[x]^=1;
        for(auto t:nds) if(mp[x].count(t)) lig[t]+=coef*mp[x][t];
        print(ans); putchar('\n');
    }
    return 0;
}

十字路口

设每次观察的时间为 \(t_i\),那么可以根据每个灯红灯剩余时间得到 \(\Theta(m^2)\) 个形如 \(t_i-t_j=a_i-a_j\) 的方程

将每次观察建立点,将 \(a_i< a_j\)\((i,j)\) 连边,使用 \(\rm Floyd\) 可以求出来最小环作为周期,复杂度 \(\Theta(m^3)\),在 \(n>m\) 时有效

否则可以将每个灯变成红灯的时间设为 \(x_i\),比对两次观察可以根据其它点在红灯里面的减少量得到方程

仍然使用图上连边的方式可以得到一个复杂度为 \(\Theta(n^3+n^2m)\),在 \(n\le m\) 的时候有效

两种做法揉起来即可,总复杂度 \(\Theta(nm\sqrt{nm})\)

Code Display
const int N=1e5+10,inf=0x3f3f3f3f3f3f3f3f;
int n,m;
vector<vector<int> >G;
map<int,int> app[N];
signed main(){
    freopen("crossing.in","r",stdin); freopen("crossing.out","w",stdout);
    n=read(); m=read();
    G.resize(m+1);
    for(int i=1;i<=m;++i) G[i].resize(n+1);
    for(int i=1;i<=m;++i){
        for(int j=1;j<=n;++j) G[i][j]=read();
        bool rem=1;
        for(int j=1;j<=n;++j){
            if(app[j].count(G[i][j])){
                int lin=app[j][G[i][j]];
                for(int k=1;k<=n;++k) if(G[lin][k]!=G[i][k]) puts("-1"),exit(0);
                rem=0;
                break;
            }
        }
        if(rem){
            for(int j=1;j<=n;++j) if(G[i][j]) app[j][G[i][j]]=i;
        }else --m,--i,G.pop_back();
    }
    int ans=inf;
    if(m<=n){
        vector<vector<int> > dp(m+1,vector<int>(m+1,inf));
        for(int i=1;i<=n;++i){
            for(int j=1;j<=m;++j) if(G[j][i]){
                for(int k=1;k<=m;++k) if(j^k){
                    if(G[k][i]<G[j][i]) continue;
                    if(dp[j][k]==inf) dp[j][k]=G[k][i]-G[j][i];
                }
            }
        }
        for(int k=1;k<=m;++k){
            for(int i=1;i<=m;++i) if(dp[i][k]!=inf){
                for(int j=1;j<=m;++j) ckmin(dp[i][j],dp[i][k]+dp[k][j]);
            }
        }
        for(int i=1;i<=m;++i) ckmin(ans,abs(dp[i][i]));
    }else{
        vector<vector<int> > dp(n+1,vector<int>(n+1,inf));
        for(int i=1;i<=m;++i){
            for(int j=1;j<=n;++j) if(G[i][j]){
                for(int k=1;k<=n;++k) if(j^k){
                    if(G[i][k]<G[i][j]) continue;
                    if(dp[j][k]==inf) dp[j][k]=G[i][k]-G[i][j];
                }
            }
        }
        for(int k=1;k<=n;++k){
            for(int i=1;i<=n;++i) if(dp[i][k]!=inf){
                for(int j=1;j<=n;++j) ckmin(dp[i][j],dp[i][k]+dp[k][j]);
            }
        }
        for(int i=1;i<=n;++i) ckmin(ans,abs(dp[i][i]));
    }
    print(ans==inf?-1:ans); putchar('\n');  
    return 0;
}

密室逃脱

注意到如果某个某个状态可以在 \(i\) 囤积若干个人,那么进行若干此操作之后仍然可以达成 “在 \(i\) 处囤积相同人数” 的目标,也就是说操作是可逆的

\(f_{i,j}\) 表示考虑前 \(i\) 个隧道处囤积 \(j\) 个人时,只考虑前 \(i\) 个隧道最多能放置的人数

这个状态巧妙地使用了上面的性质,那么转移有如下几条:

  • \(j\in[a_i,a_i+b_i)\),必须让 \(a_i\) 个人留到 \(i\) 隧道处,剩下的前进,转移至 \(f_{i+1,j-a_i}\)

  • \(j\ge a_i+b_i\),正向反向都可以顺利通过这个隧道,转移至 \(f_{i+1,j}\)

  • \(j\in [0,a[i])\) 通道是从左边打不开的,如果右边放置了 \(b_i\) 个人那么可以全员前进,否则右边放置的人数就是最多能到达 \(i+1\) 的人数,也就是以下两条

    \[\begin{aligned}f_{i+1,j+b_i}&\leftarrow f_{i,j}+b_i\\f_{i+1,j}&\leftarrow f_{i,j}+k\ [k<b_i]\end{aligned} \]

注意第二维状态的上界,是 \(\max\{m,\max\{a_i+b_i\}\}\)

Code Display
const int inf=0x3f3f3f3f3f3f3f3f;
const int N=1010,U=2e4;
int dp[N][20010],a[N],b[N];
int n,m;
signed main(){
    freopen("escape.in","r",stdin); freopen("escape.out","w",stdout);
    n=read(); m=read();
    for(int i=1;i<n;++i) a[i]=read(),b[i]=read();
    memset(dp,-0x3f,sizeof(dp));
    rep(i,0,m-1) dp[1][i]=i;
    for(int i=1;i<n;++i){
        int Mx=-inf;
        for(int j=0;j<a[i];++j){
            ckmax(dp[i+1][j+b[i]],dp[i][j]+b[i]);
            ckmax(Mx,dp[i][j]);
        }
        for(int j=0;j<b[i];++j) ckmax(dp[i+1][j],Mx+j);
        for(int j=a[i];j<a[i]+b[i];++j) ckmax(dp[i+1][j-a[i]],dp[i][j]);
        for(int j=a[i]+b[i];j<=U;++j) ckmax(dp[i+1][j],dp[i][j]);
    }	
    int ans=-inf;
    for(int i=0;i<=U;++i) ckmax(ans,dp[n][i]);
    print(ans);
    return 0;
}

posted @ 2022-03-26 16:16  yspm  阅读(82)  评论(0编辑  收藏  举报