【考试总结】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;
}

十字路口

设每次观察的时间为 ti,那么可以根据每个灯红灯剩余时间得到 Θ(m2) 个形如 titj=aiaj 的方程

将每次观察建立点,将 ai<aj(i,j) 连边,使用 Floyd 可以求出来最小环作为周期,复杂度 Θ(m3),在 n>m 时有效

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

仍然使用图上连边的方式可以得到一个复杂度为 Θ(n3+n2m),在 nm 的时候有效

两种做法揉起来即可,总复杂度 Θ(nmnm)

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 处囤积相同人数” 的目标,也就是说操作是可逆的

fi,j 表示考虑前 i 个隧道处囤积 j 个人时,只考虑前 i 个隧道最多能放置的人数

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

  • j[ai,ai+bi),必须让 ai 个人留到 i 隧道处,剩下的前进,转移至 fi+1,jai

  • jai+bi,正向反向都可以顺利通过这个隧道,转移至 fi+1,j

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

    fi+1,j+bifi,j+bifi+1,jfi,j+k [k<bi]

注意第二维状态的上界,是 max{m,max{ai+bi}}

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 @   没学完四大礼包不改名  阅读(84)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· 震惊!C++程序真的从main开始吗?99%的程序员都答错了
· 【硬核科普】Trae如何「偷看」你的代码?零基础破解AI编程运行原理
· 单元测试从入门到精通
· 上周热点回顾(3.3-3.9)
· winform 绘制太阳,地球,月球 运作规律
点击右上角即可分享
微信分享提示