【简要题解】CF 737 Div2 简要题解
【简要题解】CF 737 Div2 简要题解
退役一年之后还能上分?
A - Ezzat and Two Subsequences
首先\(O(n)\)枚举分成的两个序列的大小分别是多少假设分别是\(x\)和\(n-x\)
数学直觉告诉我,小的和小的一起,大的和大的一起,所以我们把前\(x\)大放在一起,剩下的放在一起就行
题解给了证明:
The average of a group of numbers always has a value between the minimum and maximum numbers in that group.
这就很显然了
//@winlere
#include<bits/stdc++.h>
using namespace std;
int qr(){
int ret=0,c=getchar(),f=0;
while(!isdigit(c)) f=c=='-',c=getchar();
while( isdigit(c)) ret=ret*10+c-48,c=getchar();
return f?-ret:ret;
}
const int maxn=1e5+5;
double data[maxn],pre[maxn],suf[maxn];
int main(){
ios::sync_with_stdio(0);
int T=qr();
while(T--){
int n=qr();
for(int t=1;t<=n;++t) data[t]=qr();
sort(data+1,data+n+1);
for(int t=1;t<=n;++t) pre[t]=pre[t-1]+data[t];
suf[n+1]=0;
for(int t=n;t;--t) suf[t]=suf[t+1]+data[t];
double ans=-2e9;
for(int t=1;t<n;++t) ans=max(ans,pre[t]/t+suf[t+1]/(n-t));
cout<<fixed<<setprecision(10)<<ans<<endl;
}
return 0;
}
B - Moamen and k-subarrays
离散化后直接看非连续段的个数,很难说比A题难
//@winlere
#include<iostream>
#include<cstdio>
#include<cstring>
#include<algorithm>
using namespace std;
int qr(){
int ret=0,c=getchar(),f=0;
while(!isdigit(c)) f=c=='-',c=getchar();
while( isdigit(c)) ret=ret*10+c-48,c=getchar();
return f?-ret:ret;
}
const int maxn=1e5+5;
int data[maxn],sav[maxn];
int main(){
int T=qr();
while(T--){
int n=qr(),k=qr(),cnt=0;
for(int t=1;t<=n;++t) sav[t]=data[t]=qr();
sort(sav+1,sav+n+1);
for(int t=1;t<=n;++t) data[t]=lower_bound(sav+1,sav+n+1,data[t])-sav;
data[n+1]=-114514;
for(int t=1,r=1;t<=n;t=++r){
while(data[r+1]==data[r]+1) ++r;
++cnt;
}
puts(cnt<=k?"YES":"NO");
}
return 0;
}
C - Moamen and XOR
这道题深刻地揭示了退役多年的我已经变得多么菜了
从二进制高位到低位考虑问题的,高位比较出结果低位随便填了。
设\(f(n)\)表示有\(n\)个\(bit\),这些\(bit\)的异或和\(=\)并起来的和
设\(g(n)\)表示有\(n\)个\(bit\),这些\(bit\)异或和\(>\)并起来的和
显然:
只需要知道\(2^{n-1}\)是怎么来的
在\(n\)个二进制位中,选中奇数个使其为一的方案数=选中偶数个使其为一的。这个结论的证明是,可以构造一一对应的关系:将这些二进制位中的第一个给翻转一下得到偶与奇的对应。
其余的\(+1,-1\)是考虑全\(1\)或全\(0\)的情况
然后枚举是再哪一位比出个\(>\)的结果出来。注意到因为原题是\(\ge\),所以还要加一个全部位都相等的情况。
还要注意\(k=0\)
//@winlere
#include<iostream>
#include<cstdio>
#include<cstring>
#include<algorithm>
#include<vector>
using namespace std;
int qr(){
int ret=0,c=getchar(),f=0;
while(!isdigit(c)) f=c=='-',c=getchar();
while( isdigit(c)) ret=ret*10+c-48,c=getchar();
return f?-ret:ret;
}
const int mod=1e9+7;
const int maxn=2e5+5;
typedef long long ll;
int f[maxn],g[maxn];
int MOD(const int&a,const int&b){return 1ll*a*b%mod;}
int MOD(const int&a){return a%mod;}
int MOD(vector<int> ve){int ret=1;for(auto t:ve) ret=MOD(ret,t); return ret;}
int ksm(const int&ba,const ll&p){
int ret=1;
for(ll t=p,b=ba;t;t>>=1,b=MOD(b,b))
if(t&1) ret=MOD(ret,b);
return ret;
}
void pre(int n){
f[1]=2;
for(int t=2;t<=n;++t)
if(t&1) f[t]=ksm(2,t-1)+1;
else f[t]=ksm(2,t-1)-1,g[t]=1;
}
int main(){
ios::sync_with_stdio(0);
pre(2e5);
int T=qr();
while(T--){
int n=qr(),k=qr(),ans=0;
if(k) {
for(int t=k;t;--t){
int ret=MOD({ksm(f[n],k-t),g[n],ksm(2,1ll*(t-1)*n)});
ans=MOD(ans+ret);
}
ans=MOD(ans+ksm(f[n],k));
}else ans=1;
cout<<ans<<endl;
}
return 0;
}
D - Ezzat and Grid
线段树都写错,我不配叫OIer((
问题转化为图论问题,将满足相邻条件的两行由序数小的向序数大的连一条有向边,问题转化为最长路问题
然而这个复杂度是\(O(n^2)\)的,考虑优化,首先一个问题,如果\(1->2\)合理,并且\(2->3\)合理,并且\(1->3\)合理,那么我们不需要连\(1->3\)了。
考虑什么时候\(1->2,2->3,1->3\)都合理,当且仅当他们有某个相同位置都有"1"
离散化所有位置信息,用线段树维护"在这个位置有'1'的编号最大的行的编号",这个用线段树轻松维护。
这个时候就有人问了,你修改的时候可以用lazy tag复杂度是对的,但是你查询的时候复杂度不是\(O(值域)\)了吗
并非如此,每个我们可以记录一个\(dat\in[0,n]\)表示当前区间是全为一个数,若有则这个数是多少。
考虑那些\(dat=0\)的节点,这是因为某个位置的编号和周围不一样,这个打破统一的情况只会在修改区间的端点附近产生(这个最开始我没考虑到,之前居然分析成\(O(\log^2m)\)我真的太菜了...),可以发现一次修改操作最多产生$ \log m$个这样的区间
而我们查询的时候,每次遇到\(dat\),我们就递归下去,而且因为修改的区间和查询的区间一样,这个时候\(dat\)更新了,所以相当于查到一个\(dat=0\)就把这个\(dat\)修改成有值了。
复杂度\(O(m\log m)\)
//@winlere
#include<iostream>
#include<cstdio>
#include<cstring>
#include<algorithm>
#include<map>
#include<vector>
using namespace std;
int qr(){
int ret=0,c=getchar(),f=0;
while(!isdigit(c)) f=c=='-',c=getchar();
while( isdigit(c)) ret=ret*10+c-48,c=getchar();
return f?-ret:ret;
}
const int maxn=3e5+5;
vector<int> e[maxn];
int n,m,sav[maxn<<1],cnt,dp[maxn],usd[maxn];
void add(int fr,int to){
if(fr<=0||to<=0||to==fr) return;
e[fr].push_back(to);
}
struct QWQ{
int i,l,r;
bool operator < (const QWQ&a)const{
return i<a.i;
}
}data[maxn];
#define mid ((l+r)>>1)
#define lef l,mid,pos<<1
#define rgt mid+1,r,pos<<1|1
struct XDS{
int tag,dat;//删你麻痹的uni去死吧!
void upd(int x){tag=dat=x;}
}seg[maxn<<4];
void pd(int pos){
if(!seg[pos].tag) return;
seg[pos<<1].upd(seg[pos].tag);
seg[pos<<1|1].upd(seg[pos].tag);
seg[pos].tag=0;
}
void pp(int pos){
if(seg[pos<<1].dat==seg[pos<<1|1].dat&&seg[pos<<1].dat>0)
seg[pos].dat=seg[pos<<1].dat;
else seg[pos].dat=0;
}
void que(int to,int L,int R,int l,int r,int pos){
if(L>r||R<l) return;
if(seg[pos].dat||l==r) return add(seg[pos].dat,to);
pd(pos);
que(to,L,R,lef); que(to,L,R,rgt);//线段树都不会写,逻辑发生巨大错误,upd的时候不管了。。。。写下代码的时候要想想啊!
pp(pos);
}
void upd(int to,int L,int R,int l,int r,int pos){
if(L>r||R<l) return;
if(L<=l&&r<=R) return seg[pos].upd(to);
pd(pos);
upd(to,L,R,lef); upd(to,L,R,rgt);
pp(pos);
}
void dfs(int now){
if(dp[now]) return;
dp[now]=1;
for(auto t:e[now])
if(t^now)
dfs(t),dp[now]=max(dp[now],dp[t]+1);
}
void getAns(int now){
usd[now]=1;
for(auto t:e[now])
if(dp[t]+1==dp[now])
return getAns(t);
}
int main(){
n=qr(); m=qr();
for(int t=1;t<=m;++t) data[t].i=qr(),sav[++cnt]=data[t].l=qr(),sav[++cnt]=data[t].r=qr();
sort(sav+1,sav+cnt+1);
cnt=unique(sav+1,sav+cnt+1)-sav-1;
for(int t=1;t<=m;++t) data[t].l=lower_bound(sav+1,sav+cnt+1,data[t].l)-sav,data[t].r=lower_bound(sav+1,sav+cnt+1,data[t].r)-sav;
sort(data+1,data+m+1);
for(int t=1;t<=m;++t)
que(data[t].i,data[t].l,data[t].r,1,cnt,1),upd(data[t].i,data[t].l,data[t].r,1,cnt,1);
for(int t=1;t<=n;++t) dfs(t);
int ans=0;
for(int t=1;t<=n;++t) ans=max(ans,dp[t]);
printf("%d\n",n-ans);
for(int t=1;t<=n;++t)
if(dp[t]==ans)
getAns(t),t=n;
for(int t=1;t<=n;++t)
if(!usd[t])
printf("%d ",t);
if(n-ans) putchar('\n');
return 0;
}