Educational Codeforces Round 61题解
A题
首先)(其实相当于1个即可
所以如果1和4相等并且大于等于1即可
#include<bits/stdc++.h> using namespace std; typedef long long ll; typedef pair<int,int> pll; const int N=5e5+10; int main(){ ios::sync_with_stdio(false); int a,b,c,d; cin>>a>>b>>c>>d; c=min(c,1); if(a==d&&a>=c){ cout<<1<<endl; } else{ cout<<0<<endl; } }
B题
直接找到K大数就行
#include<bits/stdc++.h> using namespace std; typedef long long ll; typedef pair<int,int> pll; const int N=5e5+10; int n,m; int a[N],b[N]; int main(){ ios::sync_with_stdio(false); cin>>n; int i; ll sum=0; for(i=1;i<=n;i++){ cin>>a[i]; sum+=a[i]; } sort(a+1,a+1+n); reverse(a+1,a+1+n); cin>>m; for(i=1;i<=m;i++){ cin>>b[i]; } for(i=1;i<=m;i++){ cout<<sum-a[b[i]]<<endl; } }
C题
首先发现就是删掉两个,因此直接枚举两个人
写了发线段树出了点小问题,后来改成直接前缀和维护1的个数,因为只有区间内的1才会消失
#include<bits/stdc++.h> using namespace std; typedef long long ll; typedef pair<int,int> pll; const int N=2e5+10; int cnt[N]; int sum[N]; struct node{ int l,r; }s[N]; bool cmp(node a,node b){ if(a.l==b.l) return a.r<b.r; return a.l<b.l; } int main(){ ios::sync_with_stdio(false); int n,m; cin>>n>>m; int i,j; for(i=1;i<=m;i++){ cin>>s[i].l>>s[i].r; for(j=s[i].l;j<=s[i].r;j++) cnt[j]++; } sort(s+1,s+1+m,cmp); int ans=0; for(i=1;i<=m;i++){ for(j=s[i].l;j<=s[i].r;j++){ cnt[j]--; } int tmp=0; for(j=1;j<=n;j++){ if(cnt[j]==1){ sum[j]=sum[j-1]+1; } else{ sum[j]=sum[j-1]; } if(cnt[j]) tmp++; } for(j=i+1;j<=m;j++){ ans=max(ans,tmp-sum[s[j].r]+sum[s[j].l-1]); } for(j=s[i].l;j<=s[i].r;j++) cnt[j]++; } cout<<ans<<endl; }
D题
这次二分答案是肯定的,在check的时候,其实刚开始想的是复杂度低的正解,也就是找到每个点最晚什么时候要+,之后不断往后跳到K位置,之后找前缀和,如果每个i的前缀和大于i,说明前i个时间点
要大于i的次数充电,那么显然是非法的,这里用了一个贪心+前缀和,可惜写的时候没想清楚,没写出来这个写法,主要是在找到第一次最晚后,不知道下面继续怎么做
另一种用优先队列维护的姿势比较好想,就是每次找到能撑天数最少的点充电,如果发现不行就返回false,这种写法复杂度较高,理论上能过,但是需要一些优化的姿势,我是卡过去的
#include<bits/stdc++.h> using namespace std; typedef long long ll; typedef pair<int,int> pll; const int N=2e5+10; int n,k; ll a[N],b[N]; struct node{ ll x,y; ll k; bool operator <(const node &t) const{ if(k!=t.k) return k>t.k; if(y!=t.y) return y>t.y; return x>t.x; } }; bool check(ll x){ priority_queue<node> q; int i; for(i=1;i<=n;i++){ ll tmp=a[i]/b[i]; q.push({a[i],b[i],tmp}); } if(q.empty()) return true; for(i=1;i<=k;i++){ auto t=q.top(); q.pop(); if(t.k<i-1) return false; if(t.k>k) return true; q.push({t.x+x,t.y,(t.x+x)/t.y}); } return true; } int main(){ ios::sync_with_stdio(false); cin>>n>>k; int i; for(i=1;i<=n;i++) cin>>a[i]; for(i=1;i<=n;i++) cin>>b[i]; ll l=0,r=2e12; while(l<r){ ll mid=l+r>>1; if(check(mid)) r=mid; else l=mid+1; } if(l==2e12) cout<<-1<<endl; else cout<<l<<endl; }
#include<bits/stdc++.h> using namespace std; typedef long long ll; typedef pair<int,int> pll; const int N=2e5+10; int n,k; ll a[N],b[N]; int cnt[N]; bool check(ll x){ memset(cnt,0,sizeof cnt); int tot=0; int i; for(i=1;i<=n;i++){ ll tmp=a[i]/b[i]; tmp+=1; ll t=a[i]; while(tmp<k&&tot<k){ cnt[tmp]++; ++tot; t+=x; tmp=(t/b[i])+1; } if(tot==k) return false; } for(i=1;i<=k;i++){ cnt[i]+=cnt[i-1]; if(cnt[i]>i) return false; } return true; } int main(){ ios::sync_with_stdio(false); cin>>n>>k; int i; for(i=1;i<=n;i++) cin>>a[i]; for(i=1;i<=n;i++) cin>>b[i]; ll l=0,r=2e12; while(l<r){ ll mid=l+r>>1; if(check(mid)) r=mid; else l=mid+1; } if(l==2e12) cout<<-1<<endl; else cout<<l<<endl; }
E题
这题确实转化比较巧妙,但是对应的思路其实以前出现过,就是找到lcm的值,我记得以前有一个青蛙跳石子也是这样的,需要牢记一下。
我们看到这个题会发现需要剪枝或者转化,并不是简单的背包dp,那么这里就有一步重要的转化,我们发现只有1-8这些数,他们的最小公倍数是840,而每个值取法都能表示为 c=840/i*p+q,where 0≤q<840/i
因此我们维护dp状态为f[i][j],表示前i个权值取到j容量可以取到最大的840个数是多少。
之后就是dp转移,然后枚举状态取max,注意不能超过最大值w
#include<bits/stdc++.h> using namespace std; typedef long long ll; typedef pair<int,int> pll; const int N=2e5+10; ll cnt[N]; ll f[10][840*8+10]; int main(){ ios::sync_with_stdio(false); ll w; memset(f,-1,sizeof f); cin>>w; int i,j,k; ll ans=0; for(i=1;i<=8;i++){ cin>>cnt[i]; ans+=cnt[i]*i; } if(ans<=w){ cout<<ans<<endl; return 0; } f[0][0]=0; for(i=1;i<=8;i++){ for(j=0;j<=840*8;j++){ if(f[i-1][j]!=-1){ ll tmp=840/i; tmp=min(tmp,cnt[i]); for(k=0;k<=tmp;k++){ f[i][j+k*i]=max(f[i][j+k*i],f[i-1][j]+(cnt[i]-k)/(840/i)); } } } } ans=0; for(i=0;i<=840*8;i++){ if(i>w) break; if(f[8][i]!=-1){ ans=max(ans,i+min(f[8][i],(w-i)/840)*840); } } cout<<ans<<endl; }
F题
比较朴素的区间dp,只要三维之后看看前后点是否相同,相同就能-1,别人的解法是中间点和末尾的比较,我们直接比较前后是一样的,虽然中间转移的时候可能状态不一样,但是结果是相等的
这是因为我们小区间的开头其实就是大区间的中间点,这和别人的转移是一样的。
前后相同就能-1就是因为可以先删中间。因为我们枚举到了所有状态,所以转移是正确的
#include<bits/stdc++.h> using namespace std; typedef long long ll; typedef pair<int,int> pll; const int N=5e5+10; int f[510][510]; int main(){ ios::sync_with_stdio(false); int n; string s; cin>>n>>s; int i,j,k; s=" "+s; memset(f,0x3f,sizeof f); for(int len=1;len<=n;len++){ for(i=1;i+len-1<=n;i++){ j=i+len-1; if(len==1){ f[i][i]=1; continue; } for(k=i;k<j;k++){ f[i][j]=min(f[i][j],f[i][k]+f[k+1][j]-(s[i]==s[j])); } } } cout<<f[1][n]<<endl; }
G题
想了有些时间,终于发现了题目的性质,但是在最后求解的时候犯糊涂了
心路历程:
正向看了一下,发现不太行,转移状态过多,那么正难则反,反向看,又结合题目当中的信息,一个点必须要连后面最接近他的比他大的点
这么一看,每个点只有一个父亲节点,而之前往前看的时候注意到每个点有很多儿子节点,那不就是树吗
于是想到单调栈建树。建完之后一个想法就呼之欲出,每个点的贡献就是给他和他的儿子长度+1,容易想到dfs序建线段树实现区间加
想到这里我又犯模糊了,真实区间又不是连续的,怎么求最大值,其实这是线段树最简单的套路,打的时候有些意识模糊。因为现在每个点的含义就是当前状态下的这个点能连出的最长路,因此只要查询tr[1].mx就能找到全部区间内的最大值
#include<bits/stdc++.h> using namespace std; typedef long long ll; typedef pair<int,int> pll; const int N=2e6+10; int n,k; int h[N],ne[N],e[N],idx; int dfn[N],id[N],sz[N],times; vector<int> ans; int rt; int q[N]; int a[N]; struct node{ int l,r; int mx; int lazy; }tr[N<<2]; void add(int a,int b){ e[idx]=b,ne[idx]=h[a],h[a]=idx++; } void dfs(int u){ int i; dfn[u]=++times; id[times]=u; sz[u]=1; for(i=h[u];i!=-1;i=ne[i]){ int j=e[i]; dfs(j); sz[u]+=sz[j]; } } void build(int u,int l,int r){ if(l==r){ tr[u]={l,r}; } else{ tr[u]={l,r}; int mid=l+r>>1; build(u<<1,l,mid); build(u<<1|1,mid+1,r); } } void pushup(int u){ tr[u].mx=max(tr[u<<1].mx,tr[u<<1|1].mx); } void pushdown(int u){ int x=tr[u].lazy; tr[u<<1].mx+=x; tr[u<<1|1].mx+=x; tr[u<<1].lazy+=x; tr[u<<1|1].lazy+=x; tr[u].lazy=0; } void modify(int u,int l,int r,int x){ if(tr[u].l>=l&&tr[u].r<=r){ tr[u].mx+=x; tr[u].lazy+=x; return ; } if(tr[u].lazy) pushdown(u); int mid=tr[u].l+tr[u].r>>1; if(l<=mid) modify(u<<1,l,r,x); if(r>mid) modify(u<<1|1,l,r,x); pushup(u); } int main(){ ios::sync_with_stdio(false); cin>>n>>k; int i; rt=n+1; memset(h,-1,sizeof h); for(i=1;i<=n;i++){ cin>>a[i]; } int hh=0,tt=-1; for(i=1;i<=n;i++){ while(hh<=tt&&a[q[tt]]<a[i]){ add(i,q[tt]); tt--; } q[++tt]=i; } while(hh<=tt){ add(rt,q[tt]); tt--; } dfs(rt); build(1,1,n+1); for(i=1;i<=k;i++){ modify(1,dfn[i],dfn[i]+sz[i]-1,1); } ans.push_back(tr[1].mx); for(i=k+1;i<=n;i++){ modify(1,dfn[i-k],dfn[i-k]+sz[i-k]-1,-1); modify(1,dfn[i],dfn[i]+sz[i]-1,1); ans.push_back(tr[1].mx); } for(auto x:ans){ cout<<x<<" "; } cout<<endl; }