2021.8.5考试总结[NOIP模拟31]
暴力打满直接rk3?
T1 Game
想了一万种贪心和两万种$hack$。
可以先用最显然的贪心求出最高得分是多少。(从小到大用最小的大于$b_i$的$a$得分)
然后用一棵权值线段树维护值域内$a$和$b$的个数,发现两个儿子合并对答案的贡献就是$min(a_r,b_l)$。(要用大的$a$对小的$b$)于是最优分数可以直接在建树时算出。
对每个位置考虑放什么数可以令最优分数不下降,发现它是单调的。于是可以二分。具体是把$mid$值删去后检查一遍最优分数有无下降。
对$a_mid$是否大于$b_i$要分类讨论,如果$a_mid$大于$b_i$,检查时要将最终答案$+1$,因为当前位置会贡献$1$。
找到该位答案后把$a_mid$和$b_i$删掉,对最优答案$-1$,使它不会对之后的答案产生影响。
挺妙的,在线段树操作中自动修改最优分数。
$code:$
1 #include<bits/stdc++.h> 2 #define ld rt<<1 3 #define rd (rt<<1)|1 4 #define rin register signed 5 using namespace std; 6 const int NN=1e5+5; 7 int n,a[NN],pre[NN],b[NN],ans[NN],an[NN],bota[NN],k,it,botb[NN],tk; 8 inline int read(){ 9 int x=0,f=1; char ch=getchar(); 10 while(ch<'0'||ch>'9'){ if(ch=='-') f=-1; ch=getchar(); } 11 while(ch>='0'&&ch<='9'){ x=(x<<1)+(x<<3)+(ch^48); ch=getchar(); } 12 return x*f; 13 } 14 inline void write(int x){ 15 char ch[20]; int len=0; 16 if(x<0) putchar('-'), x=~x+1; 17 do{ 18 ch[len++]=x%10+(1<<5)+(1<<4); 19 x/=10; 20 }while(x); 21 for(rin i=len-1;i>=0;--i) putchar(ch[i]); 22 return; 23 } 24 struct segment_tree{ 25 int suma[NN<<2],sumb[NN<<2],l[NN<<2],r[NN<<2]; 26 void pushup(int rt){ 27 int tmp=min(suma[rd],sumb[ld]); tk+=tmp; 28 suma[rt]=suma[ld]+suma[rd]-tmp; 29 sumb[rt]=sumb[ld]+sumb[rd]-tmp; 30 } 31 void build(int rt,int opl,int opr){ 32 l[rt]=opl; r[rt]=opr; 33 if(opl==opr){ 34 suma[rt]=bota[opl]; 35 sumb[rt]=botb[opl]; 36 return; 37 } 38 int mid=opl+opr>>1; 39 build(ld,opl,mid); 40 build(rd,mid+1,opr); 41 pushup(rt); 42 } 43 void update(int rt,int pos){ 44 if(l[rt]==r[rt]){ 45 suma[rt]=bota[pos]; 46 sumb[rt]=botb[pos]; 47 return; 48 } 49 int mid=l[rt]+r[rt]>>1; 50 tk-=min(sumb[ld],suma[rd]); 51 if(pos<=mid) update(ld,pos); 52 else update(rd,pos); 53 pushup(rt); 54 } 55 }s; 56 bool check(int mid,int op){ 57 bota[mid]--; s.update(1,mid); 58 int res=tk+op; 59 bota[mid]++; s.update(1,mid); 60 return res==k; 61 } 62 signed main(){ 63 n=read(); rin atmp=0; 64 for(rin i=1;i<=n;++i){ b[i]=read(); botb[b[i]]++; } 65 for(rin i=1;i<=n;++i){ int t=read(); bota[t]++; it=max(it,t); } 66 s.build(1,1,it); k=tk; 67 for(rin i=1;i<=n;i++){ 68 botb[b[i]]--; s.update(1,b[i]); 69 while(!bota[it]&&it) it--; 70 int l=b[i]+1,r=it,pos=0; 71 while(l<=r){ 72 int mid=l+r>>1; 73 if(check(mid,1)) l=mid+1, pos=mid; 74 else r=mid-1; 75 } 76 if(!pos){ 77 l=0; r=b[i]; 78 while(l<=r){ 79 int mid=l+r>>1; 80 if(check(mid,0)) l=mid+1, pos=mid; 81 else r=mid-1; 82 } 83 } 84 else k--; 85 bota[pos]--; s.update(1,pos); ans[i]=pos; 86 } 87 for(int i=1;i<=n;i++) write(ans[i]), putchar(' '); 88 putchar('\n'); 89 return 0; 90 }
T2 Time
是个贪心。每次找到序列中的最小值,把它移到左或右端点,哪近移哪。貌似可以分治。
事实上每个点移到它成为最小值时端点对答案的贡献就是它左/有大于它数的个数。树状数组即可。
(到现在我也不知道求逆序对为什么假了
$code:$
1 #include<bits/stdc++.h> 2 #define ld rt<<1 3 #define rd (rt<<1)|1 4 using namespace std; 5 const int NN=1e5+5; 6 int n,p[NN],ans,c[NN],pre[NN],suf[NN]; 7 inline int read(){ 8 int x=0,f=1; char ch=getchar(); 9 while(ch<'0'||ch>'9'){ if(ch=='-') f=-1; ch=getchar(); } 10 while(ch>='0'&&ch<='9'){ x=(x<<1)+(x<<3)+(ch^48); ch=getchar(); } 11 return x*f; 12 } 13 inline void write(int x){ 14 char ch[20]; int len=0; 15 if(x<0) putchar('-'), x=~x+1; 16 do{ 17 ch[len++]=x%10+(1<<5)+(1<<4); 18 x/=10; 19 }while(x); 20 for(int i=len-1;i>=0;i--) putchar(ch[i]); 21 return; 22 } 23 inline int lowbit(int x){ return x&(-x); } 24 inline void insert(int pos){ 25 while(pos<100000){ 26 c[pos]++; 27 pos+=lowbit(pos); 28 } 29 } 30 inline int query(int pos){ 31 int res=0; 32 while(pos){ 33 res+=c[pos]; 34 pos-=lowbit(pos); 35 } 36 return res; 37 } 38 signed main(){ 39 n=read(); 40 for(int i=1;i<=n;i++) 41 p[i]=read(); 42 for(int i=1;i<=n;i++){ 43 pre[i]=query(100000)-query(p[i]); 44 insert(p[i]); 45 } 46 memset(c,0,sizeof(c)); 47 for(int i=n;i;i--){ 48 suf[i]=query(100000)-query(p[i]); 49 insert(p[i]); 50 } 51 for(int i=1;i<=n;i++) 52 ans+=min(pre[i],suf[i]); 53 write(ans); putchar('\n'); 54 return 0; 55 }
T3 Cover
是道神题。首先区间不交或包含,可以转化成树上问题。
暴力$DP$挺简单的,定义$f_{i,j}$为$i$子树中最多覆盖$j$层,有:
$f_{i,j}=max(max(\sum f_{son,j-1}+w_i,\sum f_{son,j}),f_{i,j-1})$
然后时空$O(n^2)$全炸了。
注意到很多计算是重复或无用的,而$f_{i,j}$在$i$相同时单调不减,可以直接维护差分。
而因为维护的是最优策略,可以发现差分值也单调不增,然后考虑$\sum f_{son,j-1}+w_i$什么时候能成为最优答案。
首先差分意义下,初始的$f_{i,j}=\sum f_{son,j}$。
那么有$f_{i,j}<f_{i,j-1}+w_i\Rightarrow w_i>f_{i,j}-f_{i,j-1}$,即$w_i$大于$f_{i,j}$与$f_{i,j-1}$的差分值。
于是可以直接用保证数据单调的数据结构(堆,$multiset$)维护差分值,子树合并后直接将$w_i$插入即可。
$code:$
1 #include<bits/stdc++.h> 2 #define int long long 3 #define pb push_back 4 using namespace std; 5 const int NN=3e5+5; 6 int n,m,res; 7 vector<int>son[NN]; 8 struct node{ 9 int l,r,a; 10 bool operator<(const node& s)const{ return l<s.l||(l==s.l&&r>s.r); } 11 }d[NN]; 12 multiset<int>f[NN]; 13 stack<int>stk; 14 inline int read(){ 15 int x=0,f=1; char ch=getchar(); 16 while(ch<'0'||ch>'9'){ if(ch=='-') f=-1; ch=getchar(); } 17 while(ch>='0'&&ch<='9'){ x=(x<<1)+(x<<3)+(ch^48); ch=getchar(); } 18 return x*f; 19 } 20 inline void write(int x){ 21 char ch[20]; int len=0; 22 if(x<0) putchar('-'), x=~x+1; 23 do{ 24 ch[len++]=x%10+(1<<5)+(1<<4); 25 x/=10; 26 }while(x); 27 for(int i=len-1;i>=0;i--) putchar(ch[i]); 28 return; 29 } 30 void merge(multiset<int> &a,multiset<int> &b){ 31 if(a.size()<b.size()) swap(a,b); 32 vector<int>tmp; 33 for(auto x:b){ tmp.pb(x+*a.begin()); a.erase(a.begin()); } 34 for(auto t:tmp) a.insert(t); 35 } 36 void dfs(int s){ 37 for(auto to:son[s]){ 38 dfs(to); 39 merge(f[s],f[to]); 40 } 41 f[s].insert(-d[s].a); 42 } 43 signed main(){ 44 n=read(); m=read(); 45 for(int i=1;i<=m;i++) 46 d[i].l=read(), d[i].r=read(), d[i].a=read(); 47 sort(d+1,d+m+1); 48 stk.push(0); 49 for(int i=1;i<=m;i++){ 50 while(stk.top()!=0&&d[stk.top()].r<d[i].r) stk.pop(); 51 son[stk.top()].pb(i); 52 stk.push(i); 53 } 54 dfs(0); 55 for(int i=1;i<=m;i++){ 56 if(f[0].size()) 57 res-=*f[0].begin(), f[0].erase(f[0].begin()); 58 write(res); putchar(' '); 59 } putchar('\n'); 60 return 0; 61 }