19.11.17ACM集训补题

CodeForces - 1250A

这道题会根据a数组直接对a[i]进行操作,可能与前面交换,可能位置不变。

然后要你求出每个值最小和最大的位置


都说做题前要分析,但是我看了几遍还是没想到用什么算法简单

但其实是我不会分析

因为模拟也是种做法,并且按题意模拟的话,也不过是遍历a数组的操作,仅仅O(m)的复杂度而已。这就是想复杂了,要把模拟加入考虑,并且判断复杂度能不能过

#include<iostream>
#include<string.h>
#include<stdio.h>
using namespace std;
const int maxn=1e5+7;
int n,m,a[maxn*4],l[maxn],h[maxn],num[maxn],pos[maxn];
///数字i的位置是pos[i],位置i的数字是num[i]
int main(){
    scanf("%d%d",&n,&m);
    for(int i=1;i<=m;i++)
        scanf("%d",&a[i]);
    for(int i=1;i<=n;i++)
        l[i]=h[i]=num[i]=pos[i]=i;
    for(int i=1;i<=m;i++){
        int psi=pos[a[i]];
        if(psi==1)continue;
        int t=num[psi-1];///t是前一个位置的数字
        pos[a[i]]=psi-1;
        pos[t]=psi;
        num[psi]=t;
        num[psi-1]=a[i];
        l[a[i]]=min(l[a[i]],psi-1);
        h[t]=max(h[t],psi);
    }
    for(int i=1;i<=n;i++)
        cout<<l[i]<<' '<<h[i]<<endl;
    return 0;
}
View Code

CodeForces - 1250L

这道题明显是个二分啊

先确定屋子的大小,然后尽量把a或c单独放一个屋子

如果有一个屋子最后还是同时放了a和c,那就失败

#include<iostream>
#include<string.h>
#include<stdio.h>
using namespace std;
int ce,a,b,c;
bool check(int mid){
    if(a>mid&&c>mid)return false;
    if(a<=mid&&c<=mid)return true;
    if(a>mid)
        return a<=mid*2;
    if(c>mid)
        return c<=mid*2;
}
int main(){
    scanf("%d",&ce);
    while(ce--){
        scanf("%d%d%d",&a,&b,&c);
        int l=(a+b+c-1)/3+1,r=max(a,max(b,c)),ans;
        while(l<=r){
            int mid=(l+r)>>1;
            if(check(mid)){
                ans=mid;
                r=mid-1;
            }
            else l=mid+1;
        }
        printf("%d
",ans);
    }
    return 0;
}
View Code

总结我一个常见错误——就是我常用++,--,+=,-=,然后就会在多次判断的题里WA

 
这道题题意是选择车厢大小s,保证s每次至少能装一个队最多装两个队,然后运r次运完
得到的花费s*r最小

我前面做了一道二分,然后看这道题也像二分
不过明显这道题判断条件不是成功与否,而是花费多少
所以我果断地选择了三分答案,让LR逼近花费少的答案,然后WA
为什么会WA呢,因为我即使能通过知道s算出需要的花费,但是没考虑到花费并不与s成单调关系
比如3 2 3 用3的车厢运3次花费9,而4的车厢运3次花费12,5的车厢运两次花费10
所以三分是不行的(虽然也过了20多个样例)
然后看了题解,这道题应该是贪心
贪心策略是尽量把人数多的队和人数少的队放一起,使每个车厢的人差不多,然后把人数特别多的队单独放一个车厢。理想状态是有多少人就花费多少
#include<iostream>
#include<string.h>
#include<stdio.h>
#include<algorithm>
using namespace std;
const int maxk=8007;
const int inf=0x7fffffff;
int n,m,k,pep[maxk],maxroom;
int main(){
    scanf("%d%d",&n,&k);
    for(int i=0;i<n;i++){
        scanf("%d",&m);
        pep[m]++;
    }
    sort(pep+1,pep+1+k);
    if(k==1)maxroom=pep[1];
    else{
        for(int i=1,j=k;j-i>=1;i++,j--)
            maxroom=max(maxroom,pep[i]+pep[j]);
    }
    int ans=inf;
    for(int i=pep[k];i<=maxroom;i++){
        int l=1,h=k,r=0;
        while(l<=h){
            if(pep[l]+pep[h]<=i)
                l++,h--;
            else h--;
            r++;
        }
        ans=min(ans,i*r);
    }
    printf("%d
",ans);
    return 0;
}
View Code

CodeForces - 1250J

给出1到N的身高的人的个数,想排成k排,每排的身高最低最高差距不大于1,每排人数一样,问最多参与排队的人的个数


我就是做了这道和上上道都是二分,然后才以为是专题,然后用二分做上道题的
这道题用二分,先确定一排的人数,然后看这样排能不能排,然后得到k排条件下,一排最多的人数
#include<iostream>
#include<string.h>
#include<stdio.h>
using namespace std;
typedef long long LL;
const int maxn=3e4+7;
int ce,n;
LL k,c[maxn],sum,l,r,ans;
bool check(LL mid){
    LL numk=0,lef=0;
    for(int i=1;i<=n;i++){
        LL t=(lef+c[i])/mid;
        numk+=t;
        if(numk>=k)return true;
        lef=lef>=t*mid?c[i]:c[i]+lef-t*mid;
    }
    return false;
}
int main(){
    scanf("%d",&ce);
    while(ce--){
        scanf("%d%lld",&n,&k);
        sum=0;
        for(int i=1;i<=n;i++){
            scanf("%lld",&c[i]);
            sum+=c[i];
        }
        l=1,r=sum/k,ans=-1;
        while(l<=r){
            LL mid=(l+r)>>1;
            if(check(mid)){
                ans=mid;
                l=mid+1;
            }
            else r=mid-1;
        }
        printf("%lld
",ans!=-1?ans*k:0);
    }
    return 0;
}
View Code

CodeForces - 1250N

个人认为是道综合性很强的图论。虽然看别人题解说是简单图论,可是代码还是很老实的...

要对构造图很了解才行

题解

#include<bits/stdc++.h>
using namespace std;
typedef pair<int,int>P;
const int maxn=1e5+7;
struct edge{
    int to,next;
}e[maxn*2];
vector<int>v;
int ce,n,len,tot,sz,head[maxn*2],wire[maxn*2],fr[maxn*2],ed[maxn*2],num[maxn*2];
P p[maxn];
bool vis[maxn*2];
void add(int u,int v){
    e[tot].next=head[u];
    e[tot].to=v;
    head[u]=tot++;
}
void dfs(int u,int fa){
    vis[u]=true;
    for(int i=head[u];i!=-1;i=e[i].next){
        int v=e[i].to;
        if(!vis[v]){
            ed[sz]=v;
            dfs(v,u);
        }
    }
}
int main(){
    scanf("%d",&ce);
    while(ce--){
        scanf("%d",&n);
        v.clear();tot=sz=0;
        memset(vis,false,sizeof(vis));
        memset(head,-1,sizeof(head));
        for(int i=1;i<=n;i++){
            scanf("%d%d",&p[i].first,&p[i].second);
            v.push_back(p[i].first);
            v.push_back(p[i].second);
        }
        sort(v.begin(),v.end());
        v.erase(unique(v.begin(),v.end()),v.end());
        len=unique(v.begin(),v.end())-v.begin();
//        for(int i=0;i<v.size();i++)
//            cout<<v[i]<<' ';
        for(int i=1;i<=n;i++){
            int id1=lower_bound(v.begin(),v.end(),p[i].first)-v.begin();
            num[id1]=p[i].first;
            int id2=lower_bound(v.begin(),v.end(),p[i].second)-v.begin();
            num[id2]=p[i].second;
            wire[id1]=tot;
            add(id1,id2);
            wire[id2]=tot;
            add(id2,id1);
        }
//        cout<<num[2]<<endl;
        for(int i=0;i<len;i++)
        if(!vis[i]){
//            cout<<num[i]<<endl;
//            cout<<i<<endl;
            sz++;
            fr[sz]=i;
            dfs(i,-1);
        }
        printf("%d
",sz-1);
//        for(int i=1;i<=sz;i++)
//            cout<<num[fr[i]]<<' ';
//        cout<<endl;
        for(int i=1;i<sz;i++)
            cout<<wire[ed[i]]/2+1<<' '<<num[ed[i]]<<' '<<num[fr[i+1]]<<endl;
    }
    return 0;
}
View Code

以下是注意:

1建立双向边的时候,可以设置边的序号是0,1或2,3,这样边的两个结点/2+1就都变成1或2了

2 1e5的输入,有可能两个点都不一样,结果造成2e5的数据,我就这样WA了好几次

CodeForces - 1250C

看了好久终于看懂了,说说自己对题解的理解吧

首先题意是选择[L,R]的区间,使得所有L<=Li,Ri<=R的计划报酬之和减去(R+1-L)*K最大

事关区间,我能想到贪心和背包

如果这里的有时间重叠的计划不能同时做的话,我会去考虑背包,但是不是,所以背包排除

而如果把计划的开始时间排个序,好像也不能找到贪心策略,毕竟报酬多的计划也可能区间比较长,反而性价比不高

让人没想到的是选择用线段树


其实这里算报酬增加的钱是比算区间减去的时间要麻烦的
因为减的时候每天固定减k,需要考虑的是区间长度
而加的时候区间固定加p,但是要考虑加在哪个区间
给这个区间加p,怎么加?用差分,左端点加p,右端点减p?
其实可以转换一下,直接以每个区间作为处理的最小单位
就像区间更新减k的时候,我们平时是给线段树减去k*(r+1-l)的
而如果线段树最底端放的是各个区间的话,就是直接把这个区间减k就行了
这样原来不知道怎么处理加p,就变成了
给这个区间加p再减去(r+1-l)个k了
所以我觉得用线段树的主要目的是处理加p
#include<iostream>
#include<stdio.h>
#include<string.h>
#include<vector>
#include<bits/stdc++.h>
using namespace std;
typedef long long LL;
const int maxn=2e5+7;
typedef pair<LL,int>pi;
typedef tuple<LL,int,int>tp;
int n,l,r;
LL k,p,lazy[maxn<<2];
vector<tp>v[maxn];
vector<int>ans;
pi st[maxn<<2];
void pushdown(int rt){
    if(lazy[rt]){
        lazy[rt<<1]+=lazy[rt],lazy[rt<<1|1]+=lazy[rt];
        st[rt<<1].first+=lazy[rt],st[rt<<1|1].first+=lazy[rt];
        lazy[rt]=0;
    }
}
void pushup(int rt){
    st[rt]=max(st[rt<<1],st[rt<<1|1]);
}
void build(int l,int r,int rt){
    if(l==r){
        st[rt]=make_pair(0LL,l);
        return;
    }
    int mid=(l+r)>>1;
    build(l,mid,rt<<1);
    build(mid+1,r,rt<<1|1);
    pushup(rt);
}
void updata(int l,int r,int rt,int ll,int rr,LL d){
    if(ll<=l&&r<=rr){
//    if(l==ll&&r==rr){
        st[rt].first+=d;
        lazy[rt]+=d;
        return;
    }
    pushdown(rt);
    int mid=(l+r)>>1;
//    if(rr<=mid)updata(l,mid,rt<<1,ll,rr,d);
//    else if(ll>mid)updata(mid+1,r,rt<<1|1,ll,rr,d);
//    else {
//        updata(l,mid,rt<<1,ll,mid,d);
//        updata(mid+1,r,rt<<1|1,mid+1,rr,d);
//    }
    if(ll<=mid)updata(l,mid,rt<<1,ll,rr,d);
    if(rr>mid)updata(mid+1,r,rt<<1|1,ll,rr,d);
    pushup(rt);
}
int main(){
    scanf("%d%lld",&n,&k);
    for(int i=1;i<=n;i++){
        scanf("%d%d%lld",&l,&r,&p);
        v[r].push_back(make_tuple(p,l,i));
    }
    p=0;
    build(1,maxn,1);
    for(int i=1;i<maxn;i++){
        updata(1,maxn,1,1,i,-k);
        for(tp P:v[i])updata(1,maxn,1,1,get<1>(P),get<0>(P));
        if(st[1].first>p){
            p=st[1].first;
            r=i,l=st[1].second;
        }
    }
    if(p<=0)printf("0
");
    else{
        for(int i=l;i<=r;i++)
            for(tp P:v[i])
            if(get<1>(P)>=l)ans.push_back(get<2>(P));
        printf("%lld %d %d %d
",p,l,r,(int)ans.size());
        for(int id:ans)printf("%d ",id);
    }
    return 0;
}
View Code
原文地址:https://www.cnblogs.com/helman/p/11893989.html