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=∣ai−bj∣+∣aj−bi∣−∣ai−bi∣−∣aj−bj∣
我们知道直线上两个区间有三种关系:相离、相交、包含
当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}
例如:置换a和b复合,给定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}
⎩
⎨
⎧x≡t1(modT1)x≡t2(modT2)x≡t3(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;
}