基础算法
二分答案
当答案满足一定的单调性(如对于一个值成立,小于它的均成立,大于它的可能不成立,因此就要找大于它的;如果它不成立,大于它的均不成立,小于它的可能成立,因此就要找小于它的。直到找到一个值小于它成立(包括自己),大于它不成立,这个就是最值(常常也是答案))。
本质上是通过二分方法不停枚举,检验答案(检验比正面做简单)。需要满足:可检验和二分性
例题
ll a[M],L,n,m;
ll ans;
bool check(ll x)
{
ll i,j;
ll now=0,s=0;
for(i=1;i<=n+1;i++)
{
if(a[i]-a[now]<x) s++;
else now=i;
}
if(s>m) return false;
return true;
}
int main()
{
ll i,j;
L=read();
n=read(),m=read();
for(i=1;i<=n;i++){
a[i]=read();
}
a[n+1]=L;
ll l=1,r=L;
while(l<=r)
{
ll mid=(l+r)>>1;
if(check(mid)){
ans=mid;
l=mid+1;
}
else {
r=mid-1;
}
}
printf("%lld",ans);
return 0;
}
因为这道题目的是要求最小距离的最大值,因此每次找到最小距离,还应该试试有没有更大的,即 l = m i d + 1 l=mid+1 l=mid+1。
每个二分答案都有check函数。
这里检验连续两个点之间的距离。
这里是如果两个点距离大于在检验的最小距离(x),再检验下一个点对。
如果两个点距离小于,记录 s u m + + sum++ sum++,(sum用来删点,即 s u m ≤ m sum\leq m sum≤m),如果有两个点距离小于x,则必有一个点多余,删去一个,因为我们是往右边查询,删去右边的点显然比删去左边的好,因为更右边的点距离左边的点距离肯定大于距离右边的点。(如果是最后一个区间,删去右端点相当于删去左端点)。
程序具体实现只用记录现在检查到哪个点,如果右端点删掉,那么这个左指针就不变,只变右指针。
ll a[M],L,n,m;
ll ans;
bool check(ll x)
{
ll i,j;
if(x*log10(x)>=(double)n-1) return true;
else return false;
}
int main()
{
ll i,j;
n=read();
ll l=1,r=n+10;
while(l<=r)
{
ll mid=(l+r)>>1;
if(check(mid)){
ans=mid;
r=mid-1;
}
else {
l=mid+1;
}
}
printf("%lld",ans);
return 0;
}
x x ≥ 1 0 n − 1 x^x\geq 10^{n-1} xx≥10n−1很简单知道可以二分答案,但 x x x^x xx太大
但我们知道,有指数可考虑取对数。 x lg x ≥ n − 1 x\lg x\geq n-1 xlgx≥n−1变简单了许多
附:如果不是以10为底数的log,可用 log a b = lg b lg a \log_{a}b=\frac {\lg b}{\lg a} logab=lgalgb表示
struct edge{
ll x,y,nx,cost;
}e[M<<1];
ll n,m,b,a[N];
ll head[N],cnt;
ll cost[N],ans;
bool vis[N];
void add_edge(ll x,ll y,ll c)
{
e[++cnt].x=x; e[cnt].y=y;
e[cnt].nx=head[x]; head[x]=cnt;
e[cnt].cost=c;
}
struct cmp{
bool operator () (ll x,ll y){
return cost[x]>cost[y];
}
};
bool check(ll k)
{
if(k<max(a[1],a[n])) return false;
priority_queue<ll,vector<ll>,cmp> q;
ll i,j;
vis[1]=true;
for(i=head[1];i;i=e[i].nx)
{
ll y=e[i].y,cc=e[i].cost;
if(cc<=b){
cost[y]=cc;
q.push(y);
}
}
while(q.size())
{
ll s=q.top(); q.pop();
if(vis[s]) continue;
vis[s]=true;
if(cost[s]>b) break;
if(s==n){
return true;
}
for(i=head[s];i;i=e[i].nx)
{
ll x=e[i].y,cc=e[i].cost;
if(vis[x]||cc+cost[s]>b||a[x]>k) continue;
if(cost[x]==0||cost[x]>cc+cost[s]){
cost[x]=cc+cost[s];
q.push(x);
}
}
}
return false;
}
int main()
{
ll i,j;
n=read(),m=read(),b=read();
for(i=1;i<=n;i++){
a[i]=read();
ans=max(a[i],ans);
}
for(i=1;i<=m;i++)
{
ll x=read(),y=read(),cc=read();
if(x==y) continue;
add_edge(x,y,cc);
add_edge(y,x,cc);
}
ll l=1,r=ans;
bool f=false;
while(l<=r)
{
clr(vis);
clr(cost);
ll mid=(l+r)>>1;
if(check(mid)){
r=mid-1;
ans=mid;
f=true;
}
else{
l=mid+1;
}
}
if(!f) printf("AFK");
else
printf("%lld",ans);
return 0;
}
这道题求的最大费用不是和!!!是路径上的max,因此用二分答案很合适。
在check时只需要用Dijkstra找伤害最少的路劲(同时最大费用不能超过k)
ll s[N][N],n,m,a[N][N],A,B,ans=inf;
bool check(ll x)
{
ll i,j;
ll sum=0,now=0;
for(i=1;i<=n;i++)
{
ll he=0,num=0;
for(j=1;j<=m;j++)
{
he+=s[i][j]-s[i][j-1]-s[now][j]+s[now][j-1];
if(he>=x)
{
he=0;
num++;
}
}
if(num>=B)
{
sum++;
now=i;
}
}
if(sum>=A) return true;
return false;
}
int main()
{
ll i,j;
n=read(),m=read(),A=read(),B=read();
for(i=1;i<=n;i++){
for(j=1;j<=m;j++){
a[i][j]=read();
s[i][j]=s[i-1][j]+s[i][j-1]-s[i-1][j-1]+a[i][j];
ans=min(a[i][j],ans);
}
}
ll l=ans,r=s[n][m];
while(l<=r)
{
ll mid=(l+r)>>1;
if(check(mid))
{
ans=mid;
l=mid+1;
}
else r=mid-1;
}
printf("%lld",ans);
return 0;
}
二分性很明显,但check有难度。先预处理二维前缀和。
在检验中,设now为之前切的在哪一行,然后扫列,每当一列连续的大于x,就 n u m + + num++ num++,并 h e = 0 he=0 he=0(因为其和已经大于检验值,不需要在往里面加入值),一直扫下去,如果 n u m ≥ b num\geq b num≥b,说明可以且 b − 1 b-1 b−1刀, s u m + + , n o w = i sum++,now=i sum++,now=i(原因和之前一样);如果 n u m < b num<b num<b,说明还需要加入下一行。
最后判断 s u m sum sum与 a a a大小即可
ll a[M],L,n,m,k;
ll ans1,ans2;
int check(ll x)
{
ll i,j;
ll s=0,res=0;
for(i=1;i<=n;i++)
{
s=max(0ll,s+a[i]);
if(s>=x) {s=0; res++;}
}
if(res==k) return 0;
if(res>k) return -1;
if(res<k) return 1;
}
int main()
{
ll i,j;
n=read(),k=read();
for(i=1;i<=n;i++){
a[i]=read();
}
bool f=false;
ll l=1,r=999999999999999;
while(l<=r)
{
ll mid=(l+r)>>1;
ll c=check(mid);
if(c==0){
ans1=mid;
r=mid-1;
f=true;
}
else if(c>0)
r=mid-1;
else
l=mid+1;
}
l=1,r=999999999999999;
while(l<=r)
{
ll mid=(l+r)>>1;
ll c=check(mid);
if(c==0){
ans2=mid;
l=mid+1;
}
else if(c>0)
r=mid-1;
else
l=mid+1;
}
if(!f) printf("-1");
else
printf("%lld %lld",ans1,ans2);
return 0;
}
这道题判断相等,因此二分比较有意思。大于说明值小了 l = m i d + 1 l=mid+1 l=mid+1,小于说明值大了 r = m i d − 1 r=mid-1 r=mid−1
贪心
寻找当前某个性质的局部最优解,进而成为全局最优解。
例题
- [JSOI2010]缓存交换(cache)
struct node{
ll id,wei;
bool operator < (const node &a) const{
return wei>a.wei;
}
}a[M];
priority_queue<ll> q;
ll ans,n,m,rank[M<<1],nxt[M],last[M];
bool vis[M];
int main()
{
ll i,j;
n=read(),m=read();
for(i=1;i<=n;i++){
a[i].wei=read();
a[i].id=i;
}
ll sum=0;
sort(a+1,a+n+1);
ll cnt=0;
a[0].wei=-19;
for(i=1;i<=n;i++)
{
if(a[i].wei!=a[i-1].wei){
rank[a[i].id]=++cnt;
}
else rank[a[i].id]=cnt;
}
for(i=1;i<=n;i++)
{
nxt[last[rank[i]]]=i;
last[rank[i]]=i;
}
for(i=1;i<=n;i++){nxt[last[rank[i]]]=n+i;rank[n+i]=rank[i];}//!!!最后一次出现
for(i=1;i<=n;i++)
{
if(vis[rank[i]]){q.push(nxt[i]); continue;}
if(sum<m)
{
sum++;
ans++;
q.push(nxt[i]);
vis[rank[i]]=true;
}
else
{
ll y=rank[q.top()],x=rank[i];
q.pop();
vis[y]=false;
vis[x]=true;
q.push(nxt[i]);
ans++;
}
}
printf("%lld",ans);
return 0;
}
本题是贪心,找最晚出现的(不是出现最少的)。
举例说明:1 2 3(cache中),还有4…3…2…1…(其中均表示其最早出现),对于4,假如换1,那么直到1出现之前,和换掉其他的比肯定不会坏,最后再换一次。如果换3 变成1 2 4,到3时,如果换4,变成 1 2 3,显然没有最开始换1好,换2(或1)变成1 3 4 就相当于最开始换2(或1),显然也不好。
struct man{
ll a,b;
}a[1010];
ll n,lena,lens,lend;
int ans[N],s[N],d[N];
void com();
void mul(ll x);
void div(ll x);
bool cmp1(man x,man y){return x.a*x.b<y.a*y.b;}
int main()
{
ll i,j;
n=read();
a[0].a=read(),a[0].b=read();
for(i=1;i<=n;i++){
a[i].a=read(),a[i].b=read();
}
sort(a+1,a+n+1,cmp1);
ll x=a[0].a;
while(x)
{
s[++lens]=x%10;
x/=10;
}
for(i=1;i<=n;i++)
{
div(a[i].b);
com();
mul(a[i].a);
}
for(i=1;i<=lena;i++)
printf("%d",ans[i]);
return 0;
}
void com()
{
ll i,j;
if(lena<lend)
{
lena=lend;
for(i=1;i<=lend;i++)
ans[i]=d[i];
}
else if(lena==lend){
bool f=false;
for(i=1;i<=lend;i++)
{
if(ans[i]>d[i]) break;
if(ans[i]<d[i]) {f=true; break;}
}
if(f)
{
for(i=1;i<=lend;i++)
ans[i]=d[i];
}
}
}
void mul(ll x)
{
ll i,j,c=0;
for(i=1;i<=lens;i++)
{
s[i]*=x;
s[i]+=c;
c=s[i]/10;
s[i]=s[i]-c*10;
}
while(c)
{
s[++lens]=c%10;
c/=10;
}
}
void div(ll x)
{
ll ss=0,i,j;
lend=0;
bool f=false;
for(i=lens;i>=1;i--)
{
ss*=10;
ss+=s[i];
if(ss>=x)
{
d[++lend]=ss/x;
ss%=x;
f=true;
}
else if(f)
{
d[++lend]=0;
}
}
}
贪心找 l [ i ] ∗ r [ i ] l[i]*r[i] l[i]∗r[i]最小的。证明:
看相邻的两个,他们俩的顺序不会影响他们前后的值。
设前面乘积为 x x x,一位大臣 l 1 , r 1 l_1,r_1 l1,r1,另二位大臣 l 2 , r 2 l_2,r_2 l2,r2
两种排法得到的值分别是 s 1 = m a x ( l 1 ∗ x r 1 , l 1 ∗ l 2 ∗ x r 2 ) s_1=max(\frac {l_1*x}{r_1},\frac{l_1*l_2*x}{r_2}) s1=max(r1l1∗x,r2l1∗l2∗x)
s 2 = m a x ( l 2 ∗ x r 2 , l 1 ∗ l 2 ∗ x r 1 ) s_2=max(\frac {l_2*x}{r_2},\frac{l_1*l_2*x}{r_1}) s2=max(r2l2∗x,r1l1∗l2∗x)
注意到 l 1 ∗ l 2 ∗ x r 1 > l 1 ∗ x r 1 , l 1 ∗ l 2 ∗ x r 2 > l 2 ∗ x r 2 \frac{l_1*l_2*x}{r_1}>\frac {l_1*x}{r_1},\frac{l_1*l_2*x}{r_2}>\frac {l_2*x}{r_2} r1l1∗l2∗x>r1l1∗x,r2l1∗l2∗x>r2l2∗x
不妨另 s 2 ≥ s 1 s_2\geq s_1 s2≥s1(另一种情况一样)
显然 s 2 = l 1 ∗ l 2 ∗ x r 1 ≥ l 1 ∗ l 2 ∗ x r 2 s_2=\frac{l_1*l_2*x}{r_1}\geq \frac{l_1*l_2*x}{r_2} s2=r1l1∗l2∗x≥r2l1∗l2∗x
得到 l 2 r 2 ≥ l 1 r 1 l_2r_2\geq l_1r_1 l2r2≥l1r1
这道题要用高精,高精比较,高精×低精,高精除低精。(出发为简单,用小端存储)。