20260601

A - 立方体

题意:给定 N≤200N\le 200N≤200 个轴对齐的立方体(每个用 (x1,y1,z1)(x_1,y_1,z_1)(x1,y1,z1) 到 (x2,y2,z2)(x_2,y_2,z_2)(x2,y2,z2) 表示),它们可能相交或重叠。求这些立方体并集的外表面积。所有坐标在 0,2000,2000,200 内。


赛时没去想这题,后来才发现这题其实是最简单的。

发现数据都 ≤200\le 200≤200,可以求出每个点是否有被立方体所覆盖,然后直接暴力搜索没有被覆盖的点即可。

时间复杂度是 O(2003)O(200^3)O(2003) 的。

cpp 复制代码
#include<bits/stdc++.h>
using namespace std;
int n,cf[205][205][205],a[205][205][205],ans;
bool vis[205][205][205];
int fx[8][3]={{0,0,1},{0,0,-1},{0,1,0},{0,-1,0},{1,0,0},{-1,0,0}};
queue<pair<int,pair<int,int> > > q;
signed main(){
    scanf("%d",&n);
    for(int i=1,x_1,y_1,z_1,x_2,y_2,z_2;i<=n;i++){
        scanf("%d%d%d%d%d%d",&x_1,&y_1,&z_1,&x_2,&y_2,&z_2);
        x_1++,y_1++,z_1++,cf[x_1][y_1][z_1]++,cf[x_2+1][y_1][z_1]--,cf[x_1][y_2+1][z_1]--,cf[x_1][y_1][z_2+1]--,cf[x_2+1][y_2+1][z_1]++,cf[x_2+1][y_1][z_2+1]++,cf[x_1][y_2+1][z_2+1]++,cf[x_2+1][y_2+1][z_2+1]--;
    }
    for(int i=1;i<=202;i++)
        for(int j=1;j<=202;j++)
            for(int k=1;k<=202;k++)
                a[i][j][k]=cf[i][j][k]+a[i-1][j][k]+a[i][j-1][k]+a[i][j][k-1]-a[i][j-1][k-1]-a[i-1][j][k-1]-a[i-1][j-1][k]+a[i-1][j-1][k-1];
    q.push({0,{0,0}}),vis[0][0][0]=1;
    while(!q.empty()){
        int x=q.front().first,y=q.front().second.first,z=q.front().second.second;
        q.pop();
        for(int i=0;i<6;i++){
            int nx=x+fx[i][0],ny=y+fx[i][1],nz=z+fx[i][2];
            if(!(nx>=0&&nx<=202&&ny>=0&&ny<=202&&nz>=0&&nz<=202)) continue;
            if(vis[nx][ny][nz]) continue;
            if(!a[nx][ny][nz]) q.push({nx,{ny,nz}}),vis[nx][ny][nz]=1;
            else ans++;
        }
    }
    return printf("%d\n",ans),0;
}

B - 城市建设

题意:给定一个 nnn 点 mmm 边的无向带权图,qqq 次操作,每次修改一条边的权值。每次修改后,询问当前图的最小生成树边权和。n≤2×104n \le 2\times 10^4n≤2×104,m,q≤5×104m,q \le 5\times 10^4m,q≤5×104。


删边操作比较难处理,考虑离线下来,正难则反,将删边转化成区间含有一条边。

这个容易想到线段树分治,也就是将一段区间拆成 log⁡\loglog 个子区间。

现在,则是需要一种能支持动态 MST 的算法。这个有点类似于 NOI2014 魔法森林,可以使用 LCT,假设原本有一颗 MST,那么加入一条边其实就相当于将连接这两个点当中最大的一条边与新的边比较,看看谁更优则取谁。

cpp 复制代码
#include<bits/stdc++.h>
#define int long long 
using namespace std;
int n,m,q,ans,Cnt;
struct LCT{
    int son[2],fa,lazy,Max,val,Max_num;
}tree[10000005];
int identify(int x){
    if(tree[tree[x].fa].son[0]==x) return 0;
    if(tree[tree[x].fa].son[1]==x) return 1;
    return -1;
}
void pushup(int rt){
   tree[rt].Max = max(tree[rt].val, max(tree[tree[rt].son[0]].Max, tree[tree[rt].son[1]].Max));
    tree[rt].Max_num = 0;
    if(tree[rt].Max == tree[rt].val)               tree[rt].Max_num = rt;
    if(tree[rt].Max == tree[tree[rt].son[0]].Max)  tree[rt].Max_num = tree[tree[rt].son[0]].Max_num;
    if(tree[rt].Max == tree[tree[rt].son[1]].Max)  tree[rt].Max_num = tree[tree[rt].son[1]].Max_num;
}
void rotate(int x){
    int y=tree[x].fa,z=tree[y].fa,opx=identify(x),opy=identify(y);
    if(opy!=-1) tree[z].son[opy]=x;
    tree[x].fa=z;
    tree[y].son[opx]=tree[x].son[opx^1],tree[tree[x].son[opx^1]].fa=y;
    tree[x].son[opx^1]=y,tree[y].fa=x;
    pushup(y),pushup(x);
}
void pushdown(int x){
    if(tree[x].lazy){
        swap(tree[x].son[0],tree[x].son[1]);
        if(tree[x].son[0]) tree[tree[x].son[0]].lazy^=1;
        if(tree[x].son[1]) tree[tree[x].son[1]].lazy^=1;
        tree[x].lazy=0;
    }
}
void pushall(int x){
    if(identify(x)!=-1) pushall(tree[x].fa);
    pushdown(x);
}
void splay(int x){
    pushall(x);
    while(identify(x)!=-1){
        int y=tree[x].fa,z=tree[y].fa,opx=identify(x),opy=identify(y);
        if(opy==-1) rotate(x);
        else{
            (opx==opy)?rotate(y):rotate(x);
            rotate(x);
        }
    }
}
void access(int x){
    for(int p=0;x;p=x,x=tree[x].fa)
        splay(x),tree[x].son[1]=p,pushup(x);
}
void makeroot(int x){
    access(x),splay(x),tree[x].lazy^=1;
}
int findroot(int x){
    access(x),splay(x);
    while(tree[x].son[0]) pushdown(x),x=tree[x].son[0];
    splay(x);
    return x;
}
void link(int x,int y){
    makeroot(x);
    if(findroot(y)!=x) tree[x].fa=y;
}
void cut(int x, int y) {
    makeroot(x),access(y),splay(y);
    if(tree[y].son[0] == x && !tree[x].son[1]) {
        tree[y].son[0] = tree[x].fa = 0;
        pushup(y);
    }
}
int x[50005],y[50005],val[50005],lst[50005],X[50005*25],Y[50005*25];
vector<pair<pair<int,int>,int> > v[50005<<2];
void change(int rt,int l,int r,int L,int R,int s,int t,int num){
    if(L>R) return;
    if(l==L&&r==R){
        v[rt].push_back({{s,t},num});
        return;
    }
    int mid=l+r>>1;
    if(R<=mid) change(rt<<1,l,mid,L,R,s,t,num);
    else if(mid+1<=L) change(rt<<1|1,mid+1,r,L,R,s,t,num);
    else change(rt<<1,l,mid,L,mid,s,t,num),change(rt<<1|1,mid+1,r,mid+1,R,s,t,num);
}
stack<pair<int,pair<int,pair<int,int> > > > Stack;
void dfs(int rt,int l,int r){
    int sum=0;
    for(pair<pair<int,int>,int > i:v[rt]){
        int u=i.first.first,v=i.first.second,val=i.second;
        if(findroot(u)==findroot(v)){
            makeroot(u),access(v),splay(v);
            if(tree[v].Max<=val) continue;
            ans-=tree[v].Max;
            int w=tree[v].Max_num;
            Stack.push({-tree[v].Max,{w,{Y[w],X[w]}}}),sum++;
            cut(X[w],w),cut(Y[w],w);
        }
        tree[++Cnt]={{0,0},0,0,val,val,Cnt},link(u,Cnt),link(v,Cnt),Stack.push({val,{Cnt,{u,v}}}),ans+=val,sum++,X[Cnt]=u,Y[Cnt]=v;
    }
    if(l==r) printf("%lld\n",ans);
    else{
        int mid=l+r>>1;
        dfs(rt<<1,l,mid),dfs(rt<<1|1,mid+1,r);
    }
    while(sum--){
        int op=Stack.top().first,num=Stack.top().second.first,u=Stack.top().second.second.first,v=Stack.top().second.second.second;
        Stack.pop(),ans-=op;
        if(op<0) link(u,num),link(v,num);
        else cut(u,num),cut(v,num);
    }
}
signed main(){
    scanf("%lld%lld%lld",&n,&m,&q),Cnt=n,tree[0]={{0,0},0,0,-1000000000,-1000000000,0};
    for(int i=1;i<=n;i++) tree[i]={{0,0},0,0,-1000000000,-1000000000,i};
    for(int i=1;i<=m;i++) scanf("%lld%lld%lld",&x[i],&y[i],&val[i]),lst[i]=1;
    for(int i=1,id,Val;i<=q;i++)
        scanf("%lld%lld",&id,&Val),change(1,1,q,lst[id],i-1,x[id],y[id],val[id]),val[id]=Val,lst[id]=i;
    for(int i=1;i<=m;i++) change(1,1,q,lst[i],q,x[i],y[i],val[i]);
    dfs(1,1,q);
    return 0;
}

C - 比特矩阵

题意:定义比特矩阵乘法:C=A×BC = A \times BC=A×B 满足 ci,j=⋁k=1n(ai,k⊕bk,j)c_{i,j} = \bigvee_{k=1}^{n} (a_{i,k} \oplus b_{k,j})ci,j=⋁k=1n(ai,k⊕bk,j),其中 ∨\vee∨ 表示按位或,⊕\oplus⊕ 表示按位异或。给定 n×nn \times nn×n 矩阵 AAA 和正整数 mmm(n≤500n \le 500n≤500,m≤109m \le 10^9m≤109),计算 AmA^mAm(mmm 次幂)。矩阵元素为非负整数 ≤109\le 10^9≤109。


二进制拆分,对于每一位考虑。

这样一次矩阵乘法可以用 bitset 优化,降到 O(n3w)O(\frac{n^3}{w})O(wn3),猜一下,肯定是不断循环的,通过暴力,可以发现,循环节很小,在 n=100n=100n=100 时最大循环长度就 555,所以大胆猜测,n=500n=500n=500 时也很小,直接暴力找循环节可能就可以了。


赛时的思路,循环节长度错了。其实随机下很小,但是可以卡到 >n>n>n 的。然后由于数据加强了,就变成 88pts88pts88pts 了。在认为自己做法还是正确的时候,发现了一个将矩阵乘法 O(n3w)O(\frac{n^3}{w})O(wn3) 优化到 O(n2)O(n^2)O(n2) 的做法,就是观察一下,它其实就是等价于判断一行和另一个的一列是否完全相等。如相等,则是 000 否则为 111,用 Hash 即可。

但是,由于循环节的原因,极端情况下是 O(n2log⁡a×循环节)O(n^2 \log a \times 循环节)O(n2loga×循环节) 的,无法通过。

后来,看了题解,其实如果在把矩阵按行在拆一下,问题就解决了。用 n2n^2n2 那个 Hash,可以看出,一个行向量去和那个单位矩阵去做矩阵乘法其实是最多只会有 n+1n+1n+1 种的(与每一个分别相等 + 都不相等)。这样其实问题就解决了,暴力找循环节。但是这里需要提前预处理一下 n+1n+1n+1 种情况之间相互变化关系,用 O(n)O(n)O(n) 求之间变化后的结果(因为是行向量与单位矩阵,所以不用 O(n2)O(n^2)O(n2)),这样就可以降到 O(n2log⁡nlog⁡a)O(n^2 \log n \log a)O(n2lognloga) 了。

cpp 复制代码
#include<bits/stdc++.h>
using namespace std;
int n,k,num[505][505],Ans[505][505],to[2005],Round[2005],to_Round[2005];
bitset<505> Y[505],X[2005],All,s[505];
unordered_map<bitset<505>,int> Map;
bitset<505> ans; 
bitset<505> change(bitset<505> x){
    ans.reset();
    for(int i=1;i<=n;i++) ans[i]=(x!=Y[i]);
    return ans;
}
bool vis[505];
vector<int> v[505];
signed main(){
    scanf("%d%d",&n,&k);
    for(int i=1;i<=n;i++)
        for(int j=1;j<=n;j++)
            scanf("%d",&num[i][j]);
    for(int i=1;i<=n;i++) X[1][i]=1; 
    for(int w=0;w<=30;w++){
        Map.clear();
        for(int j=1;j<=n;j++){
            Y[j].reset();
            for(int i=1;i<=n;i++) Y[j][i]=((num[i][j]>>w)&1);
        }
        int cnt=2;
        Map[X[1]]=1; Map[X[2]]=2;
        memset(vis,0,sizeof vis);
        for(int i=1;i<=n;i++){
            if(vis[i]) continue;
            ++cnt; X[cnt].reset();
            for(int j=1;j<=n;j++) 
                if(!vis[j] && Y[j]==Y[i])
                    vis[j]=1, X[cnt][j]=1;
            if(Map.count(X[cnt])) cnt--;
            else Map[X[cnt]]=cnt;
        }
        for(int i=1;i<=n;i++){
            s[i].reset();
            for(int j=1;j<=n;j++) s[i][j]=((num[i][j]>>w)&1);
            if(!Map.count(s[i]))
                ++cnt, X[cnt]=s[i], Map[s[i]]=cnt;
        }
        queue<int> q;
        for(int i=1;i<=cnt;i++) q.push(i);
        while(!q.empty()){
            int u = q.front(); q.pop();
            bitset<505> nxt = change(X[u]);
            if(!Map.count(nxt)){
                Map[nxt] = ++cnt;
                X[cnt] = nxt;
                q.push(cnt);
            }
        }
        for(int i=1;i<=cnt;i++) to[i]=Map[change(X[i])];
        vector<int> Vis(cnt+1,0), jl_p(cnt+2,0);
        for(int i=1;i<=n;i++){
            fill(Vis.begin(), Vis.end(), 0);
            int state = Map[s[i]];
            Vis[state]=1; jl_p[1]=state;
            int First=0, Size=0;
            int cur = state;
            for(int step=2;step<=k;step++){
                cur = to[cur];
                jl_p[step]=cur;
                if(Vis[cur]){
                    Size = step - Vis[cur];
                    First = Vis[cur];
                    break;
                }
                Vis[cur]=step;
            }
            int p = k;
            if(First) p = (k - First) % Size + First;
            for(int j=1;j<=n;j++) if(X[jl_p[p]][j]) Ans[i][j] |= (1<<w);
        }
    }
    for(int i=1;i<=n;i++,puts(""))
        for(int j=1;j<=n;j++)
            printf("%d ",Ans[i][j]);
    return 0;
}

总结

对于像 B 这样删边操作很难处理而又没强制在线的时候,可以想一想反向来做,将它转化成一段区间内的加边,用线段树分治将它分解,从而使其问题转化容易。

而 C 这样,需要多挖掘一下题目的性质,而不是盲目的用暴力去验证自己的猜想。

相关推荐
晚笙coding1 小时前
从“看起来像双指针”到真正的动态规划 —— 最长公共子序列
算法·动态规划
05候补工程师1 小时前
【考研高数核心突破】极限的本质、高频解题套路与海涅定理深度解析(附经典例题思维导图式拆解)
经验分享·笔记·考研·算法
智者知已应修善业1 小时前
【51单片机8个LED的花样12亮34熄56间隔78闪烁3秒3闪烁】2023-11-4
c++·经验分享·笔记·算法·51单片机
老鱼说AI2 小时前
统计学习方法第五章:从浅入深解析决策树
人工智能·深度学习·算法·决策树·机器学习·学习方法
KaMeidebaby2 小时前
卡梅德生物技术快报|蛋白修饰调控 NETosis 分子机制及实验研究进展
前端·数据库·人工智能·算法·百度
初中就开始混世的大魔王2 小时前
5 Fast DDS-Discovery
网络·c++·算法·中间件
Deep-w2 小时前
【MATLAB】基于模型预测控制的自适应巡航车辆过渡工况安全控制研究
开发语言·人工智能·算法·机器学习·matlab
运行时记录2 小时前
Sirchmunk 让搜索随查询自进化
算法
浮生望2 小时前
双指针算法面试通关指南:从入门到精通
算法