算法竞赛进阶指南0x00-算法基础

0x01 位运算

位移运算

\(1 << n = 2^n , n << 1 = n*2 , n >> 1 = n/2\)(向下取整)

二进制应用

1.快速幂: a^b ,
Raising Modulo Numbers
二分乘法: 64位整数乘法

各种位运算符
2.状态压缩:最短Hamilton路径
题目大意:求起点位0,终点为n-1的最短哈密图路线。(Hamilton路径的定义是从 0 到 n-1 不重不漏地经过每个点恰好一次。) (n<=20)

思路:状压dp。(对于数据是15~20的题,很大概率是状压dp)dp[state][j]表示state的二进制表示下,每一位如果是1表示走过了这个点,j表示当前状态停在了哪个点,此状态下的最短路径。
状态转移方程:\(dp[state][j]=min(dp[state][j],dp[i\)^\((1\)<<\(j)][k]+g[j][k])\), (\(i\&(1\)<<\(k)==1\),\(i\&(1\)<<\(k)==1\))。即从没有到过j时在k的状态到j可以得到到过j,k最后停在k的最优状态。

#include<bits/stdc++.h>
using namespace std;
long long dp[(1<<20)][20];
int g[20][20];
int main(){
    int n;
    cin>>n;
    memset(dp,0x3f,sizeof dp);
    for(int i=0;i<n;++i){
        for(int j=0;j<n;++j){
            cin>>g[i][j];
        }
    }
    dp[1][0]=0;
    for(int i=1;i<(1<<n);++i){
        for(int j=0;j<n;++j){
            if(i&(1<<j)){
                for(int k=0;k<n;++k){
                    if(j==k) continue;
                    if(i&(1<<k)){
                       dp[i][j]=min(dp[i][j],dp[i^(1<<j)][k]+g[j][k]);
                    }
                }
            }
        }
    }
    cout<<dp[(1<<n)-1][n-1]<<endl;
    return 0;
}

0x02 递归与递推

递归实现指数型枚举
递归实现组合型枚举
递归实现排列型枚举

奇怪的汉诺塔

题目描述:

汉诺塔问题,条件如下:
1、这里有A、B、C和D四座塔。
2、这里有n个圆盘,n的数量是恒定的。
3、每个圆盘的尺寸都不相同。
4、所有的圆盘在开始时都堆叠在塔A上,且圆盘尺寸从塔顶到塔底逐渐增大。
5、我们需要将所有的圆盘都从塔A转移到塔D上。
6、每次可以移动一个圆盘,当塔为空塔或者塔顶圆盘尺寸大于被移动圆盘时,可将圆盘移至这座塔上。
请你求出将所有圆盘从塔A移动到塔D,所需的最小移动次数是多少。(1≤n≤12)

思路:我们可以这样想一开始A上有n个盘子。先把A上面的i个盘子移到B上,再利用A,C,D把A下面的n-i个盘子移动到D上,之后D上的盘子就固定了。然后的任务就是B上的i个移动到D上,同理我们移动上面的j个到A上,再将B下面i-j个移动到D上...我们发现这是一个连带三个汉诺塔的递归,我们知道三个柱子汉诺塔移动n个盘子的次数是2^n-1。然后就是如何选取这i,j..个盘子,设n个盘子的答案是\(f[n],f[n]=min(2*f[i]+2\)\(n-i\)\(-1)(0<i<k)\),我们已知1,2的f等于1,3那么后面的就可以递推出来啦。

#include<bits/stdc++.h>
using namespace std;
int cnt=0;
int f[20];
int main(){
    int n;
    f[1]=1;f[2]=3;
    for(int k=3;k<=12;++k){
        f[k]=10000000;
        for(int i=1;i<k;++i){
            int t=2*f[i]+pow(2,k-i)-1;
            f[k]=min(f[k],t);
        }
    }
    for(int i=1;i<=12;++i){
        cout<<f[i]<<endl;
    }
    return 0;
}

约数之和

题目描述:
假设现在有两个自然数A和B,S是AB的所有约数之和。请你求出S mod 9901的值是多少。
输入格式
在一行中输入用空格隔开的两个整数A和B。
输出格式
输出一个整数,代表\(S mod 9901\)的值。
数据范围:\(0≤A,B≤5×10^7\)

思路:这道题放在这是考察分治求等比数列和的。还需要知道约束和公式,a可以分解质因子为: \(a=p_1\)i\(×p_2\)j\(×...×p_n\)k.
a的约数和等于:\((1+a_1+...+a_1\)i\()×(1+a_2+...+a_2\)j\()×...×(1+a_n+...+a_n\)k\()\)
可以发现每一项都是一个以1为首相a为公比的等比数列前i+1项和。
等比数列有两种求法:

1.分治

偶数项的等比数列可表示为,\(sum(p,k)\)
k为奇数,那么就有偶数项:
\(1+a^1+a^2+...+a^k=(1+a\)(k+1)/2\()×sum(p,(k-1)/2)\)
k为偶数,那么就有奇数项:
\(1+a^1+a^2+...+a^k=(1+a\)k/2\()×sum(p,k/2-1)+p^k\)
可以容易推出偶数项的情况,而奇数项为项-1的偶数项加上\(p^k\)

2.快速幂+逆元

\(sum(p,k)=(qp(p,k)-1)*inv(p-1,mod)\)
需要注意一点就是p-1和mod不互质时,那么含p的所有约数与mod模都是1,所有答案就是项的个数k+1

#include<bits/stdc++.h>
using namespace std;
#define int long long
const int mod=9901;
using namespace std;
int p[100];
int num[100];
typedef long long LL;
void ex_gcd(LL a,LL b,LL &x,LL &y,LL &d){
	if(b==0){
		d=a;
		x=1;y=0;
		return ;
	}
	ex_gcd(b,a%b,y,x,d);
	y-=x*(a/b);
}
LL inv(LL t,LL p){  //除数t关于模数p的逆元
	LL d,x,y;
	ex_gcd(t,p,x,y,d);
	return d==1 ? (x%p+p)%p : -1;//使逆元非负
}
int qp(int a,int b){
    int res=1;
    while(b){
        if(b&1){
            (res*=a)%=mod;
        }
        a=a*a%mod;
        b>>=1;
    }
    return res;
}

int f(int x){//快速幂+逆元
    if(num[x]%mod==1){
        return (p[x])%mod;
    }
    else
    return ((qp(num[x],p[x]+mod-1)-1+mod)%mod*inv(num[x]-1,mod)%mod)%mod;
}
int f(int p,int c){//分治
    if(c==0) return 1;
    if(c==1) return (1+p)%mod;
    if(c%2==1){
        return (1+qp(p,(c+1)/2)%mod)%mod*f(p,(c-1)/2)%mod;
    }
    if(c%2==0){
        return ((1+qp(p,c/2))%mod*f(p,c/2-1)%mod+qp(p,c)%mod)%mod;
    }
}
signed  main(){
    int a,b,cnt=0;
    cin>>a>>b;
    b%=mod-1;
    if(a==0){
        cout<<"0"<<endl;
        return 0;
    }
    for(int i=2;i*i<=a;++i){
        if(a%i==0){
            ++cnt;
            while(a%i==0){
                p[cnt]++;
                a/=i;
            }
            (p[cnt]*=b);
            (p[cnt]+=1)%=(mod-1);
            num[cnt]=i;
        }
    }
    if(a>1){
        p[++cnt]=b+1;
        num[cnt]=a;
    }
    int res=1;
    for(int i=1;i<=cnt;++i){
        (res*=f(i))%=mod;
    }
    cout<<res<<endl;
    return 0;
}

分形之城

城市的规划在城市建设中是个大问题。
不幸的是,很多城市在开始建设的时候并没有很好的规划,城市规模扩大之后规划不合理的问题就开始显现。
而这座名为 Fractal的城市设想了这样的一个规划方案,如下图所示:
当城区规模扩大之后,Fractal的解决方案是把和原来城区结构一样的区域按照图中的方式建设在城市周围,提升城市的等级。
对于任意等级的城市,我们把正方形街区从左上角开始按照道路标号。
虽然这个方案很烂,Fractal 规划部门的人员还是想知道,如果城市发展到了等级N,编号为A和B的两个街区的直线距离是多少。
街区的距离指的是街区的中心点之间的距离,每个街区都是边长为 10 米的正方形。

思路:
从1—>2我们可以看出,在2的左上是顺时针旋转90°后水平翻转,右上是沿平向右水方向平移一个1图的边长,右下是沿平向向右和沿竖直向下方向平移一个1图的边长,左下是逆时针旋转90°后水平翻转再沿竖直向下方向平移一个1图的边长。
我们要想得到n级图,首要需要得到n-1级图。
为了模运算更加方便,我们设0,0点位1号点,n-1图的边长为L,x为水平坐标,y为竖直坐标,n-1->n每个点对应的变化为:
n-1图的左上点(x,y)顺时针旋转90°变为(L-y-1,x),再水平翻转变为(y,x);
n-1图的右上点(x,y)沿平向右水方向平移变为(x+L,y);
n-1图的右下点(x,y)沿平向右水方向平移变为(x+L,y+L);
n-1图的左下点(x,y)先逆时针旋转90°后变为(y,L-x-1),再水平翻转变为(L-y-1,L-x-1),最后沿竖直向下平移(L-y-1,L*2-x-1)。
移动有点抽象,可以手动画在纸上或paint翻转一下就比较显然了。

#include<bits/stdc++.h>
using namespace std;
struct ac{
    long long x,y;
};
ac calc(long long n,long long m){
    if(n==0) return {0,0};
    long long l=(1LL<<(n-1)),cnt=(1LL<<(2*n-2));
    ac pre=calc(n-1,m%cnt);
    long long x=pre.x,y=pre.y,z=m/cnt;
    if(z==0) return {y,x};
    if(z==1) return {x+l,y};
    if(z==2) return {x+l,y+l};
    if(z==3) return {l-1-y,l*2-1-x};
}
int main(){
    long long N,T,a,b;
    cin>>T;
    while(T--){
        cin>>N>>a>>b;
        a--;b--;
        ac aa=calc(N,a);
        ac bb=calc(N,b);
        double d=sqrt(1.0*(aa.x-bb.x)*(aa.x-bb.x)+1.0*(aa.y-bb.y)*(aa.y-bb.y))*10;
        printf("%.0lf\n",d);
    }
    return 0;
}

0x03 前缀和差分

激光炸弹

地图上有 N 个目标,用整数Xi,Yi表示目标在地图上的位置,每个目标都有一个价值Wi。
注意:不同目标可能在同一位置。
现在有一种新型的激光炸弹,可以摧毁一个包含 R×R 个位置的正方形内的所有的目标。
激光炸弹的投放是通过卫星定位的,但其有一个缺点,就是其爆炸范围,即那个正方形的边必须和x,y轴平行。
求一颗炸弹最多能炸掉地图上总价值为多少的目标。
输入格式
第一行输入正整数 N 和 R ,分别代表地图上的目标数目和正方形的边长,数据用空格隔开。
接下来N行,每行输入一组数据,每组数据包括三个整数Xi,Yi,Wi,分别代表目标的x坐标,y坐标和价值,数据用空格隔开。
输出格式
输出一个正整数,代表一颗炸弹最多能炸掉地图上目标的总价值数目。
数据范围:
\(0≤R≤10^9, 0<N≤10000, 0≤Xi,Yi≤5000, 0≤Wi≤1000.\)

思路: 二位前缀和。
初始化:(1,1)点到(i,j)矩形内的和为:\(pre[i][j]+=pre[i-1][j]+pre[i][j-1]-pre[i-1][j-1];\)
求部分矩形内的和:(x,y)点到(i,j)矩形内的和为:\(pre[x][y]-pre[i-1][y]-pre[x][j-1]+pre[i-1][j-1];\)
本题x,y很小,直接枚举左上角即可。

#include<bits/stdc++.h>
using namespace std;
int pre[5010][5010];
int calc(int x,int y,int i,int j){
    return pre[x][y]-pre[i-1][y]-pre[x][j-1]+pre[i-1][j-1];
}
int main(){
    int n,r,x,y,v;
    cin>>n>>r;
    for(int i=1;i<=n;++i){
        cin>>x>>y>>v;
        pre[++x][++y]=v;
    }
    for(int i=1;i<=5001;++i){
        for(int j=1;j<=5001;++j){
            pre[i][j]+=pre[i-1][j]+pre[i][j-1]-pre[i-1][j-1];
        }
    }
    int res=0;
    for(int i=1;i<=5001;++i){
        for(int j=1;j<=5001;++j){
            x=i+r-1,y=j+r-1;
            x=min(x,5001);y=min(y,5001);
            res=max(res,calc(x,y,i,j));
        }
    }
    cout<<res<<'\n';
    return 0;
}

增减序列

给定一个长度为n的数列\(a_1,a_2,…,a_n\),每次可以选择一个区间[l,r],使下标在这个区间内的数都加一或者都减一。
求至少需要多少次操作才能使数列中的所有数都一样,并求出在保证最少次数的前提下,最终得到的数列可能有多少种。
输入格式
第一行输入正整数n。
接下来n行,每行输入一个整数,第i+1行的整数代表\(a_i\)
输出格式
第一行输出最少操作次数。
第二行输出最终能得到多少种结果。
数据范围
\(0<n≤10^5, 0≤a_i<2147483648\)

思路:差分。
如果把原序列a转化为差分序列b,那么\(b_1\)等于某个值,\(b_2,b_3...,b_n\)等于0,整个原序列就是都等于\(b_1\)的相等序列了。 我们选取下标为i,j种的两个数一个加1一个减1 。为保证操作次数最少我们先选取2n之间的数,我们选取一个正数,一个负数,整数加负数减,这样一定是最优的。设2n正数和负数的绝对值分别是q,p,操作的次数就是min(p,q),之后2~n就只剩下整数或负数了,之后我们选取第1和负数的下标j,然后需要再操作max(p,q)-min(p,q),所以一共的操作次数就是max(p,q),对下标1操作了max(p,q)-min(p,q)次,加上原本的\(b_1\)就max(p,q)-min(p,q)种可能。
代码里实际只求了差分序列,但答案考察了差分应用的思想。

#include<bits/stdc++.h>
using namespace std;
#define int long long
int a[100010],d[100010];
signed main(){
    int p=0,q=0,n,x;
    cin>>n;
    for(int i=1;i<=n;++i){
        cin>>a[i];
    }
    for(int i=1;i<=n;++i){
        d[i]=a[i]-a[i-1];
        if(i!=1){
            if(d[i]>0) q+=d[i];
            else p-=d[i];
        }
    }
    cout<<max(q,p)<<'\n'<<abs(p-q)+1<<'\n';
    return 0;
}

最高的牛

有 N 头牛站成一行,被编队为\(1、2、3…N,\)每头牛的身高都为整数。
当且仅当两头牛中间的牛身高都比它们矮时,两头牛方可看到对方。
现在,我们只知道其中最高的牛是第 P 头,它的身高是 H ,剩余牛的身高未知。
但是,我们还知道这群牛之中存在着 M 对关系,每对关系都指明了某两头牛 A 和 B 可以相互看见。
求每头牛的身高的最大可能值是多少。
输入格式
第一行输入整数\(N,P,H,M,\)数据用空格隔开。
接下来M行,每行输出两个整数A和B,代表牛A和牛B可以相互看见,数据用空格隔开。
输出格式
一共输出N行数据,每行输出一个整数。
第i行输出的整数代表第i头牛可能的最大身高。
数据范围
\(1≤N≤10000, 1≤H≤1000000, 1≤A,B≤10000, 0≤M≤10000\)

思路:
差分贪心。首先我们以h作为偏移量,构造出一个初始值都为0的差分序列,表示所有牛都一样高的相对值,每次出现一列数据A,B,就表示A~B之间的牛比A,B矮了一个单位,所以就让它们都降1:d[A+1]-1,d[B]+=1. 由于我们求出来的差分序列都是相对值最后加上h,这样就是所有牛尽量高的最优答案。

#include<bits/stdc++.h>
using namespace std;
int a[10001];
map<pair<int,int> ,bool> mmp;
int main(){
    int n,p,h,m;
    cin>>n>>p>>h>>m;
    for(int i=1;i<=m;++i){
        int l,r;
        cin>>l>>r;
        if(l>r) swap(l,r);
        if(mmp.count(make_pair(l,r))) continue;
        mmp[make_pair(l,r)]=1;
        if(r-l==1) continue;
        else a[l+1]-=1,a[r]+=1;
    }
    for(int i=1;i<=n;++i){
        a[i]+=a[i-1];
        cout<<a[i]+h<<'\n';
    }

    return 0;
}

电影

莫斯科正在举办一个大型国际会议,有n个来自不同国家的科学家参会。
每个科学家都只懂得一种语言。
为了方便起见,我们把世界上的所有语言用1到\(10^9\)之间的整数编号。
在会议结束后,所有的科学家决定一起去看场电影放松一下。
他们去的电影院里一共有m部电影正在上映,每部电影的语音和字幕都采用不同的语言。
对于观影的科学家来说,如果能听懂电影的语音,他就会很开心;如果能看懂字幕,他就会比较开心;如果全都不懂,他就会不开心。
现在科学家们决定大家看同一场电影。
请你帮忙选择一部电影,可以让观影很开心的人最多。
如果有多部电影满足条件,则在这些电影中挑选观影比较开心的人最多的那一部。
输入格式
第一行输入一个整数n,代表科学家的数量。
第二行输入n个整数\(a1,a2…an\),其中ai表示第i个科学家懂得的语言的编号。
第三行输入一个整数m,代表电影的数量。
第四行输入m个整数\(b1,b2…bm\),其中bi表示第i部电影的语音采用的语言的编号。
第五行输入m个整数\(c1,c2…cm\),其中ci表示第i部电影的字幕采用的语言的编号。
请注意对于同一部电影来说,\(bi≠ci\)
同一行内数字用空格隔开。
输出格式
输出一个整数,代表最终选择的电影的编号。
如果答案不唯一,输出任意一个均可。
数据范围
\(1≤n,m≤200000, 1≤a_i,b_i,c_i≤10^9\)

思路:离散哈希。这道题考察离散,由于\(c_i\)非常大,数组记录不了所有需要离散。用map更方便。

#include<bits/stdc++.h>
using namespace std;
int p[200001*3],peo[200001],sum[200001*3];
int num[200001*3],cnt1,cnt2;
struct ac{
    int a,b;
}mo[200001];
int discreate(){
    sort(p+1,p+cnt1+1);
    cnt2=0;
    for(int i=1;i<=cnt1;++i){
        if(i==1||p[i]!=p[i-1])
            num[++cnt2]=p[i];
    }
    //cnt2=unique(p+1,p+cnt1+1)-p; //stl函数也可以
}
int query(int x){
    return lower_bound(num+1,num+1+cnt2,x)-num;
    //return lower_bound(p+1,p+1+cnt2,x)-p;
}
int main(){
    int n,m;
    cnt1=cnt2=0;
    cin>>n;
    for(int i=1;i<=n;++i){
        cin>>peo[i];
        p[++cnt1]=peo[i];
    }
    cin>>m;
    for(int i=1;i<=m;++i){
        cin>>mo[i].a;
        p[++cnt1]=mo[i].a;
    }
    for(int i=1;i<=m;++i){
        cin>>mo[i].b;
        p[++cnt1]=mo[i].b;
    }
    discreate();
    int ans,Maxa=0,Maxb=0;
    for(int i=1;i<=n;++i){
        int x=query(peo[i]);
        sum[x]++;
    }
    for(int i=1;i<=m;++i){
        int na=query(mo[i].a),nb=query(mo[i].b);
        if(sum[na]>Maxa){
            ans=i;
            Maxa=sum[na];
            Maxb=sum[nb];
        }
        else if(sum[na]==Maxa&&sum[nb]>Maxb){
            ans=i;
            Maxb=sum[nb];
        }
    }
    cout<<ans<<'\n';
    return 0;
}

0x04二分

最佳牛围栏

农夫约翰的农场由 N 块田地组成,每块地里都有一定数量的牛,其数量不会少于1头,也不会超过2000头。
约翰希望用围栏将一部分连续的田地围起来,并使得围起来的区域内每块地包含的牛的数量的平均值达到最大。
围起区域内至少需要包含 F 块地,其中 F 会在输入中给出。
在给定条件下,计算围起区域内每块地包含的牛的数量的平均值可能的最大值是多少。
输入格式
第一行输入整数 N 和 F ,数据间用空格隔开。
接下来 N 行,每行输出一个整数,第i+1行输出的整数代表,第i片区域内包含的牛的数目。
输出格式
输出一个整数,表示平均值的最大值乘以1000再 向下取整 之后得到的结果。
数据范围
\(1≤N≤100000 1≤F≤N\)

思路:二分。由于求最大的平均值,我们可以想到二分,最大平均值是有单调性的(其实我一开始想的是三分枚举长度,后来发现也会有多峰函数的可能,卒...)。二分枚举最大平均值mid,然后在check函数中求是否存在一段长度大于等于f的区间平均值大于等于mid。(到这一步没思路了)我们把所有点减去mid,check就转化为是否存在一段长度大于等于f的区间之和大于0,问题又等价于是否存在一段长度大于等于f的区间的最大值是否大于0,这个值可以用dp求:当前点的前缀和减去预存前f长度前缀和的最小值就是以当前点为右端点长度大于等于f的区间最大和。

#include<bits/stdc++.h>
using namespace std;
double a[100001],sum[100001],dp[100001];
int n,f;
bool check(double m){
    for(int i=1;i<=n;++i){
        sum[i]=a[i]-m+sum[i-1];
    }
    double ans=-1e6,t=0;
    for(int i=f;i<=n;++i){
        dp[i]=max(ans,sum[i]-t);
        t=min(t,sum[i-f+1]);
    }
    for(int i=f;i<=n;++i){
        if(dp[i]>=0) return true;
    }
    return false;
}
int main(){
    cin>>n>>f;
    for(int i=1;i<=n;++i){
        cin>>a[i];
    }
    double l=-1e6,r=1e6,ans;
    for(int i=1;i<=200;++i){
        double m=(l+r)/2;
        if(check(m)) {
            ans=m;
            l=m;
        }
        else r=m;
    }
    cout<<(int)((ans+1e-6)*1000)<<endl;
    return 0;
}

赶牛入圈

农夫约翰希望为他的奶牛们建立一个畜栏。
这些挑剔的畜生要求畜栏必须是正方形的,而且至少要包含C单位的三叶草,来当做它们的下午茶。
畜栏的边缘必须与X,Y轴平行。
约翰的土地里一共包含N单位的三叶草,每单位三叶草位于一个1 x 1的土地区域内,区域位置由其左下角坐标表示,并且区域左下角的X,Y坐标都为整数,范围在1到10000以内。
多个单位的三叶草可能会位于同一个1 x 1的区域内,因为这个原因,在接下来的输入中,同一个区域坐标可能出现多次。
只有一个区域完全位于修好的畜栏之中,才认为这个区域内的三叶草在畜栏之中。
请你帮约翰计算一下,能包含至少C单位面积三叶草的情况下,畜栏的最小边长是多少。
输入格式
第一行输入两个整数 C 和 N。
接下来 N 行,每行输入两个整数 X 和 Y,代表三叶草所在的区域的X,Y坐标。
同一行数据用空格隔开。
输出格式
输出一个整数,代表畜栏的最小边长。
数据范围
\(1≤C≤500, C≤N≤500\)

思路:二分双指针。二分枚举边长比较好想,check函数判断是否存在边长L的正方形内大于c的点,由于数据并不大,可以通过遍历长度为L横坐标区间,将在这一区间内的草的y坐标统计下来,利用双指针记录包含c个点的最小y距离是否小于等于L。
(题解是差分,点坐标差分感觉很难啊...)

#include<bits/stdc++.h>
using namespace std;
struct node{
    int x,y;
}p[501];
bool cmp(node n1,node n2){
    return n1.x<n2.x;
}
int n,c;
int tmp[501];
bool check(int len){
    for(int i=1;i<=n;++i){
        if(i+c-1>n) break;
        int cnt=0;
        for(int j=i;j<=n;++j){
            if(p[j].x+1-p[i].x<=len) tmp[++cnt]=p[j].y;
        }
        if(cnt<c) continue;
        sort(tmp+1,tmp+1+cnt);
        int l=1,r=c,ans=10000;
        while(r<=cnt){
            ans=min(ans,tmp[r]+1-tmp[l]);
            ++r;
            ++l;
            if(ans<=len) return true;
        }
    }
    return false;
}
int main(){
    cin>>c>>n;
    for(int i=1;i<=n;++i)
        cin>>p[i].x>>p[i].y;
    sort(p+1,p+1+n,cmp);
    int l=0,r=10000;
    while(l<=r){
        int mid=(l+r)/2;
        if(check(mid)){
            r=mid-1;
        }
        else l=mid+1;
    }
    cout<<l<<endl;
    return 0;
}

防线

达达学习数学竞赛的时候受尽了同仁们的鄙视,终于有一天......受尽屈辱的达达黑化成为了黑暗英雄怪兽达达。
就如同中二漫画的情节一样,怪兽达达打算毁掉这个世界。
数学竞赛界的精英 lqr 打算阻止怪兽达达的阴谋,于是她集合了一支由数学竞赛选手组成的超级行动队。
由于队员们个个都智商超群,很快,行动队便来到了怪兽达达的黑暗城堡的下方。
但是,同样强大的怪兽达达在城堡周围布置了一条“不可越过”的坚固防线。
防线由很多防具组成,这些防具分成了N组。
我们可以认为防线是一维的,那么每一组防具都分布在防线的某一段上,并且同一组防具是等距离排列的。
也就是说,我们可以用三个整数 S, E 和 D 来描述一组防具,即这一组防具布置在防线的 \(S,S + D,S + 2D,…,S + KD(K∈ Z,S + KD≤E,S + (K + 1)D>E)\)位置上。
黑化的怪兽达达设计的防线极其精良。如果防线的某个位置有偶数个防具,那么这个位置就是毫无破绽的(包括这个位置一个防具也没有的情况,因为 0 也是偶数)。
只有有奇数个防具的位置有破绽,但是整条防线上也最多只有一个位置有奇数个防具。
作为行动队的队长,lqr 要找到防线的破绽以策划下一步的行动。
但是,由于防具的数量太多,她实在是不能看出哪里有破绽。作为 lqr 可以信任的学弟学妹们,你们要帮助她解决这个问题。
输入格式
输入文件的第一行是一个整数T,表示有T 组互相独立的测试数据。
每组数据的第一行是一个整数N。
之后N行,每行三个整数\(Si,Ei,Di\),代表第 i 组防具的三个参数,数据用空格隔开。
输出格式
对于每组测试数据,如果防线没有破绽,即所有的位置都有偶数个防具,输出一行 "There's no weakness."(不包含引号) 。
否则在一行内输出两个空格分隔的整数P和 C,表示在位置P有C个防具。当然C应该是一个奇数。
数据范围
防具总数不多于108,
\(Si≤Ei, 1≤T≤5, N≤200000, 0≤Si,Ei,D_i≤2\)31\(−1\)

思路:
由于本题保证最多只有一个点有奇数个防具,那么就很容易想到奇数加偶数等于奇数。如果一个点是奇数点,那么从该点包括改点以后的前缀和都是奇数,所以奇数点位是具有单调性的,那么二分枚举奇数点位。

#include<bits/stdc++.h>
using namespace std;
#define int long long
long long s[200010],e[200010],d[200010],Ml,Mr,n;
long long calc(long long ed){
	long long sum=0;
	for(int i=1;i<=n;++i){
		if(s[i]<=ed){
			sum+=(min(ed,e[i])-s[i])/d[i]+1;
		}
	}
	return sum;
}
bool check(int st,int ed){
	long long sum1=calc(st-1),sum2=calc(ed);
	return (sum2-sum1)%2;
}
signed main(){
	int T;
	cin>>T;
	while(T--){
		cin>>n;
		Ml=100000000000000,Mr=0;
		for(int i=1;i<=n;++i){
			cin>>s[i]>>e[i]>>d[i];
			Ml=min(Ml,s[i]);
			Mr=max(Mr,e[i]);
		}
		if(!check(Ml,Mr)){
			puts("There's no weakness.");
			continue;
		}
		int l=Ml,r=Mr;
		while(l<=r){
			int mid=(l+r)>>1;
			if(check(Ml,mid)){
				r=mid-1;
			}
			else l=mid+1;
		}
		cout<<l<<" "<<calc(l)-calc(l-1)<<endl;
	}	
	return 0;
}

0x05 排序

中位数

货仓选址

在一条数轴上有 N 家商店,它们的坐标分别为 A1~AN。
现在需要在数轴上建立一家货仓,每天清晨,从货仓到每家商店都要运送一车商品。
为了提高效率,求把货仓建在何处,可以使得货仓到每家商店的距离之和最小。
输入格式
第一行输入整数N。
第二行N个整数\(A_1~A_N\)
输出格式
输出一个整数,表示距离之和的最小值。
数据范围
\(1≤N≤100000\)

思路:中位数思想的第一道题。
答案即求x取何值\(|A_1-A_x|+|A_2-A_x|+...+|A_N-A_x|\)最小。
首先我们将\(A_1~A_N\)从小到大排个序,设仓库选在x位置,x左边有q家,右边有p家。那么当仓库位置x+1即向右移动1个单位,距离之和d变为d+q-p,同理向左移动d变为d+p-q,当p=q时为最优解,即x在中间位置。因此应选择中位数的为仓库,当N为偶数时N/2,N/2+1都是正确答案,N为奇数时N/2是正确答案。

#include<bits/stdc++.h>
using namespace std;
int a[100001];
int main(){
    int n;
    cin>>n;
    for(int i=1;i<=n;++i){
        cin>>a[i];
    }
    sort(a+1,a+1+n);
    int t=a[n/2+1],res=0;
    for(int i=1;i<=n;++i){
        res+=abs(a[i]-t);
    }
    cout<<res<<'\n';
    return 0;
}

糖果传递

有n个小朋友坐成一圈,每人有a[i]个糖果。
每人只能给左右两人传递糖果。
每人每次传递一个糖果代价为1。
求使所有人获得均等糖果的最小代价。
输入格式
第一行输入一个正整数n,表示小朋友的个数。
接下来n行,每行一个整数\(a[i]\),表示第i个小朋友初始得到的糖果的颗数。
输出格式
输出一个整数,表示最小代价。
数据范围
\(1≤n≤1000000\)
数据保证一定有解。

思路:
中位数,纸牌均分。
纸牌均分:有N对纸牌水平排列,相邻纸牌可以做交换,一次只能交换一张纸牌,求使每一堆纸牌数相等的最少交换次数。
我们先把所有堆都减去平均值\(A_i=a_i-avg\)第一次1和2交换我们只能在通过使得1的个数达到平均,答案就是\(|A_1|\),第二次2和3交换,我们需保证1和2的个数达到平均值那么答案就是\(|A_1+A_2|\),后面的同理。那么总答案就是\(|A_1|\)+\(|A_1+A_2|+...+|A_1+A_2+...+A\)n| ,第n项一定是0嘛。设\(S\)为A的前缀和,答案也可以是\(|S_1|+|S_2|+...+|S_n|\)
再回到这道题上,可以发现这是一个环形的纸牌均分问题,也一定存在环上某两个相邻的人不发生交换的情况。那么我们只需要找到一个线性的均分方法使得水平均分纸牌的答案最小。假设我们选取k号位置为最后一个点。答案就变为:\(|S\)k+1\(-S_k|+|S\)k+2\(-S_k|+...+|S_n-S_k|+|S_1-S_k|+...+|S_k+S_n-S_k|\)。S_n等于0。那么答案就是\(|S_i-S_k|\)之和,这和的货仓选址问题的形式一摸一样,就是选取\(S_k\)到所有\(S_i\)的距离之和最小,\(S_k\)就是中位数了。

#include<bits/stdc++.h>
using namespace std;
long long a[1000001],s[1000001];
int main(){
    long long n,sum=0;
    cin>>n;
    for(int i=1;i<=n;++i){
        cin>>a[i];
        sum+=a[i];
    }
    long long avg=sum/n;
    for(int i=1;i<=n;++i){
        s[i]=s[i-1]+a[i]-avg;
    }
    sort(s+1,s+1+n);
    long long mid=s[n/2+1],res=0;
    for(int i=1;i<=n;++i){
        res+=abs(mid-s[i]);
    }
    cout<<res<<'\n';
    return 0;
}

七夕祭

思路:
行列向做两次糖果传递,因为只能和上下左右相邻的摊位交换,行列方向的划分时独立的。

#include<bits/stdc++.h>
using namespace std;
long long a[100010],b[100010],s[100010];
long long solve(int n,long long *arr){
    long long sum=0;
    for(int i=1;i<=n;++i){
        sum+=arr[i];
    }
    if(sum%n!=0) return -1;
    long long avg=sum/n,res=0;
    for(int i=1;i<=n;++i){
        s[i]=s[i-1]+arr[i]-avg;
    }
    sort(s+1,s+1+n);
    long long mid=s[n/2+1];
    for(int i=1;i<=n;++i){
        res+=abs(s[i]-mid);
    }
    return res;
}
int main(){
    int n,m,t,x,y;
    cin>>n>>m>>t;
    for(int i=1;i<=t;++i){
        cin>>x>>y;
        a[x]++;b[y]++;
    }
    long long res1=solve(n,a);
    long long res2=solve(m,b);
    if(res1!=-1&&res2!=-1) {
        printf("both %lld\n",res1+res2);
    }
    else if(res1!=-1){
        printf("row %d\n",res1);
    }
    else if(res2!=-1){
        printf("column %d\n",res2);
    }
    else puts("impossible");
    return 0;
}

士兵

格格兰郡的N名士兵随机散落在全郡各地。
格格兰郡中的位置由一对(x,y)整数坐标表示。
士兵可以进行移动,每次移动,一名士兵可以向上,向下,向左或向右移动一个单位(因此,他的x或y坐标也将加1或减1)。
现在希望通过移动士兵,使得所有士兵彼此相邻的处于同一条水平线内,即所有士兵的y坐标相同并且x坐标相邻。
请你计算满足要求的情况下,所有士兵的总移动次数最少是多少。
需注意,两个或多个士兵不能占据同一个位置。
输入格式
第一行输入整数N,代表士兵的数量。
接下来的N行,每行输入两个整数x和y,分别代表一个士兵所在位置的x坐标和y坐标,第i行即为第i个士兵的坐标(x[i],y[i])。
输出格式
输出一个整数,代表所有士兵的总移动次数的最小值。
数据范围
\(1≤N≤10000, −10000≤x[i],y[i]≤10000\)

思路:
在y方向上就是中位数思想。x方向上可以这样转化为中位数思想:最终状态下,每个人与第一个人左边1的位置的距离从左到右依次差1,2...,n,这个移动的消耗是固定的,那么把每个人的这个消耗减去,问题就转化成了所有人到同一个位置的最少消耗,那就是中位数了!

#include<bits/stdc++.h>
using namespace std;
int x[10001],y[10001];
int main(){
    int n;
    cin>>n;
    for(int i=1;i<=n;++i){
        cin>>x[i];cin>>y[i];
    }
    sort(y+1,y+1+n);
    int mid=y[n/2+1];
    int ans=0;
    for(int i=1;i<=n;++i){
        ans+=abs(mid-y[i]);
    }
    sort(x+1,x+1+n);
    for(int i=1;i<=n;++i){
        x[i]-=i;
    }
    sort(x+1,x+1+n);
    mid=x[n/2+1];
    for(int i=1;i<=n;++i){
        ans+=abs(mid-x[i]);
    }
    cout<<ans<<'\n';
    return 0;
}

动态中位数

题目大意:
动态维护当前序列中的中位数。
思路:
用两个堆维护,大根堆维护中位数前的数,小根堆位数中位数后以及中位数。

#include<bits/stdc++.h>
using namespace std;
priority_queue<int> Min,Max;
vector<int> ans;
int main(){
    int T;
    cin>>T;
    for(int cas=1;cas<=T;++cas){
        ans.clear();
        while(!Min.empty()){
            Min.pop();
        }
        while(!Max.empty()){
            Max.pop();
        }
        int id,m,x;
        cin>>id>>m;
        for(int i=1;i<=m;++i){
            cin>>x;
            if(i==1){
                Min.push(-x);
            }
            else if(i==2){
                if(x>-Min.top()){
                    int t=-Min.top();
                    Min.pop();
                    Max.push(t);
                    Min.push(-x);
                }
                else Max.push(x);
            }
            else if(i%2){
                if(x<Max.top()){
                    int t=Max.top();
                    Max.pop();
                    Max.push(x);
                    Min.push(-t);
                }
                else Min.push(-x);
            }
            else {
                if(x>-Min.top()){
                    int t=-Min.top();
                    Min.pop();
                    Min.push(-x);
                    Max.push(t);
                }
                else Max.push(x);
            }
            if(i%2)
                ans.push_back(-Min.top());
        }
        cout<<id<<" "<<ans.size()<<'\n';
        int cnt=0;
        for(int i=0;i<ans.size();++i){
            cnt++;
            if(cnt==10||i==ans.size()-1){
                cout<<ans[i]<<'\n';
                cnt=0;
            }
            else if(cnt!=10)
            cout<<ans[i]<<" ";
        }
    }
    return 0;
}

超快速排序

题目大意:
求对一个序列进行冒泡排序的最少交换次数。\(0≤N<500000\)
思路:
如果直接冒泡排序记录交换次数肯定超时。冒泡排序的最少交换次数等于逆序数对数。例如:\(2,4,1,3\) 在第三个数时我们需要交换的次数是2,之后序列变为:\(1,2,4,3\),第四个数时需要交换的次数是1.我们发现从前往后维护一个有序序列第i位,那么1i-1前都是有序的,交换的次数就是1i-1比第i位数大的数,那么就是逆序数了。
分治法求逆序数。
代码:
https://paste.ubuntu.com/p/vymXGsvv2S/

奇数码问题

思路:我们可以很容易看出来一个八数码左右移动空格逆序对数不变,上下移动逆序对数±2.推广到N阶两个奇数码可达的条件就是逆序对数奇偶性相同。
(N阶偶数码可达的条件是逆序对数之差与空格行数之差奇偶性相同)

#include<bits/stdc++.h>
using namespace std;
const int N=510;
int a[N*N],tmp[N*N];
long long res=0;
void solve(int l,int r){
    if(l>=r) return ;
    int i=l,mid=(l+r)/2;int j=mid+1,cnt=l;
    solve(l,mid);solve(mid+1,r);

    while(i<=mid&&j<=r){
        if(a[i]<=a[j]){
            tmp[cnt++]=a[i];
            i++;
        }
        else {
            res+=mid-i+1;
            tmp[cnt++]=a[j];
            j++;
        }
    }
    while(i<=mid){
        tmp[cnt++]=a[i];
        ++i;
    }
    while(j<=r){
        tmp[cnt++]=a[j];
        ++j;
    }
    for(int i=l;i<=r;++i){
        a[i]=tmp[i];
    }
}
int main(){
    int n;
    while(cin>>n){
        int n1=0,n2=0,c;
        for(int i=1;i<=n*n;++i){
            cin>>c;
            if(c!=0) a[++n1]=c;
        }
        res=0;

        solve(1,n1);
        long long cnt1=res;
        for(int i=1;i<=n*n;++i){
            cin>>c;
            if(c!=0) a[++n2]=c;
        }
        res=0;
        solve(1,n2);
        long long cnt2=res;
        if((cnt1&1)==(cnt2&1)) cout<<"TAK\n";
        else cout<<"NIE\n";
    }
    return 0;
}

0x06 贪心

1.微扰(邻项交换)
证明在任意局面下,任何对局部最优策略的微小改变都会造成整体结果变差。经常
用于以“排序”为贪心策略的证明。
2.范围缩放
证明任何对局部最优策略作用范围的扩展都不会造成整体结果变差。
3.决策包容性
证明在任意局面下,作出局部最优决策以后,在问题状态空间中的可达集合包含了
作出其他任何决策后的可达集合。换言之,这个局部最优策略提供的可能性包含其他所
有策略提供的可能性。
4.反证法
5.数学归纳法

防晒

有C头奶牛进行日光浴,第i头奶牛需要minSPF[i]到maxSPF[i]单位强度之间的阳光。
每头奶牛在日光浴前必须涂防晒霜,防晒霜有L种,涂上第i种之后,身体接收到的阳光强度就会稳定为SPF[i],第i种防晒霜有cover[i]瓶。
求最多可以满足多少头奶牛进行日光浴。
输入格式
第一行输入整数C和L。
接下来的C行,按次序每行输入一头牛的minSPF和maxSPF值,即第i行输入minSPF[i]和maxSPF[i]。
再接下来的L行,按次序每行输入一种防晒霜的SPF和cover值,即第i行输入SPF[i]和cover[i]。
每行的数据之间用空格隔开。
输出格式
输出一个整数,代表最多可以满足奶牛日光浴的奶牛数目。
数据范围
\(1≤C,L≤2500, 1≤minSPF≤maxSPF≤1000, 1≤SPF≤1000\)

思路:
贪心。我们按左端点给牛升序排序,从左端点最大的牛开始遍历,如果有两个防晒霜x,y(x<=y)都包含在了当前区间内,那么对于后面的牛,
【1.包含了x,y,2.只包含了x,3.都没有包含】。不存在只包含了y而不包含x的可能。在当前区间应该优先选择y,由于所有的点对答案的贡献都为1,所以在当前这头牛选择这个点不会对最优答案有影响。所有对防晒霜从大到小开始遍历。对称方法:按右端点从小到大排序,从左往右扫描点也是可以(可以注意到两个方法都把下一区间的排序端点固定到了当前区间之外)。复杂度\(O(n*m)\)

#include<bits/stdc++.h>
using namespace std;
const int maxn=3000;
struct cow{
    int l,r;
}a[maxn];
struct milk{
    int c,s;
}b[maxn];
bool cmpa(cow c1,cow c2){
    return c1.l>c2.l;
}
bool cmpb(milk c1,milk c2){
   return c1.c<c2.c;
}
map<int,int> mmp;
int main(){
    int n,m;
    cin>>n>>m;
    for(int i=1;i<=n;++i){
        cin>>a[i].l>>a[i].r;
    }
    int t1,t2,cnt=0;
    for(int i=1;i<=m;++i){
        cin>>t1>>t2;
        if(mmp[t1]==0){
            mmp[t1]=++cnt;
            b[cnt]=(milk){t1,t2};
        }
        else b[mmp[t1]].s+=t2;
    }
    sort(a+1,a+1+n,cmpa);
    sort(b+1,b+1+m,cmpb);
    int ans=0;
    for(int i=1;i<=n;++i){
        for(int j=m;j>=1;--j){
            if(b[j].s>0&&a[i].l<=b[j].c&&b[j].c<=a[i].r){
                b[j].s--;
                ans++;
                break;
            }
        }
    }
    cout<<ans<<endl;
    return 0;
}

利用堆的贪心思路。将牛以左端点为第一关键字右端点为第二关键字降序排序,防晒霜降序排序,从左向右遍历防晒霜,对于左端点在当前点左边入队,右端点不在当前点右边的出队。队列中得到在包含点内的牛,对于每个点让右端点小的尽量得到防晒霜,因为右端点小的更有可能在遍历下一防晒霜时出队。做法和这道题非常相似:羊吃草

#include<bits/stdc++.h>
using namespace std;
const int maxn=3000;
struct cow{
    int l,r;
}a[maxn];
struct milk{
    int c,s;
}b[maxn];
bool cmpa(cow c1,cow c2){
    if(c1.l==c2.l) return c1.r<c2.r;
    else return c1.l<c2.l;
}
bool cmpb(milk c1,milk c2){
   return c1.c<c2.c;
}
priority_queue<int,vector<int> ,greater<int> > q;
int main(){
    int n,m;
    cin>>n>>m;
    for(int i=1;i<=n;++i){
        cin>>a[i].l>>a[i].r;
    }
    for(int i=1;i<=m;++i){
        cin>>b[i].c>>b[i].s;
    }
    sort(a+1,a+1+n,cmpa);
    sort(b+1,b+1+m,cmpb);
    int ans=0;
    for(int i=1,j=1;i<=m;++i){
        while(j<=n&&a[j].l<=b[i].c)
            q.push(a[j].r),++j;
        while(!q.empty()&&q.top()<b[i].c)
            q.pop();
        while(!q.empty()&&b[i].s>0){
            b[i].s--;
            ++ans;
            q.pop();
        }
    }
    cout<<ans<<endl;
    return 0;
}

畜栏预约

有N头牛在畜栏中吃草。
每个畜栏在同一时间段只能提供给一头牛吃草,所以可能会需要多个畜栏。
给定N头牛和每头牛开始吃草的时间A以及结束吃草的时间B,每头牛在[A,B]这一时间段内都会一直吃草。
当两头牛的吃草区间存在交集时(包括端点),这两头牛不能被安排在同一个畜栏吃草。
求需要的最小畜栏数目和每头牛对应的畜栏方案。
输入格式
第1行:输入一个整数N。
第2..N+1行:第i+1行输入第i头牛的开始吃草时间A以及结束吃草时间B,数之间用空格隔开。
输出格式
第1行:输入一个整数,代表所需最小畜栏数。
第2..N+1行:第i+1行输入第i头牛被安排到的畜栏编号,编号是从1开始的 连续 整数,只要方案合法即可。
数据范围
\(1≤N≤50000, 1≤A,B≤1000000\)

思路:贪心。按左端点降序排序,如果堆非空且堆顶牛的右端点小于当前牛的左端点,说明这头牛可以接着用这个畜栏,让牛进入小根堆。否则增加畜栏数,让牛进入小根堆。

#include<bits/stdc++.h>
using namespace std;
priority_queue<pair<int,int> > q;
struct ac{
    int l,r,id;
}cow[50010];
bool cmp(ac c1,ac c2){
    return c1.l<c2.l;
}
int pos[50010];
int main(){
    int n;
    cin>>n;
    for(int i=1;i<=n;++i){
        cin>>cow[i].l>>cow[i].r;
        cow[i].id=i;
    }
    sort(cow+1,cow+1+n,cmp);
    int ans=0;
    for(int i=1;i<=n;++i){
        if(!q.empty()&&-q.top().first<cow[i].l){
            pos[cow[i].id]=pos[q.top().second];
            q.pop();
            q.push({-cow[i].r,cow[i].id});
           // cout<<"in"<<endl;
        }
        else {
            q.push({-cow[i].r,cow[i].id});
            pos[cow[i].id]=++ans;
        }
    }
    cout<<ans<<'\n';
    for(int i=1;i<=n;++i){
        cout<<pos[i]<<'\n';
    }
    return 0;
}

雷达设备

假设海岸是一条无限长的直线,陆地位于海岸的一侧,海洋位于另外一侧。
每个小岛都位于海洋一侧的某个点上。
雷达装置均位于海岸线上,且雷达的监测范围为d,当小岛与某雷达的距离不超过d时,该小岛可以被雷达覆盖。
我们使用笛卡尔坐标系,定义海岸线为x轴,海的一侧在x轴上方,陆地一侧在x轴下方。
现在给出每个小岛的具体坐标以及雷达的检测范围,请你求出能够使所有小岛都被雷达覆盖所需的最小雷达数目。
输入格式
第一行输入两个整数n和d,分别代表小岛数目和雷达检测范围。
接下来n行,每行输入两个整数,分别代表小岛的x,y轴坐标。
同一行数据之间用空格隔开。
输出格式
输出一个整数,代表所需的最小雷达数目,若没有解决方案则所需数目输出“-1”。
数据范围
1≤n≤1000

思路:
贪心,几何。首先利用勾股定理求出能在给定的d下每个小岛能够被覆盖的x坐标范围,然后求出N个区间。贪心算出能包含这N个区间的最少点数,将每个点按右端点降序排序,第一个点的右端点(最小的右端点)位置时第一个雷达的位置,往后判断每个区间的左端点是否小于这个点,如果没有就设新的雷达在这个区间的右端点。

#include<bits/stdc++.h>
using namespace std;
const double eps=1e-5;
double x[1001],y[1001];
struct ac{
    double l,r;
}seg[1001];
ac calc(int p,double d){
    double len=sqrt(d*d-y[p]*y[p]);
    return (ac){x[p]-len,x[p]+len};
}
bool cmp(ac a1,ac a2){
    return eps<a2.r-a1.r;
}
int main(){
    int n,cnt=0;double d;bool f=0;
    cin>>n>>d;
    for(int i=1;i<=n;++i){
        cin>>x[i]>>y[i];
        if(y[i]-d>eps) f=1;
    }
    if(f){
        puts("-1");
        return 0;
    }
    else if(n==0){
        puts("0");
        return 0;
    }
    for(int i=1;i<=n;++i){
        seg[i]=calc(i,d);
    }
    sort(seg+1,seg+1+n,cmp);
    int ans=1;
    double now=seg[1].r;
    for(int i=2;i<=n;++i){
        if(seg[i].l-now>eps){
            ans++;
            now=seg[i].r;
        }
    }
    cout<<ans<<'\n';
    return 0;
}

任务

今天某公司有M个任务需要完成。
每个任务都有相应的难度级别和完成任务所需时间。
第i个任务的难度级别为yi,完成任务所需时间为xi分钟。
如果公司完成此任务,他们将获得(500 * xi + 2 * yi)美元收入。
该公司有N台机器,每台机器都有最长工作时间和级别。
如果任务所需时间超过机器的最长工作时间,则机器无法完成此任务。
如果任务难度级别超过机器的级别,则机器无法完成次任务。
每台机器一天内只能完成一项任务。
每个任务只能由一台机器完成。
请为他们设计一个任务分配方案,使得该公司能够最大化他们今天可以完成的任务数量。
如果有多种解决方案,他们希望选取赚取利润最高的那种。
输入格式
输入包含几个测试用例。
对于每个测试用例,第一行包含两个整数N和M,分别代表机器数量和任务数量。
接下来N行,每行包含两个整数xi,yi,分别代表机器最长工作时间和机器级别。
再接下来M行,每行包含两个整数xi,yi,分别代表完成任务所需时间和任务难度级别。
输出格式
对于每个测试用例,输出两个整数,代表公司今天可以完成的最大任务数以及他们将获得的收入。
数据范围
\(1≤N,M≤100000, 0<x_i<1440, 0≤y_i≤100\)

思路:
贪心。我们发现对于每个任务收入中y最大的贡献也不过200,而x最小时都是1×500了。所以应优先完成x大的任务。我们将机器和任务都按x为第一关键字y为第二关键字排序,设置两个指针i,j都从大开始遍历,每次遍历一个任务,当机器的x大于等于任务的x的mulitset(mulitset可以二分查找),所有符合的加入后,取出y大于等于且最接近当前任务的y的机器完成该任务,下次--i,mulitset里的机器都是大于上一任务的机器,由于排序也一定大于当前任务了。

#include<bits/stdc++.h>
using namespace std;
typedef pair<int,int> PII;
typedef long long LL;
const int maxn=100010;
PII mec[maxn],task[maxn];
int main(){
    int n,m;
    while(cin>>n>>m){
        for(int i=1;i<=n;++i){
            cin>>mec[i].first>>mec[i].second;
        }
        for(int i=1;i<=m;++i){
            cin>>task[i].first>>task[i].second;
        }
        sort(mec+1,mec+1+n);
        sort(task+1,task+1+m);
        LL res=0,cnt=0;
        multiset<int> mss;
        for(int i=m,j=n;i>=1;--i){
            int x=task[i].first,y=task[i].second;
            while(x<=mec[j].first){
                mss.insert(mec[j--].second);
            }
            auto it=mss.lower_bound(y);
            if(it!=mss.end()){
                res+=x*500+y*2;
                cnt++;
                mss.erase(it);
            }
        }
        cout<<cnt<<' '<<res<<'\n';
    }
    return 0;
}

国王的游戏

恰逢 H 国国庆,国王邀请 n 位大臣来玩一个有奖游戏。
首先,他让每个大臣在左、右手上面分别写下一个整数,国王自己也在左、右手上各写一个整数。
然后,让这 n 位大臣排成一排,国王站在队伍的最前面。
排好队后,所有的大臣都会获得国王奖赏的若干金币,每位大臣获得的金币数分别是:
排在该大臣前面的所有人的左手上的数的乘积除以他自己右手上的数,然后向下取整得到的结果。
国王不希望某一个大臣获得特别多的奖赏,所以他想请你帮他重新安排一下队伍的顺序,使得获得奖赏最多的大臣,所获奖赏尽可能的少。
注意,国王的位置始终在队伍的最前面。
输入格式
第一行包含一个整数 n,表示大臣的人数。
第二行包含两个整数 a 和 b,之间用一个空格隔开,分别表示国王左手和右手上的整数。
接下来 n 行,每行包含两个整数 a 和 b,之间用一个空格隔开,分别表示每个大臣左手和右手上的整数。
输出格式
输出只有一行,包含一个整数,表示重新排列后的队伍中获奖赏最多的大臣所获得的金币数。
数据范围
\(1≤n≤1000 0<a,b<10000\)

思路:
肯定是要排序,可以通过微调来想思路。
设有(1,2),(2,1)两种排队方式,
\((1,2)\) 答案有:\((a/b_1,(a\*a1)/b_2)\)
\((2,1)\) 答案有:(\(a/b_2,(a\*a_2)/b_1\)).
首先可以得到:\(a/b_1<(a\*a_1)/b_2,a/b_2<(a\*a_2)/b_1\),获得最多的只可能时\(max((a\*a_2)/b_1,(a\*a_1)/b_2)\),设
\((a\*a_1)/b_2<(a\*a_2)/b_1\) ,可得:
\(a_1\*b_1 < a_2\*b_2\),故按照(1,2)排列最优时:\(a_1\*b_1 < a_2\*b_2\)。所以按照 \(a*b\) 降序排序。

N=int(input())
s=input().split()
S=int(s[0])
T=int(s[1])
a=[]
for i in range(1,N+1):
    k=input().split()
    a.append((int(k[0]),int(k[1])))
a.sort(key=lambda x:x[0]*x[1])
ans=0
for i in range(0,N):
    if(S//(a[i])[1]>ans):
        ans=S//(a[i])[1]
    S*=(a[i])[0]
print(ans)

类似题目:

耍杂技的牛

按w+s降序排序

#include<bits/stdc++.h>
using namespace std;
#define int long long
struct ac{
    int w,s;
}c[50010];
bool cmp(ac a1,ac a2){
    return a1.w+a1.s<a2.w+a2.s;
}
signed main(){
    int n;
    cin>>n;
    for(int i=1;i<=n;++i){
        cin>>c[i].w>>c[i].s;
    }
    sort(c+1,c+1+n,cmp);
    int sum=0,ans=-1000000000000;
    for(int i=1;i<=n;++i){
        ans=max(ans,sum-c[i].s);
        sum+=c[i].w;
    }
    cout<<ans<<'\n';
    return 0;
}
posted @ 2020-03-18 14:36  0x4f  阅读(255)  评论(0编辑  收藏  举报