孩子们,我开了文件读写然后全爆 0 了!!!!!
T1 吃
吃了吧。
题意
给 \(a\) 和 \(b\) 两个序列,选定一个子串,对于子串的任意一个位置,都可以选择 \(a_i\) 或 \(b_i\) 其一,选定的连续子串合法当且仅当存在一种选法使选择的所有数都相同。
\(1\le n\le 10^5,1\le a_i,b_i\le 5\)。
赛时
赛时 20 分钟敲了个转移很显然的 \(O(n)\) dp。结果挂成 10pts。
赛后暴力枚举值域过了。
啥啊原来我转移式写错了,一个位置的 1 写成 0 了。【不对拍的后果】
题解
考虑 \(dp_{i,0/1}\) 表示第 \(i\) 个位置选择 \(a_i\) 或 \(b_i\),以 \(i\) 为结尾的最长合法子串。
显然,当 \(a_{i-1}=a_i\) 时,\(dp_{i,0}\) 可以从 \(dp_{i-1,0}\) 转移。
同理,当 \(b_{i-1}=a_i\) 时,\(dp_{i,0}\) 可以从 \(dp_{i-1,1}\) 转移。
以此类推。总共四个转移式。
最后答案取全局 \(\max\) 再找到符合要求的最小值就行了。这个做法可以推广到 \(a_i,b_i\gt 5\) 的情况。
#include<bits/stdc++.h>
#define inf 0x3f3f3f3f
#define infll 0x3f3f3f3f3f3f3f3fll
using namespace std;int n;
int a[100010],b[100010];
int dp[100010][2];int main(){ios::sync_with_stdio(false);cin.tie(0),cout.tie(0);cin>>n;for(int i=1;i<=n;i++) cin>>a[i]>>b[i];for(int i=1;i<=n;i++) dp[i][0]=dp[i][1]=1;for(int i=2;i<=n;i++){if(a[i]==a[i-1]) dp[i][0]=max(dp[i][0],dp[i-1][0]+1);if(b[i]==b[i-1]) dp[i][1]=max(dp[i][1],dp[i-1][1]+1);if(a[i]==b[i-1]) dp[i][0]=max(dp[i][0],dp[i-1][1]+1);if(b[i]==a[i-1]) dp[i][1]=max(dp[i][1],dp[i-1][0]+1);}int mx=-1;for(int i=1;i<=n;i++) mx=max(mx,max(dp[i][0],dp[i][1]));int ans=inf;for(int i=1;i<=n;i++) for(int j=0;j<=1;j++) if(dp[i][j]==mx) ans=min(ans,(j?b[i]:a[i]));cout<<mx<<" "<<ans;return 0;
}
T2 糖
糖不糖。
题意
给长为 \(n\) 的序列 \(a\) 和正整数 \(m\)。构造一个长为 \(n\) 的序列 \(b\),满足 \(\sum b\le m\)。最小化 \(\sum_{b_i<a_i} (a_i-b_i)^2\),并求出这个最小值。
赛时
赛时摸了个二分+贪心的解法。居然真了。
题解
严格证明太复杂了。考虑简单点的,我们把向 \(b\) 加数转化为向 \(a\) 减数。
首先,初始答案显然为 \(\sum a_i^2\)。
对于 \(a_i-1\to a_i\),答案的变化量为 \(-2a_i+1\)。那么显然每次选择最大的数进行操作一定不劣。思考到这里容易想到 \(O(m\log n)\) 的优先队列解法。
考虑优化这个减数的过程。注意到我们会对同一个数反复修改,非常耗费时间。所以我们可以把相等的数合并到一起,并一次性减少到此时序列中的严格次小值(From baieeeeeeeee)。
这是一种办法,但是还是太复杂了。
所以我又想了一个更简单的办法,先二分出来极限情况下能把所有数减少到多少。很显然有单调性,可以二分。
处理出来这个信息之后,再把剩余的数降序排一下,剩下没减的数肯定选择最大值更优。
然后就可以 \(O(n\log V + n\log n)\) 解决了。
#include<bits/stdc++.h>
#define inf 0x3f3f3f3f
#define infll 0x3f3f3f3f3f3f3f3fll
using namespace std;int n;
long long m;
long long a[100010];bool chk(long long md){long long nw=0;for(int i=1;i<=n;i++) if(a[i]>md) nw+=a[i]-md;return nw<=m;
}int main(){ios::sync_with_stdio(false);cin.tie(0),cout.tie(0);cin>>m>>n;for(int i=1;i<=n;i++) cin>>a[i];long long l=0,r=LLONG_MAX;while(l<r){long long mid=(l+r)>>1;if(chk(mid)) r=mid;else l=mid+1;}long long nw=0;for(int i=1;i<=n;i++) if(a[i]>l) nw+=a[i]-l,a[i]=l;nw=m-nw;sort(a+1,a+1+n,greater<int>());for(int i=1;i<=n&&nw;i++,nw--) a[i]--;long long ans=0;for(int i=1;i<=n;i++) ans+=a[i]*a[i];cout<<ans; return 0;
}
T3 数
还在数?
题意
给长为 \(n\) 的序列 \(a\)。定义 \(f(s)=\max_{x\in s}x-\min_{x\in s}x\)。
求 \(\sum_{i=1}^{n} \sum_{j=i}^{n} f(a[i:j])\)。
赛时
敲了单调栈,但是没处理相等的情况还写挂了。所以全挂了。
第二天补了题,细节真的很多,但不失为一道好题。
题解
考虑拆贡献。
对于每个数 \(a_i\),当其所在序列中不存在比其大的数时,这个数能作为最大值计入答案。
对应的,当其所在序列中不存在比其小的数时,这个数能作为最小值计入答案。
然后把两个情况合起来就是答案了。
用四个单调栈维护就够了。听别人说用线段树,但我感觉完全没必要。
#include<bits/stdc++.h>
#define inf 0x3f3f3f3f
#define infll 0x3f3f3f3f3f3f3f3fll
using namespace std;int n;
long long a[300010];stack<int> stk;
int fr1[300010],fr2[300010];
int bk1[300010],bk2[300010];int main(){ios::sync_with_stdio(false);cin.tie(0),cout.tie(0);cin>>n;for(int i=1;i<=n;i++) cin>>a[i];for(int i=1;i<=n;i++){while(!stk.empty()&&a[stk.top()]>=a[i]) stk.pop();if(stk.empty()) fr1[i]=1;else fr1[i]=stk.top()+1;stk.push(i);}while(!stk.empty()) stk.pop();for(int i=1;i<=n;i++){while(!stk.empty()&&a[stk.top()]<=a[i]) stk.pop();if(stk.empty()) fr2[i]=1;else fr2[i]=stk.top()+1;stk.push(i);}while(!stk.empty()) stk.pop();for(int i=n;i>=1;i--){while(!stk.empty()&&a[stk.top()]>a[i]) stk.pop();if(stk.empty()) bk1[i]=n;else bk1[i]=stk.top()-1;stk.push(i);}while(!stk.empty()) stk.pop();for(int i=n;i>=1;i--){while(!stk.empty()&&a[stk.top()]<a[i]) stk.pop();if(stk.empty()) bk2[i]=n;else bk2[i]=stk.top()-1;stk.push(i);}while(!stk.empty()) stk.pop();long long ans=0;for(int i=1;i<=n;i++){ans+=1ll*(bk2[i]-i+1)*(i-fr2[i]+1)*a[i];ans-=1ll*(bk1[i]-i+1)*(i-fr1[i]+1)*a[i];}cout<<ans;return 0;
}