怕被喷,于是写个反思。
ZJ:
马上就结束了,不管是如何……如何……
T1看看,写一个暴力,然后开始寻求规律,然后也没找出来。
T2先码了一个大暴力,然后发现可以$Theta(N^2)$dp,然后有qj了一点分。
T3先想的是把每一个MagicStone的贡献单独考虑,后来过不了大样例,发现错了,要容斥。
于是去码暴力,最后没弄完。如果写完了就有$10$分
没办法,就再补补吧。
26
|
Miemeng | 30
00:59:58
|
50
00:59:59
|
0
00:59:59
|
80
00:59:59
|
终于有题解la:
好!
T1:
大神秒切蒟蒻不会的题。
$20\%$算法:
直接归并即可。
复杂度很高:$O(NM)$
$100\%$算法:
有两个做法法法法法法法法法法法法。
法1:
我们可以粘题解通过当前数的位置得到
这个数在另一个序列的期望位置。假设当前的数为 $x$ ,期望位置的数
为 $y$ ,下一个数为 $z$ ,那么 $z leq x leq y$ 时 $x$ 就是答案,否则比较一下大小,往两边跳。
法2:
首先我们考虑如何减小问题规模。
我们想想暴力可以怎么搞。
往一个数据结构里扔数,并维护$size$,如果正好为$k$,我们就把$k$输出来。
那前面的呢?弃掉了。
于是我们是不是可以通过弃掉前面的数来使后面的数正确呢?
显然可以。
于是我们考虑加速这个过程,一个一个弃不慢么?
那么分治的思想告诉我们要用中点以平衡复杂度。
考虑两个区间$[l_a,r_a],[l_b,r_b]$的第$frac{k}{2}$个数
如果$A_{k/2}<B_{k/2}$,那么$A_{k/2}$及之前的可以全部弃掉了。
问题转换为在$[l_a+k/2+1,r_a],[l_b,r_b]$中找第$frac{k}{2}$个数,问题规模就缩小了一半。
重复上述操作直到你去世可以一步出答案。
$A_{k/2} geq B_{k/2}$同理。
#include <iostream> #include <cstring> #include <cstdio> #define N 555555 using namespace std; int pn,arr[N],brr[N],qn; int read() { char c=getchar(); int x = 0; for(;c<'0'||c>'9';c=getchar()); for(;c>='0'&&c<='9';c=getchar())x=x*10-'0'+c; return x; } int solve(int kth,int l1,int r1,int l2,int r2){ // cout<<kth<<" "<<l1<<" "<<r1<<" "<<l2<<" "<<r2<<endl; int hlf=kth/2; if(l1>r1){ return brr[l2+kth-1]; } else if(l2>r2){ return arr[l1+kth-1]; } else if(kth == 1){ int ans=0x7fffffff; if(l1<=r1) ans=min(ans,arr[l1]); if(l2<=r2) ans=min(ans,brr[l2]); return ans; } else if(l1+hlf-1<=r1 && l2+hlf-1<=r2) { if(arr[l1+hlf-1] < brr[l2+hlf-1]) return solve(kth-hlf,l1+hlf,r1,l2 ,r2); else return solve(kth-hlf,l1 ,r1,l2+hlf,r2); } else if(l1+hlf-1<=r1){ return solve(kth-hlf,l1+hlf,r1,l2,r2); } else if(l2+hlf-1<=r2){ return solve(kth-hlf,l1,r1,l2+hlf,r2); } else { puts("µ"); return 0; } } int main(){ #ifndef LOCAL freopen("median.in" ,"r",stdin); freopen("median.out","w",stdout); #endif int opt,a,b,c,d; pn=read();qn=read(); for(int i=1;i<=pn;i++) arr[i]=read(); for(int i=1;i<=pn;i++) brr[i]=read(); for(int i=1;i<=qn;i++){ opt=read(); if(opt==1){ a=read(),b=read(),c=read(); if(a==0) arr[b]=c; else brr[b]=c; } else{ a=read(),b=read(),c=read(),d=read(); int k=(b-a+d-c+2+1)/2; printf("%d ",solve(k,a,b,c,d)); } } }
T2:
%%%toot
上面的大聚聚暴锤正解。
正解:线段树维护单调栈。复杂度:$Theta(N log N)$
巨解:单调栈自己维护自己。复杂度:$Theta(N)$
于是……
首先我们知道如何写暴力$dp$。
//min #include <iostream> #include <cstring> #include <cstdio> #define N 222222 #define LL long long using namespace std; int pn; int arr[N]; int a,b,c,d; LL dp[N]; LL f(LL x){ return x*x*x*a+x*x*b+x*c+d; } LL ans=0; namespace qj{ void work(){ bool is_upd=0; LL minn=0x7fffffff; for(int i=1;i<=pn;i++){ minn=min(1ll*arr[i],minn); if(f(arr[i])>0){ is_upd=1; ans+=f(arr[i]); } } if(!is_upd){ ans=f(minn); } cout<<ans<<endl; } } int main(){ #ifndef LOCAL freopen("min.in" ,"r",stdin); freopen("min.out","w",stdout); #endif ios_base::sync_with_stdio(false); cin>>pn>>a>>b>>c>>d; for(int i=1;i<=pn;i++) cin>>arr[i]; if(a==0 && b==0 && c<=0 ){ qj::work(); return 0; } LL minn=0x7fffffff; for(int i=1;i<=pn;i++){ minn=min(minn,1ll*arr[i]); dp[i]=f(minn); } for(int i=1;i<=pn;i++){ minn=0x7fffffff; for(int j=i+1;j<=pn;j++){ minn=min(minn,1ll*arr[j]); dp[j]=max(dp[j],dp[i]+f(minn)); } } cout<<dp[pn]<<endl; }
于是我们考虑如何优化$dp$过程
方法1:
$k$常优化。
一般遇到这种题出题人都$kuku$,
我们把第二层循环搞一搞,让它只跑$k$次
复杂度$Theta(kN)$。
你可以试试$300 sim 500$
方法2:
单调栈,因为我们要维护区间最小值于是考虑这个数据结构。
我们发现单调栈一段区间内的最小值是一定的。
于是先维护最小值,
然后$dp$一定从这段区间内的最大dp值转移过来。
于是维护每个区间的最大$dp+f(min{l,r})$
那么我们就会被题解蛊惑用线段树
但是聚聚没有。
发现每次单调栈都只是把后面的删掉,或是加入。
我们直接在单调栈里维护前缀最大值直接转移就好了。
××我也不知道大聚聚怎么想到的,我太蒟蒻了$QAQ$
#include <iostream> #include <cstring> #include <climits> #include <cstdio> #include <stack> #define N 222222 #define LL long long using namespace std; struct Mystack{ LL A[2*N]; int tp; Mystack(){tp=0;} void pop(){tp--;} void push(const LL k){A[tp++]=k;} void clear(){tp=0;} int size(){return tp;} LL top(){return A[tp-1];} bool empty(){return tp==0;} }st,ddp,premax; int pn; LL arr[N]; LL dp[N]; LL a,b,c,d; inline LL f(LL x){ return x*x*x*a+x*x*b+x*c+d; } int main(){ #ifndef LOCAL freopen("min.in" ,"r",stdin); freopen("min.out","w",stdout); #endif ios_base::sync_with_stdio(false); cin>>pn>>a>>b>>c>>d; for(int i=1;i<=pn;i++) cin>>arr[i]; dp[0]=0,arr[0]=-1e16; st.push(0); ddp.push(arr[0]); premax.push(arr[0]); for(int i=1;i<=pn;i++){ // cout<<i<<" "<<dp[i-1]<<endl; LL pdp=dp[i-1]; while(!st.empty() && arr[st.top()]>=arr[i]){ pdp=max(pdp,ddp.top()); st.pop(); ddp.pop(); premax.pop(); } dp[i]=max(1ll*pdp+f(arr[i]),premax.top()); st.push(i); ddp.push(pdp); premax.push(max(premax.top(),pdp+f(arr[i]))); } cout<<dp[pn]<<endl; }