2 条题解

  • 0
    @ 2026-7-19 11:32:19

    P14981 [USACO26JAN1] Milk Buckets G 题解

    思路

    注意到每次合并相当于将合并的两个数对答案的贡献 ×12\times\frac{1}{2},所以合并顺序一定是从小到大。

    注意到当序列为单谷序列时符合合并条件,问题转化为将序列变为单谷的最小操作次数。

    注意到对于一个数,要么将他移到序列谷的左侧,要么移到右侧,移动的代价即为两侧比它小的数的数量中的较小值。

    所以只需要离散化后用树状数组统计一下就好了。

    代码

    #include<bits/stdc++.h>
    using namespace std;
    typedef long long ll;
    ll t,n,a[200010],lsh[200010],L[200010],R[200010],ln;
    int lowbit(int x){
    	return x&(-x);
    }
    struct TR{
    	ll tr[200010];
    	void add(int x,int v){
    		for(int i=x;i<=ln;i+=lowbit(i)){
    			tr[i]+=v;
    		}
    	}
    	ll find(int x){
    		ll ans=0;
    		for(int i=x;i;i-=lowbit(i)){
    			ans+=tr[i];
    		}
    		return ans;
    	}
    }tr;
    int main(){
    	ios::sync_with_stdio(0);
    	cin.tie(0);
    	cin>>t;
    	while(t--){
    		cin>>n;
    		for(int i=1;i<=n;i++){
    			cin>>a[i];
    			lsh[i]=a[i];
    		}
    		sort(lsh+1,lsh+1+n);
    		ln=unique(lsh+1,lsh+1+n)-lsh-1;
    		for(int i=1;i<=n;i++){
    			a[i]=lower_bound(lsh+1,lsh+1+ln,a[i])-lsh;
    		}
    		for(int i=1;i<=ln;i++)tr.tr[i]=0;
    		for(int i=1;i<=n;i++){
    			L[i]=tr.find(a[i]-1);
    			tr.add(a[i],1);
    		}
    		for(int i=1;i<=ln;i++)tr.tr[i]=0;
    		for(int i=n;i;i--){
    			R[i]=tr.find(a[i]-1);
    			tr.add(a[i],1);
    		}
    		ll ans=0;;
    		for(int i=1;i<=n;i++)ans+=min(L[i],R[i]);
    		cout<<ans<<'\n';
    	}
    	return 0;
    }
    
    • 0
      @ 2026-4-28 11:37:56

      先考虑如何让最终权值最大。将合并视作两个桶的贡献系数乘以 12\frac 12,则最终答案可以理解成 aici\sum a_ic_i,其中 ci=1\sum c_i=1,且 cic_i 形如 12l\frac 1{2^l}。尝试将 cic_i 乘以 22,则其他 cc 一共需要有恰好 cic_i 的减量。此时,若产生减量对应的 aa 均小于 aia_i,最终答案一定不降。于是可以发现按 aia_i 从大到小将 cic_i 赋值为 12,14,18,\frac 12,\frac 14,\frac 18,\ldots 可以得到最优解。

      这样的 cc 如何体现在合并中?不难发现就是从最小值逐渐与更大的值合并,这要求前 xx 小放在一起,也即最终的 aa 是单谷的。

      现在变成了另一个问题,如何用最少交换次数让 aa 变成单谷的。从谷两端入手,从大到小遍历 aa 中的相同值,找到其下标分别为 p1<p2<<pmp_1<p_2<\cdots<p_m,此时需要找到一个分界点 qq,使得 qq 左边移动到最左端,右边移动到最右端的总操作数,即 $\displaystyle\sum_{i=1}^q p_i-i+\sum_{i=c+1}^mn-p_i-(m-i)$(假设当前左右端仍为 1,n1,n)最小,这两个求和可以前缀和完成。可以通过树状数组维护当前相对下标来找到 pp

      最终时间复杂度 O(nlogn)\mathcal{O}(n\log n)

      :::info[code]

      #include<bits/stdc++.h>
      #define pb emplace_back
      #define pob pop_back
      #define mp make_pair
      using namespace std;
      typedef long long ll;
      const ll maxn=200007,ee=1e18,p=998244353;
      ll n,a[maxn],ans,id[maxn],s1[maxn],s2[maxn];
      struct Tree{
          ll val[maxn];
          void init(ll n){fill(val+1,val+1+n,0);}
          void add(ll x,ll k){for(;x<=n;x+=x&(-x)) val[x]+=k;}
          ll ask(ll x){ll E=0; for(;x;x-=x&(-x)) E+=val[x]; return E;}
      }tree;
      int main(void){
          //freopen("data.in","r",stdin);
          //freopen("data.out","w",stdout);
          ios::sync_with_stdio(0),cin.tie(0);
          ll T=1; cin>>T;
          for(;T--;){
              cin>>n,ans=0;
              for(ll i=1;i<=n;i++) cin>>a[i],id[i]=i;
              sort(id+1,id+1+n,[&](ll x,ll y){
                  if(a[x]!=a[y]) return a[x]>a[y];
                  else return x<y;
              });
              tree.init(n);
              for(ll i=1;i<=n;i++) tree.add(i,1);
              for(ll l=1,r,x,mx;l<=n;l=r+1){
                  for(r=l;r<=n&&a[id[l]]==a[id[r]];r++);
                  r--,s1[l-1]=s2[l-1]=0;
                  for(ll i=l;i<=r;i++){
                      x=tree.ask(id[i]);
                      s1[i]=s1[i-1]+x-(i-l+1);
                      s2[i]=s2[i-1]+(n-l+1)-x-(r-i);
                  }
                  mx=s2[r];
                  for(ll i=l;i<=r;i++){
                      mx=min(mx,s1[i]+s2[r]-s2[i]);
                      tree.add(id[i],-1);
                  }
                  ans+=mx;
              }
              cout<<ans<<"\n";
          }
          return 0;
      }
      

      :::

      • 1

      信息

      ID
      2290
      时间
      2000ms
      内存
      256MiB
      难度
      8
      标签
      递交数
      15
      已通过
      5
      上传者