T1:


分析:
写出s变换的式子:(((s+a)*b+a)*b)……
将式子化简:
又可以把m写成:
也就是将m拆成一个b进制数,每次贪心地使 i 大的时候xi尽量大,那么就可以花费最小次数凑出m。

#include<bits/stdc++.h>
using namespace std;
#define ri register int
#define ll long long
int main()
{
freopen("a.in","r",stdin);
freopen("a.out","w",stdout);
ll S,T,a,b;
scanf("%lld%lld%lld%lld",&S,&T,&a,&b);
ll nowb=1,n=0,ans=(1ll<<62);
while(1){
ll t=T-nowb*S;
if(t<0) break;
if(t%a==0){
ll tot=n,m=t/a;
ll bb=nowb;
while(m){//把m拆成b进制数
tot+=m/bb;
m%=bb;
bb/=b;
}
ans=min(ans,tot);
}
n++; nowb=nowb*b;
}
printf("%d\n",ans==(1ll<<62) ? -1 : ans);
}
/*
10 28 4 2
4685 56756 32764 43856
6 207 5 3
10000 100000000 3 5
179 222963 59 384
*/
T2:


分析:
很显然可以二分一个答案,考虑怎么check。
首先将不满足条件的提出来,对于长的边,想办法将其缩短,但有一种错误的想法:对不满足的点对取交,然后将点对设在交集处。
为什么是错的呢?
比如下面这一组数据:
1 2 3 4 5 6 7
2 7
3 6
如果按照刚刚那样做的话,会将点对设在3 6处(交集),输出2
而正确的设法应该在2 6设,3可以先走到2再走到6,输出1
下面考虑正解:
设最终设定的两个点为u,v。
任意一个点对x,y的贡献是这两种间取min:1. y-x 2. abs(x-u)+abs(y-v)
然后答案就是所有的取max
我们已经二分了一个mid,对于前一种小于等于mid的可以直接跳过,但后一种未知u,v,所以应该怎么处理呢?
abs(x-u)+abs(y-v)<=mid 是曼哈顿距离的形式。
可以将(x,y)看做平面上的一个点,所有不超过mid的范围即平面上的一个菱形,只需要对每一个点对做出一个菱形后求交即可。
但菱形不好求交,转换成正方形:坐标这样转换(x,y)->(x-y,x+y)
为什么呢?相当于将菱形的四条边向左边延长
像这样:

可以看到有两个解析式,将其移项即可知道x,y的转换关系了。
那为什么不考虑b和c分别是什么呢?
因为只需要一种映射关系,判断他们能不能有交就可以了,具体交是多少不需要知道。
然后用lx和rx分别记录一下交的范围,判断是否不符合的都有交即可。

#include<bits/stdc++.h>
using namespace std;
#define N 100005
#define ri register int
#define inf (1<<30)
int L[N],R[N],n,m;
struct node { int l,r; };
void work(node &x)
{
int ll=x.l,rr=x.r;
x.l=ll-rr; x.r=ll+rr;//将菱形转换成正方形
}
bool check(int mid)
{
int lx=-inf,rx=inf,ly=-inf,ry=inf;
for(ri i=1;i<=m;++i){
if(R[i]-L[i]<=mid) continue;
node x=(node){L[i]+mid,R[i]};
node y=(node){L[i]-mid,R[i]};
work(x); work(y);
lx=max(lx,y.l),rx=min(rx,x.l);//求交 lx rx分别表示x轴上的有交的范围 ly ry同理
ly=max(ly,y.r),ry=min(ry,x.r);
if(lx>rx || ly>ry) return false;
}
return true;
}
int main()
{
freopen("b.in","r",stdin);
freopen("b.out","w",stdout);
scanf("%d%d",&n,&m);
int mx=0;
for(ri i=1;i<=m;++i) scanf("%d%d",&L[i],&R[i]),mx=max(R[i]-L[i],mx);
int l=0,r=mx+1,ans=inf;
while(l<r){
int mid=(l+r)>>1;
if(check(mid)) r=mid,ans=min(ans,mid);
else l=mid+1;
}
printf("%d\n",ans);
}
/*
6 2
1 5
2 6
9 4
1 4
1 8
5 6
4 8
5 17
1 4
1 1
1 1
1 3
3 4
3 4
1 4
2 2
1 4
2 3
2 4
1 3
3 4
1 3
1 2
1 4
1 4
*/
T3:


分析:
有一个很重要的性质:

有了这个性质之后直接二分一个k,先让小B交换完k轮,小A再去交换,看总次数是否小于等于k即可。
任意交换数组中两个元素,使得有序的最小交换次数 求法:
4 3 1 2 5
先让4和本该属于1位置的1交换,用mp记录1的位置,直接交换即可。
注意交换后mp[4]的值会被改变!!

#include<bits/stdc++.h>
using namespace std;
#define ri register int
#define N 400005
int a[N],s[N],x[N],y[N],n,mp[N],vis[N];
bool check(int k)
{
memcpy(a,s,sizeof(s));
memset(vis,0,sizeof(vis));
for(ri i=1;i<=k;++i) swap(a[x[i]],a[y[i]]);
int cnt=0,i=0;
for(ri i=0;i<n;++i) mp[a[i]]=i;
for(ri i=0;i<n;++i)
if(a[i]!=i){//每次找到一个位置不符合的 把后面应该属于这个位置的交换过来
int xx=a[i],yy=a[mp[i]];
swap(a[i],a[mp[i]]),cnt++;
mp[xx]=mp[i];//!!!注意mp也换了位置!!
}
return cnt<=k;
}
int main()
{
freopen("c.in","r",stdin);
freopen("c.out","w",stdout);
scanf("%d",&n);
for(ri i=0;i<n;++i) scanf("%d",&s[i]);
for(ri i=1;i<=n*2;++i) scanf("%d%d",&x[i],&y[i]);
int l=0,r=2*n+1,ans;
while(l<r){
int mid=(l+r)>>1;
if(check(mid)) ans=mid,r=mid;
else l=mid+1;
}
printf("%d\n",ans);
}
/*
3
1 0 2
1 2
0 2
0 0
0 1
0 2
1 2
*/
