19.11.17ACM集训补题
这道题会根据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; }
这道题明显是个二分啊
先确定屋子的大小,然后尽量把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\n",ans); } return 0; }
总结我一个常见错误——就是我常用++,--,+=,-=,然后就会在多次判断的题里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\n",ans); return 0; }
给出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\n",ans!=-1?ans*k:0); } return 0; }
个人认为是道综合性很强的图论。虽然看别人题解说是简单图论,可是代码还是很老实的...
要对构造图很了解才行
#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\n",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; }
以下是注意:
1建立双向边的时候,可以设置边的序号是0,1或2,3,这样边的两个结点/2+1就都变成1或2了
2 1e5的输入,有可能两个点都不一样,结果造成2e5的数据,我就这样WA了好几次
看了好久终于看懂了,说说自己对题解的理解吧
首先题意是选择[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\n"); 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\n",p,l,r,(int)ans.size()); for(int id:ans)printf("%d ",id); } return 0; }