OI 笑传 #7

T1

给定一个长度为 \(n\) 的整数序列 \(a_1, a_2, \ldots, a_n\)。定义序列的代价为所有不同下标元素对的差的绝对值之和:

\[\sum_{1 \leq i < j \leq n} |a_i - a_j| \]

你可以执行以下操作最多 \(n\) 次:

  • 选择一个整数 \(V\),并将序列中每个元素 \(a_i\) 修改为 \(|a_i - V|\)

目标是找到若干次操作后序列代价的最小值。由于答案可能很大,请输出该最小值对 \(998244353\) 取模的结果。

\(1 \le n \le 10^6,0\le a_i \le 10^9\)

小思维。

想了一会想到了可以把元素排序后变成一个三角形图考虑,直觉上找最大和最小数的平均值去减是最好的,因为这样会让这个三角形变成一个两边高中间低的形式,三角形的高至少减半。

于是不停减半就好了啊,只需要 \(O(\log \max V)\) 次,模拟这么多次上述过程就好了。

最后所有数一定会变成 \(0\)\(1\),统计答案乘法原理即可。

code

Show me the code
#define psb push_back
#define mkp make_pair
#define ls p<<1
#define rs (p<<1)+1
#define rep(i,a,b) for( int i=(a); i<=(b); ++i)
#define per(i,a,b) for( int i=(a); i>=(b); --i)
#define rd read()
#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
#define int long long
ll read(){
  ll x=0,f=1;
  char c=getchar();
  while(c>'9'||c<'0'){if(c=='-') f=-1;c=getchar();}
  while(c>='0'&&c<='9'){x=(x<<3)+(x<<1)+(c^48);c=getchar();}
  return x*f;
}
vector<int> v;
signed main(){
  
  int n;cin>>n;
  for(int i=1;i<=n;i++){
    int x;cin>>x;
    v.push_back(x);
  }
  for(int i=1;i<=31;i++){
    sort(v.begin(),v.end());
    int mid=v.front()+v.back()>>1;
    for(int j=0;j<v.size();j++){v[j]=llabs(v[j]-mid);}
  }
  int cnt1=0,cnt0=0;
  int ans=0;
  for(int i=0;i<v.size();i++){
    for(int j=i+1;j<v.size();j++){
      ans+=llabs(v[i]-v[j]);
    }
  }
  cout<<ans;
  
  return 0;
}

T2

给定 \(a,b,c,d\),求解 \(\gcd(a^b,c^d)\) 的值,对 \(p=998244353\) 取模。

\(1\le a,b,c,d \le 10^{18}\),你需要回答 \(T \le 10^5\) 次这样的询问。

神秘一句话题。

首先当然不能 \(\gcd(a^b \bmod p,c^d \bmod p)\),考虑质因数分解意义下的 \(\gcd\),当然不能直接分解,考虑构造一些数拼起来。

定义函数 \(\operatorname{solve}(a,b,c,d)\) 表示计算 \(\gcd(a^b,c^d)\) 的值,钦定 \(b\le d\),接下来令 \(k=\gcd(a,c),x=\frac{a}{k},y=\frac{c}{k}\) 那么当然有 \(a=kx,b=ky\)

把式子改写下:

\[\gcd(a^b,c^d)=\gcd(x^bk^b,y^dk^d) \]

拎出一个 \(k^b\) 来:

\[\gcd(x^bk^b,y^dk^d)=k^b\gcd(x^b,y^dk^{d-b}) \]

此时考虑 \(x,y\),因为两数是 \(a,b\) 除以其最大公约数的商,可以得出 \(x,y\) 一定没有相同的质因子,证明反证即可。

于是你会发现在式子 \(\gcd(x^b,y^dk^{d-b})\) 中,\(y^d\) 这一项不会对这个式子造成任何贡献。

于是你也可以把它拿掉,式子就变成了:

\[\gcd(x^b,k^{d-b}) \]

又规约成了 \(\gcd(a^b,c^d)\) 的形式,可以递归求解了。

中止条件是当 \(k=1\) 的时候,此时显然 \(\gcd(x^b,y^dk^{d-b})=1\),返回即可。

这里同时可以证明复杂度,由于 \(k\ge 2\)\(a,c\) 每次至少减半,就是 \(\log(\min(a,c))\) 的时间复杂度。

代码好写的没边,这里用 wex 的代码:

code

Show me the code
#include<bits/stdc++.h>
#define int unsigned long long
using namespace std;
const int N=2e5+3;
const int mod=998244353;
int gcd(int a,int b){
    return b==0?a:gcd(b,a%b);
}
int qmi(int a,int b){
    int res=1;
    a%=mod;
    while(b){
        if(b%2==1){
            res=(res*a)%mod;
        }
        a=(a*a)%mod;
        b>>=1;
    }
    return res;
}

int solve(int a,int b,int c,int d){
    int k=gcd(a,c);
    if(k==1)return 1;
    if(b>d){
        swap(a,c);
        swap(b,d);
    }

    int x=a/k,y=c/k;
    return solve(x,b,k,d-b)%mod*qmi(k,b)%mod;
}

signed main(){

    int t;
    scanf("%lld",&t);
    while(t--){
        int a,b,c,d;
        scanf("%lld%lld%lld%lld",&a,&b,&c,&d);
        printf("%lld\n",solve(a,b,c,d)%mod);    
    }
    
    return 0;
}

T3

给定 \(n\) 个同学编号 \(1\)\(n\),初始按编号顺序坐成一个圈。每个同学 \(i\) 有两个最希望相邻的同学 \(x_i\)\(y_i\)。现需通过若干次命令调整位置形成新圈,使得新圈中每个同学的左右邻居恰好是其希望的同学(顺序不限)。

单次命令形式为 \((b_1, b_2, \ldots, b_m)\),其中 \(m\) 由命令制定者决定。

执行效果:\(b_1 \to b_2\) 的位置,\(b_2 \to b_3\) 的位置,\(\ldots\)\(b_m \to b_1\) 的位置(循环移位)。

该命令的代价为 \(m\)

求达成目标圈所需的最小总代价(即所有命令代价之和)。

又是个结论题啊,画了一纸的环发现好像差几个位置总是可以用位置个数的代价调整对,问题变成了怎么放环让差的位置最小。

这个就好做一些,考虑强制让构造出的排列和目标排列,也就是 \(1,2,3,\cdots n\)\(1\) 所在位置对齐,然后从旋转目标排列的角度考虑。设构造的排列的第 \(i\) 个位置的元素为 \(p_i\)。若 \(p_i>i\),则目标排列需要向左转 \(p_i-i\) 个单位才能与这个位置对齐,如果 \(i>p_i\),那么目标排列需要向右转 \(i-p_i\) 个单位才能与这个位置对齐。

为了统一我们规定右为正方向,也就是若 \(p_i>i\),则目标排列需要向右转 \(i-p_i+n\) 个单位才能与这个位置对齐。

我们当然想要让对齐的位置尽量多,于是找到在旋转次数一定时,对齐元素最多的旋转次数 \(k\),于是答案是 \(n-k\)

不合法的条件就是这些人没法按照愿望做成一个环。

当然建出环以后你可以从 \(1\) 开始向左右分别转一圈构造出两个排列,要将这两种排列的答案取个 \(\min\)

code

Show me the code
#define psb push_back
#define mkp make_pair
#define ls p<<1
#define rs (p<<1)+1
#define rep(i,a,b) for( int i=(a); i<=(b); ++i)
#define per(i,a,b) for( int i=(a); i>=(b); --i)
#define rd read()
#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
ll read(){
  ll x=0,f=1;
  char c=getchar();
  while(c>'9'||c<'0'){if(c=='-') f=-1;c=getchar();}
  while(c>='0'&&c<='9'){x=(x<<3)+(x<<1)+(c^48);c=getchar();}
  return x*f;
}
const int N=1e5+5;
vector<int> e[N];
int ind[N],oud[N];
bitset<N> vis;
vector<int> p;
void dfs1(int u,int fa){
  vis[u]=1;
  p.push_back(u);
  for(int v:e[u]){
    if(v==fa||vis[v])continue;
    dfs1(v,u);
  }
}
void dfs2(int u,int fa){
  vis[u]=1;
  bool f=0;
  p.push_back(u);
  for(int v:e[u]){
    if(v==fa||vis[v])continue;
    if(u==1&&!f){
      f=1;continue;
    }
    if(u==1&&f)dfs2(v,u);
    if(u!=1)dfs2(v,u);
  }
}
int pr[N];
int dis[N];
int a[N],b[N];
int main(){
  
  int n;cin>>n;
  for(int i=1;i<=n;i++){
    int u,v;cin>>u>>v;
    a[i]=u;b[i]=v;
    e[i].push_back(u);
    e[i].push_back(v);
    ind[u]++;ind[v]++;
    oud[i]++;oud[i]++;
  }
  for(int i=1;i<=n;i++){
    if((a[a[i]]!=i&&b[a[i]]!=i)||(a[b[i]]!=i&&b[b[i]]!=i)){
      cout<< -1;
      return 0;
    }
  }
  for(int i=1;i<=n;i++){
    if(ind[i]!=2||oud[i]!=2){
      cout<< -1;
      return 0;
    }
  }
  int ans=0x3f3f3f3f;
  dfs1(1,0);
  for(int i=1;i<=n;i++){
    if(!vis[i]){
      cout<< -1;
      return 0;
    }
  }
  int mr=0;
  int mp=0;
  for(int i=1;i<=n;i++){
    pr[i]=p[i-1];
    int del=pr[i]-i;
    if(del<0)del=del+n;
    dis[del]++;
    if(dis[del]>mr){
      mr=dis[del];
    }
  }
  ans=min(ans,n-mr);
  memset(pr,0,sizeof pr);
  memset(dis,0,sizeof dis);
  p.clear();vis.reset();
  dfs2(1,0);
  for(int i=1;i<=n;i++){
    pr[i]=p[i-1];
    int del=pr[i]-i;
    if(del<0)del=del+n;
    dis[del]++;
    if(dis[del]>mr){
      mr=dis[del];
    }
  }
  ans=min(ans,n-mr);
  cout<<ans;
  
  return 0;
}
posted @ 2025-08-06 17:43  hm2ns  阅读(15)  评论(0)    收藏  举报