• 【考试总结】20220326


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

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

    每次修改如果点度数不超过既定阀值 \(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;
    }
    

  • 相关阅读:
    [导入]mootools框架【二】Core篇: 主要方法测试实例
    公司招聘中不能说的秘密 【转载】
    国外10个ASP.Net C#下的开源CMS
    [导入][Flash开发笔记] List控件删除指定label或data的项
    [导入]用C#截取指定长度的中英文混合字符串 改进版
    今天小侄子出生,想了一天的名字
    一个正则表达式的解释
    今日小收获
    昨天的事
    两点东西
  • 原文地址:https://www.cnblogs.com/yspm/p/16059212.html
Copyright © 2020-2023  润新知