[51] (多校联训) A层冲刺NOIP2024模拟赛09

关于生成式 AI

怎么才能让这个 b 学会断句

我目前的方案是,把逗号和句号单独作为一个特殊词汇看待,也统计到词频里,该断句的时候就断

表扬这次的题解,写的很清楚

A.排列最小生成树

  • 总存在一颗生成树使得树上最大边权值小于 \(n\)

考虑直接连接序列里的所有 \((i,i+1)\),因为 \(|a_i-a_{i+1}|\lt n\)(由排列的性质),因此 \(w_{\max}\lt((i+1)-i)\times n=n\)

所以我们直接考虑连权值小于 \(n\) 的边即可

  • 一条边 \((i,j)\lt n\),则 \(|i-j|\lt \sqrt{n}\)\(|a_i-a_j|\lt\sqrt{n}\)

考虑反证,设 \(|i-j|\ge \sqrt{n}\)\(|a_i-a_j|\ge \sqrt{n}\),考虑最小的情况也有 \(\sqrt{n}\times \sqrt{n}=n\ge n\),因此原命题成立

所以我们只需要考虑使得 \(|i-j|\lt \sqrt{n}\)\(|a_i-a_j|\lt\sqrt{n}\) 的节点即可

因为是排列,可以提前统计每个值所在的位置,然后直接通过值或者位置加减来找对应的点

枚举的这里有个优化(没啥大用)

  • 由于是无序数对,钦定里层的变量小于外层变量,这样可以少一半枚举量和空间浪费

找到了以后直接跑最小生成树就行了

但是直接这么跑复杂度是 \(n\sqrt{n}\log n\alpha\) 的,还挂大常数,显然过不去

刚才我们已经证明了,我们找到的值的值域在 \(n\) 之内,因此我们直接对值域开桶,每次找到数对就存到对应的桶里,这样就能避免排序

复杂度 \(n\sqrt{n}\alpha\)

#include<bits/stdc++.h>
using namespace std;
struct dsu{
    int fa[50001];
    void clear(int n){
        for(int i=1;i<=n;++i){
            fa[i]=i;
        }
    }
    int find(int id){
        if(id==fa[id]) return id;
        return fa[id]=find(fa[id]);
    }
    bool connect(int x,int y){
        int fx=find(x),fy=find(y);
        if(fx==fy) return false;
        fa[fx]=fy;
        return true;
    }
}dsu;
int n;
int a[50001];
inline int sol(int i,int j){
    return abs(i-j)*abs(a[i]-a[j]);
}
struct node{
    int i,j;
};
vector<node>q[50001];
int pos[50001];
int vis[50001];
int main(){
    int st=scanf("%d",&n);
    for(int i=1;i<=n;++i){
        st=scanf("%d",&a[i]);
        pos[a[i]]=i;
    }
    dsu.clear(n);
    int tmp=sqrt(n);
    for(int i=1;i<=n;++i){
        for(int j=max(1,i-tmp);j<i;++j){
            if(sol(i,j)<=n){
                q[sol(i,j)].push_back({i,j});
            }
            if(sol(pos[i],pos[j])<=n){
                q[sol(pos[i],pos[j])].push_back({pos[i],pos[j]});
            }
        }
    }
    long long tot=0,ans=0;
    for(int i=1;i<=n;++i){
        for(node u:q[i]){
            if(tot==n-1) break;
            if(dsu.connect(u.i,u.j)){
                ans+=i;
                tot++;
            }
        }
    }
    cout<<ans<<endl;
}

B.卡牌游戏

先考虑互质怎么做

n=3 m=4
n 1 2 3 1 2 3 1 2 3 1 2 3
m 1 2 3 4 1 2 3 4 1 2 3 4

通过找规律可以发现,\(m_i=1\) 的所有位置中,对应的 \(n_i=1,2,3\) 恰好都出现过了一遍,其他的 \(m_i\) 也是同理,因此我们直接对 \(n\) 维护有序数组,对每个 \(m_i\),既然它们面对的对手都是一样的,那么也一样处理,在 \(n\) 的有序数组里二分查找大于,小于,等于 \(m_i\) 的数即可

那么不互质怎么做

n=4 m=6 gcd(n,m)=2
1 2 3 4 1 2 3 4 1 2 3 4 1 2 3 4 1 2 3 4 1 2 3 4
1 2 3 4 5 6 1 2 3 4 5 6 1 2 3 4 5 6 1 2 3 4 5 6

可以发现,\(m_i=1,3,5\) 对应了 \(n_i=1,3\)\(m_i=2,4,6\) 对应了 \(n_i=2,4\)

  • 如果 \(m_i\equiv n_j\pmod {\text{gcd}(n,m)}\),则 \(m_i\) 的对手中有 \(n_j\)

这里感性理解比较好理解,这里 \(i\)\(j\) 可以理解成两个数组下标差(偏移量),如果两个数初始偏移量为 \(dx\),每次偏移量均会增加 \(|n-m|\),设 \(n=k_1\text{gcd}(n,m),m=k_2\text{gcd}(n,m)\),进行 \(g\) 轮后的偏移量为 \(g\times |n-m|+dx=g|k_1-k_2|\text{gcd}(n,m)+dx\),可以发现都是在模 \(\text{gcd}(n,m)\) 的意义下同余的

因此可以拆开,将 \(i\mod \text{gcd}(n,m)\) 的数分为同一组,直接像上面互质的情况那样分别跑就行了

#include<bits/stdc++.h>
using namespace std;
#define int long long
int n,m;
int a[100001],b[100001];
vector<int>v[100001];
int ans[3];
int gcd;
signed main(){
    scanf("%lld %lld",&n,&m);
    gcd=__gcd(n,m);
    for(int i=1;i<=n;++i){
        scanf("%lld",&a[i]);
        v[i%gcd].push_back(a[i]);
    }
    for(int i=0;i<=gcd-1;++i) sort(v[i].begin(),v[i].end());
    for(int i=1;i<=m;++i){
        scanf("%lld",&b[i]);
        ans[0]+=tmp*gcd;ans[1]+=tmp2*gcd;ans[2]+=(v[i%gcd].size()-tmp-tmp2)*gcd;
    }
    cout<<ans[1]<<'\n'<<ans[0]<<'\n'<<ans[2]<<'\n';
}

C.比特跳跃

\(S=1\)

考虑怎么从 \(1\) 完全不花费跳到任意点

  • 如果目标点的二进制末尾是 \(0\),直接从 \(1\) 跳就不会有任何花费
  • 如果目标点的二进制末尾是 \(1\),那么将其取反,反串的二进制末尾是 \(0\),可以从 \(1\) 跳到其反串,再从反串调到目标点,均不需要花费

但是唯一的特例是形如 \(11111\cdots\) 的点(发现只有当 \(n=2^k\) 的时候,\(2^k-1\) 会出现这种情况,否则可以直接从高位上的点跳走),因为它的反串是 \(0\),而我们并没有 \(0\) 这个节点,此时我们可以采用下面两种方式跳到这个点

  • 直接从 \(1\) 跳过去
  • 不用花费地跳到相邻点,通过相邻的连边直接走到目标点

取最小值即可

\(S=2\)

关于异或,可以考虑拆位

  • 枚举 \(i\),每次我们只异或 \(i\) 的其中一位,并且将异或结果与 \(i\) 连边(因为是无向图,为了避免重复连边,可以只枚举 \(i\) 中为 \(1\) 的二进制位,实测不这么做会炸空间)
  • 直接跑最短路即可

\(S=3\)

  • 如果 \(i\) 不是 \(j\) 的子集,\(1\rightarrow i\rightarrow j\) 一定不比 \(1\rightarrow j\)

因为你从 \(1\)\(i\) 的贡献是 \(1\operatorname{or}i\)\(i\)\(j\) 的贡献是 \(i\operatorname{or}j\),这两个加和一定包含了 \(1\operatorname{or}j\),并且还会多出来点

因此这么做

  • 先跑一遍最短路
  • 对每个 \(i\) 枚举其子集,尝试松弛 \(i\)
  • 再跑一遍最短路

这个枚举子集的复杂度太大,具体实现的时候,建一个数组 \(f_i\) 表示 \(i\) 及其子集的最小 \(dis\),然后枚举和 \(i\) 只差一个二进制位,并且还是 \(i\) 的子集的数(这些数一定比 \(i\) 小,如果你从小到大枚举 \(i\) 就可以保证这些数一定先被更新过了),用这些数的最小值对 \(i\) 进行松弛,同时记得也用这些数更新 \(f_i\),这样才能实现我们枚举所有子集的效果

要注意的点

  • 第二遍最短路前先把要松弛的点入队(或者你懒得判断直接入所有点也可以)
  • 初值,\(f_1=0\)
#include<bits/stdc++.h>
using namespace std;
#define int long long
int n,m,s,k;
struct edge{
    int to,w;
};
vector<edge>e[200001];
int dis[200001];
bool vis[200001];
struct node{
    int id,dis;
    bool operator <(const node&A)const{
        return dis>A.dis;
    }
};
priority_queue<node>q;
namespace subtask1{
    void dij(int s){
        memset(dis,0x3f,sizeof dis);
        memset(vis,0,sizeof vis);
        dis[s]=0;
        q.push({s,dis[s]});
        while(!q.empty()){
            node u=q.top();q.pop();
            if(vis[u.id]) continue;
            vis[u.id]=true;
            for(edge i:e[u.id]){
                if(dis[i.to]>dis[u.id]+i.w){
                    dis[i.to]=dis[u.id]+i.w;
                    q.push({i.to,dis[i.to]});
                }
            }
        }
    }
    void main(){
        for(int i=1;i<=m;++i){
            int x,y,z;int st=scanf("%lld %lld %lld",&x,&y,&z);
            e[x].push_back({y,z});
            e[y].push_back({x,z});
        }
        for(int i=1;i<=n;++i){
            for(int j=i+1;j<=n;++j){
                if(s==1){
                    e[i].push_back({j,k*(i&j)});
                    e[j].push_back({i,k*(i&j)});
                }
                if(s==2){
                    e[i].push_back({j,k*(i^j)});
                    e[j].push_back({i,k*(i^j)});
                }
                if(s==3){
                    e[i].push_back({j,k*(i|j)});
                    e[j].push_back({i,k*(i|j)});
                }
            }
        }
        dij(1);
        for(int i=2;i<=n;++i){
            cout<<dis[i]<<" ";
        }
        cout<<endl;
    }
}
bool ispowerof2(int x){
    if(x==1) return true;
    if(x%2!=0) return false;
    return ispowerof2(x/2);
}
namespace subtask2{
    void main(){
        for(int i=2;i<=n;++i){
            cout<<0<<" ";
        }
        cout<<endl;
    }
}
namespace subtask3{
    void main(){
        for(int i=1;i<=m;++i){
            int x,y,z;int st=scanf("%lld %lld %lld",&x,&y,&z);
            e[x].push_back({y,z});
            e[y].push_back({x,z});
        }
        int minn=0x7fffffff;
        for(edge i:e[n]) minn=min(minn,i.w);
        for(int i=2;i<=n-1;++i){
            cout<<0<<" ";
        }
        cout<<min(minn,k)<<" ";
        cout<<endl;
    }
}
namespace subtask4{
    void dij(int s){
        memset(dis,0x3f,sizeof dis);
        memset(vis,0,sizeof vis);
        dis[s]=0;
        q.push({s,dis[s]});
        while(!q.empty()){
            node u=q.top();q.pop();
            if(vis[u.id]) continue;
            vis[u.id]=true;
            for(edge i:e[u.id]){
                if(dis[i.to]>dis[u.id]+i.w){
                    dis[i.to]=dis[u.id]+i.w;
                    q.push({i.to,dis[i.to]});
                }
            }
        }
    }
    void main(){
        for(int i=1;i<=m;++i){
            int x,y,z;scanf("%lld %lld %lld",&x,&y,&z);
            e[x].push_back({y,z});
            e[y].push_back({x,z});
        }
        for(int i=1;i<=n;++i){
            for(int j=0;j<=31;++j){
                if((i&(1ll<<j))){
                    e[i].push_back({i^(1ll<<j),k*(1ll<<j)});
                    e[i^(1ll<<j)].push_back({i,k*(1ll<<j)});
                }
            }
        }
        dij(1);
        for(int i=2;i<=n;++i){
            cout<<dis[i]<<" ";
        }
    }
}
namespace subtask5{
    int f[200001];
    void dij(int s){
        memset(dis,0x3f,sizeof dis);
        memset(vis,0,sizeof vis);
        dis[s]=0;
        q.push({s,dis[s]});
        while(!q.empty()){
            node u=q.top();q.pop();
            if(vis[u.id]) continue;
            vis[u.id]=true;
            for(edge i:e[u.id]){
                if(dis[i.to]>dis[u.id]+i.w){
                    dis[i.to]=dis[u.id]+i.w;
                    q.push({i.to,dis[i.to]});
                }
            }
        }
    }
    void dij2(int s){
        memset(vis,0,sizeof vis);
        dis[s]=0;
        q.push({s,dis[s]});
        while(!q.empty()){
            node u=q.top();q.pop();
            if(vis[u.id]) continue;
            vis[u.id]=true;
            for(edge i:e[u.id]){
                if(dis[i.to]>dis[u.id]+i.w){
                    dis[i.to]=dis[u.id]+i.w;
                    q.push({i.to,dis[i.to]});
                }
            }
        }
    }
    void main(){
        for(int i=1;i<=m;++i){
            int x,y,z;scanf("%lld %lld %lld",&x,&y,&z);
            e[x].push_back({y,z});
            e[y].push_back({x,z});
        }
        for(int i=2;i<=n;++i){
            e[1].push_back({i,k*(1ll|i)});
            e[i].push_back({1,k*(1ll|i)});
        }
        dij(1);
        memset(f,0x3f,sizeof f);
        f[1]=0;
        for(int i=2;i<=n;++i){
            for(int j=0;j<=31;++j){
                if((i&(1ll<<j))){
                    dis[i]=min(dis[i],f[i^(1ll<<j)]+k*i);
                    f[i]=min(dis[i],f[i^(1ll<<j)]);
                }
            }
        }
        for(int i=2;i<=n;++i){
            q.push({i,dis[i]});
        }
        dij2(1);
        for(int i=2;i<=n;++i){
            cout<<dis[i]<<" ";
        }
    }
}
signed main(){
    freopen("jump.in","r",stdin);
    freopen("jump.out","w",stdout);
    // freopen("sample/jump/ex6.in","r",stdin);
    // freopen("out.out","w",stdout);
    int st=scanf("%lld %lld %lld %lld",&n,&m,&s,&k);
    if(n<=800 and m<=800){
        subtask1::main();
        return 0;
    }
    if(s==1 and ispowerof2(n+1)==false){
        subtask2::main();
        return 0;
    }
    if(s==1){
        subtask3::main();
        return 0;
    }
    if(s==2){
        subtask4::main();
        return 0;
    }
    if(s==3){
        subtask5::main();
        return 0;
    }
}

这是什么

初音未来可爱捏

posted @ 2024-10-19 17:35  HaneDaniko  阅读(60)  评论(3编辑  收藏  举报