牛客多校 2023 补题

文章介绍了如何通过数学方法解决涉及操作次数优化的问题,如在限制条件下通过特定操作达到目标值的最少步骤。文章提供了MWater、HMatches和LThreePermutations三个问题的题解,涉及gcd计算、区间交换和置换复合等概念,展示了如何运用这些工具求解复杂问题。
摘要由CSDN通过智能技术生成

M Water

题目大意

有两个杯子容量分别为A,B,需要喝到x的水

有以下四个操作:

1、为A.B中的一个杯子装满水

2、倒出A、B中的一杯水

3、喝掉其中的一杯水

4、将一杯水倒入另一杯中

求最少的操作次数

题解

可以设rA + sB = x,求出r,s

当x不是gcd(A,B)的倍数时,无解

当r≥0,s≥0时,操作次数为2*(r+s),即接r次A,喝r次A,接s次B,喝s次B

当r * s <0时,假设r>0,s<0,操作次数为2*|r-s|-1,即接r次A,往B中倒s次,喝Ar次,将B中的水倒出s-1次,因为最后一次已经喝够x水,不用再将B中的水倒出

利用exgcd求出最靠近原点的r,s,然后进行枚举一些其它的解来求出ans

#include <iostream>
#include <cstring>
#include <algorithm>
#include <vector>
#include<cmath>
using namespace std;

typedef long long ll;
typedef pair<int, int> PII;
#define ll long long
const ll inf = 1e18;

ll exgcd(ll a,ll b,ll &x,ll &y)
{
    if(!b) {
        x = 1,y = 0;
        return a;
    }
    ll d = exgcd(b,a%b,y,x);
    y -= a/b*x;
    return d;
}

void slove()
{
    ll a,b,x;
    cin >> a >> b >> x;
    ll u,v,d;
    d = exgcd(a,b,u,v);
    if(x%d) {
        cout << -1 << endl;
        return ;
    }
    a /= d, b /= d, x /= d;
    u = (u*x%b+b)%b;//u*x 求出方程的解,然后再求出最小正整数的解
    v = (x-u*a)/b;//求出最小的U,由u算出v
    ll ans = inf;
    for(int i = -10;i<=10;i++)
    {
    	ll r = u+b*i,s = v-a*i;//枚举其它的解
    	if(r>=0&&s>=0) ans = min(ans,2*(r+s));
    	else ans = min(ans,2*abs(r-s)-1);
    }
    cout << ans << endl;
}

int main()
{
	int t;
    cin >> t;
    while(t--) slove();
	return 0;
}

H Matches

题目大意

给两个长度为n的数组a,b,可以将a数组中的两个数交换一下位置,求最小的abs(ai-bi)

题解

abs(ai-bi)可以看作是直线上ai点与bi点的距离
设两点 ( a i , b i ) , ( a j , b j ) 两点交换后做出的距离变化为 S = ∣ a i − b j ∣ + ∣ a j − b i ∣ − ∣ a i − b i ∣ − ∣ a j − b j ∣ 设两点(a_i,b_i),(a_j,b_j)两点交换后做出的距离变化为\\ S = |a_i-b_j|+|a_j-b_i|-|a_i-b_i|-|a_j-b_j| 设两点(ai,bi),(aj,bj)两点交换后做出的距离变化为S=aibj+ajbiaibiajbj
我们知道直线上两个区间有三种关系:相离、相交、包含

当ai<bi且aj<bj时

橙色代表交换前,红色代表交换后

相离:
很明显,交换后距离变大,不优

相交:在这里插入图片描述
很明显,交换后距离不变,不优

包含:在这里插入图片描述
很明显,交换后距离不变,不优

当ai>bi且aj<bj时

相离:
在这里插入图片描述
很明显,交换后距离变大,不优

相交:
在这里插入图片描述
交换后距离减少了abs(aj-ai)也就是两个区间的交集

包含:
在这里插入图片描述
交换后距离减少了abs(aj-bj)也就是两个区间的交集

综上所述,只有当两个区间中有一个区间的a<b且两个区间有交集的情况下交换才有意义

那么,我们可以找出满足上面的两个条件且交集最大的两个区间进行交换

#include<iostream>
#include<algorithm>
#include<cstring>
#include<vector>
using namespace std;
const int N=1000010;
long long a[N],b[N];

struct node
{
    long long l,r;
    int type;
    node(long long l1,long long r1)
    {
        l=min(l1,r1);
        r=max(l1,r1);
        type=l1>r1;
    }
    bool operator<(const node &h)const
    {   return r<h.r;
    }
    
};
const long long INF=1e18;
vector<node> p;

int main()
{
    int n;
    cin>>n;
    long long sum=0;
    for(int i=0;i<n;i++)
        cin>>a[i];
    for(int i=0;i<n;i++)
    {
    	cin >> b[i];
    	p.push_back({a[i],b[i]});
    	sum += abs(a[i]-b[i]);
    }
    sort(p.begin(),p.end());//按照右端点递减排序
    long long res=0;
    long long l[2]={INF,INF};//维护当前类内最小左端点位置
    for(int i=n-1;i>=0;i--)
    {
        int ditpedef=p[i].type^1;//找到反向区间
        if(l[ditpedef]<p[i].r)
            res=max(res,p[i].r-max(p[i].l,l[ditpedef]));
        //注意,这里是由大到小枚举右端点
        l[p[i].type]=min(p[i].l,l[p[i].type]);  
    }
    
    cout<<sum-2*res<<endl;
    return 0;
}

L Three Permutations

题目大意

给a,b,c三个长度为n的数组以及x,y,z初始值为1的整数,每一秒后,x,y,z会变成ay,bz,cx

问,x,y,z从初始值变为x0,y0,z0需要的最少时间

题解

前置知识:

置换:一个置换是一个数n的全排列,不断重复x->fx的操作,从任意元素i出发,一定可以回到原始的i,这个操作次数成为i的周期

置换可以复合,复合后仍然是置换;
例如:置换 a 和 b 复合,给定 i 返回 a b i 例如:置换a和b复合,给定i返回a_{b_i} 例如:置换ab复合,给定i返回abi
对于此题,我们可以将a,b,c三个置换复合成一个大置换fx,可以发现,每过3秒,会出现x->fx

对于x,y,z可以分别找到首次从1到x0,y0,z0的时间t,以及它的周期T

得到一个方程组,枚举初值可以分别用 excrt 计算在3t,3t+1,3t+2秒时变成x0,y0,z0的最短时间,取最小值
{ x ≡ t 1 ( m o d T 1 ) x ≡ t 2 ( m o d T 2 ) x ≡ t 3 ( m o d T 3 ) \begin{cases} x ≡ t_1(mod\quad T_1) \\ x ≡ t_2(mod\quad T_2) \\ x ≡ t_3(mod\quad T_3) \\ \end{cases} xt1(modT1)xt2(modT2)xt3(modT3)

#include<iostream>
#include<algorithm>
#include<cstring>
#include<vector>
using namespace std;
#define ll long long
const ll inf = 1e18;
typedef pair<ll, ll> pll;

ll exgcd(ll a, ll b, ll &x, ll &y)
{
    if (!b)
    {
        x = 1, y = 0;
        return a;
    }
    ll d = exgcd(b, a % b, y, x);
    y -= a / b * x;
    return d;
}

pll excrt(pll l, pll r) {
    auto[r1, m1] = l;
    auto[r2, m2] = r;
    if (r1 == -1 || r2 == -1) return {-1, -1};
    ll d, l1, l2;
    d = exgcd(m1, m2, l1, l2);
    if ((r2 - r1) % d) return {-1, -1};
    ll L = m1 * m2 / d;
    ll R = ((r1 + (r2 - r1) / d * l1 % L * m1) % L + L) % L;
    return {R, L};
}

int main() {
    ll n;
    cin >> n;
    vector<ll> a(n + 1), b(n + 1), c(n + 1);
    vector<ll> ia(n + 1), ib(n + 1), ic(n + 1);
    for (int i = 1; i <= n; ++i) cin >> a[i], ia[a[i]] = i;
    for (int i = 1; i <= n; ++i) cin >> b[i], ib[b[i]] = i;
    for (int i = 1; i <= n; ++i) cin >> c[i], ic[c[i]] = i;

    // 计算三个置换
    vector<ll> abc(n + 1), bca(n + 1), cab(n + 1);
    for (int i = 1; i <= n; ++i) abc[i] = a[b[c[i]]];
    for (int i = 1; i <= n; ++i) bca[i] = b[c[a[i]]];
    for (int i = 1; i <= n; ++i) cab[i] = c[a[b[i]]];
    ll lena = 0, lenb = 0, lenc = 0;
    // 计算到每个点的时间距离+周期
    vector<ll> disa(n + 1, -1), disb(n + 1, -1), disc(n + 1, -1);
    for (ll u = 1; disa[u] == -1; u = abc[u], ++lena) disa[u] = lena;
    for (ll u = 1; disb[u] == -1; u = bca[u], ++lenb) disb[u] = lenb;
    for (ll u = 1; disc[u] == -1; u = cab[u], ++lenc) disc[u] = lenc;
    // for(ll i=1;;)
    // {
    	// if(disa[u]!=-1) break;表示第二次到某个数
    	// disa[u] = leana;到每个数的距离
    	// u = abc[u];x->fx
    	// lena++;
    // }

    // EXCRT
    auto solve = [&](ll x, ll y, ll z) -> ll {
        if (disa[x] == -1 || disb[y] == -1 || disc[z] == -1) return inf;
        pll A(disa[x], lena);
        pll B(disb[y], lenb);
        pll C(disc[z], lenc);
        A = excrt(A, excrt(B, C));
        return A.first == -1 ? inf : A.first;
    };

    int q;
    cin >> q;
    while (q--) {
        ll x, y, z;
        cin >> x >> y >> z;
        ll m0 = solve(x, y, z);
        ll m1 = solve(ic[z], ia[x], ib[y]);
        ll m2 = solve(ic[ib[y]], ia[ic[z]], ib[ia[x]]);
        ll ans = min({m0 * 3, m1 * 3 + 1, m2 * 3 + 2});
        printf("%lld\n", ans >= inf ? -1 : ans);
    }
    return 0;
}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值