机房测试:A(数学)+B(二分+曼哈顿距离)+C(性质+二分)

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
",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

*/
View Code

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
",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
*/
View Code

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
",ans);
}
/*
3
1 0 2
1 2
0 2
0 0
0 1
0 2
1 2
*/
View Code
原文地址:https://www.cnblogs.com/mowanying/p/11800837.html