Educational Codeforces Round 111
Educational Codeforces Round 111
Problem A. Find The Array
找到长度最小且为\(n\)的数组\(a_n\),使之满足:
对于\(1 \leq i \leq n\),有\(a_i=1\)或者\(a_{i}-1,a_i-2 \in \{a_n\},\sum a_i = s\)
对于\(100\%\)的数据\(1\leq s \leq 5000\)
按照如下方法构造:\(a_1 = 1,a_i = a_{i-1}+2 , a_n = a_{n-1}+1\)
问题转化为\(n^2 \geq s\)的问题,直接输出\(\ \lceil\sqrt{s} \ \rceil\)即可。
复杂度\(O(1)\)
# include <bits/stdc++.h>
using namespace std;
int main()
{
int t; scanf("%d",&t);
while (t--) {
int x; scanf("%d",&x);
int y=sqrt(x); if (y*y<x) y++;
printf("%d\n",y);
}
return 0;
}
Problem B. Maximum Cost Deletion
给出一个\(01\)字母串\(s\),每次可以删除一串连续的相同的子串,并把剩下的部分头尾拼接。
给出\(a,b\),删除的子串长度为\(l\),一次操作的得分为\(a\times l+b\) ,求删除子串得分最大值。
对于\(100\%\)的数据\(1\leq len(s) \leq 100, |a|,|b|\leq 100\)
对于一个\(01\)字符串\(s\),删除的最多次数为\(len(s)\),尝试求删除的最少次数:
先把相邻的一串1或者一串0缩成一个0或者1,这两变成10或01交叠字符串的。
容易得知,长度为\(n\)的交叠串需要最少操作次数为\(1+n/2\)次。
显然\(a\)对本题没有贡献,若\(b<0\)删除最少的得分最大,为\(a\times len(s)+(1+n/2)\times b\)
若\(b>0\)删除最多的得分最大,为\((a+b)\times len(s)\)
时间复杂度为\(O(len(s))\)
当然,区间DP也可以做,复杂度\(O(n^3)\)
#include<bits/stdc++.h>
# define int long long
using namespace std;
const int N = 502;
char str[N];
long long dp[N][N];
signed main()
{
int t; scanf("%lld",&t);
while (t--) {
int a,b;
int n;scanf("%lld%lld%lld%s",&n,&a,&b,str);
for(int i=0 ; i<n ; i++)
dp[i][i]=1;
for(int j=1 ; j<n ; j++)
{
for(int i=0 ; i<j ; i++ )
{
dp[i][j]=0x3f3f3f3f;
for(int k=i ; k<j ; k++)
{
dp[i][j]=min(dp[i][j] , dp[i][k]+dp[k+1][j-1]+1-(int)(str[k]==str[j]));
}
}
}
//printf("%lld\n",dp[0][n-1]);
int ans=a*n;
if (b<0) ans+=b*dp[0][n-1]; else ans+=b*n;
printf("%lld\n",ans);
}
return 0;
}
Problem C. Manhattan Subarray
若点\(p,q,r\)是坏的,当且仅当存在\(d(p,q)=d(p,r)+d(r,q)\),\(d(A,B)=|x_A-x_B|+|y_A,y_B|\)
长度为\(n\)的数组\(a_i\),有多少连续的子串,满足这个任取子串中三个数\((a_i,a_j,a_k)\)不存在\((a_i,i),(a_j,j),(a_k,k)\)三个二元组是坏的。
对于\(100\%\)的数据\(1\leq n \leq 2\times 10^5 , 1\leq a_i \leq 10^9\)
由鸽巢原理容易得到,满足条件的连续子串长度最多为\(5\),若为\(6\)必然可以和前面\(5\)个其中\(2\)个组成坏三元组。
所以本题直接暴力模拟即可,复杂度\(O(n)\)
# include <bits/stdc++.h>
# define int long long
using namespace std;
const int N=2e5+10;
int a[N];
signed main()
{
int t; scanf("%lld",&t);
while (t--) {
int n; scanf("%lld",&n); int ans=0;
for (int i=1;i<=n;i++) scanf("%lld",&a[i]);
for (int i=1;i<=n;i++) {
int j=i;
while (j<=n) {
bool f=true;
for (int k=i;k<j;k++)
for (int h=k+1;h<j;h++)
if (a[k]<=a[h]&&a[h]<=a[j]) f=false;
else if (a[k]>=a[h]&&a[h]>=a[j]) f=false;
if (!f) break;
j++;
}
int l=j-i;
ans+=l;
}
printf("%lld\n",ans);
}
return 0;
}
D. Excellent Arrays
长度为\(n\)的数组\(a\)若满足:
- 任取\(i\in[1,n]\)不存在\(a_i = i\)
- \(l \leq a_i \leq r\)
- \(F(a)\)在长度为\(n\)时最大。
其中\(F(a)\)表示\((i,j)\)的组数满足$ a_i + a_j= i+j\ (1\leq i<j\leq n)$
求满足的数组\(a\)的个数,对\(10^9+7\)取模。
对于\(100\%\)的数据\(2 \leq n\leq 2\times 10^5 , -10^9\leq l \leq 1 , n \leq r \leq 10^9\)
考虑\(a_i-i + a_j - j = 0\)所以\(a_i=i+k,a_j = j-k\)。
本题转化为把\(cnt1\)个数字改为\(i+k\),\(cnt2\)个数字数字改为\(i-k\),且\(n = cnt1+cnt2\)
让\(cnt1 \times cnt2\)最大。因此\(cnt1\)和\(cnt2\)大致相等,取到最大值。
-
对于可以满足\(a_i=i+k \in [l,r]\)来说,\(k \in [l-i,r-i] , i \in[l-k,r-k]\)
-
对于可以满足\(a_i=i-k \in [l,r]\)来说,\(k \in [i-r,i-l] , i \in[l+k,r+k]\)
若对于所有\(i\)都可以满足取到\(a_i = i+k,a_i=i-k\)来说,\(1 \leq k \leq min(1-l,r-n)\)
-
对于确定的\(k\),若不能满足\(a_i = i+k\)的\(i\)满足\(i>min(r-k,n)\)
-
对于确定的\(k\),若不能满足\(a_i = i-k\)的\(i\)满足\(i<max(1+k,1)\)
对于所有\(i\)都能满足取到\(a_i = i+k,a_i=i-k\)来说,有\(min(1-l,r-n)\)这么多个\(k\)值。
对于某一个\(k\)值,尽量将\(a_i = i+k\)和\(a_i=i-k\)的数量分配均匀。
若\(n\)为偶数则\(cnt1=cnt2=n/2\) ; 若\(n\)为奇数则\(cnt1=cnt2-1=n/2\)或者\(cnt1-1=cnt2=n/2\)
则若\(n\)为偶数总共方案数为\(min(1-l,r-n)\times C_{n}^{n/2}\),若为奇数,方案数为\(min(1-l,r-n)\times C_{n}^{n/2}\times 2\)
否则,枚举\(k\),对于确定的\(k\),
\([min(r-k,n)+1,n]\)不能满足\(a_i=i+k\),\([1,max(a+k,1)-1]\)不能满足\(a_i=i-k\)
若存在一个\(i\)不能满足\(a_i=i+k\)或\(a_i=i-k\)的话\(F(a)\)就不是长度为\(n\)时的最大值了。
- 因此\(k\)枚举次数小于\(n\)次。
以下讨论不妨设\(ql=max(a+k,1),qr=min(r-k,n)\)。
当\(n\)为偶数时,标准情况是\(cnt1=n/2\),不选既能加又能减的部分\(cnt1=ql-1\)
那么在中间既能加又能减去的部分还需要\(n/2-(ql-1)\)个。
此时方案数为\(C_{qr-ql+1}^{n/2-(ql-1)}\)
当\(n\)为偶数时,$$cnt1=n/2 \ or \ n/2+1$$,不选既能加又能减的部分\(cnt1=ql-1\)
那么在中间既能加又能减去的部分还需要\(n/2-(ql-1) \ or \ n/2+1-(ql-1)\)个。
此时方案数为\(C_{qr-ql+1}^{n/2-(ql-1)} + C_{qr-ql+1}^{n/2+1-(ql-1)}\)
时间复杂度为\(O(n)\)
# include <bits/stdc++.h>
# define int long long
# define pow pppp
using namespace std;
const int p=1e9+7;
const int N=2e5+10;
int s[N],inv[N];
int C(int m,int n)
{
if (n<0||m<0) return 0;
if (m>n) return 0LL;
if (n<p&&m<p) return s[n]*inv[n-m]%p*inv[m]%p;
return C(n%p,m%p)*C(n/p,m/p)%p;
}
signed main()
{
int t; scanf("%lld",&t);
while (t--) {
int n,l,r; scanf("%lld%lld%lld",&n,&l,&r);
s[0]=inv[0]=inv[1]=1;
for (int i=1;i<=n;i++) s[i]=s[i-1]*i%p;
for (int i=2;i<=n;i++) inv[i]=(p-p/i)%p*inv[p%i]%p;
for (int i=2;i<=n;i++) inv[i]=inv[i-1]*inv[i]%p;
int lim=min(r-n,1-l);
int ans=lim*C(n/2,n)%p*(1+n%2)%p;
for (int k=lim+1;;k++) {
int lf=max(1ll,l+k),rg=min(n,r-k);
if (rg-lf+1<0) break;
ans=(ans+C(n/2-(lf-1),rg-lf+1))%p;
if (n&1) ans=(ans+C(n/2-(lf-1)+1,rg-lf+1))%p;
}
printf("%lld\n",ans);
}
return 0;
}
Problem E. Stringforces
长度为\(n\)的字符串由字符?或者是前\(k\)个字母组成。其中?处可以用任何前\(k\)个字母中的一个代替。
求出所有可能的字母串中使得最小的最大连续相同字母长度最大值。
对于\(100\%\)的数据\(1 \leq n \leq 2\times 10^5,1\leq k\leq 17\)
考虑二分答案\(res\),考虑check。
设\(p[c][i]\)表示最小的位置\(p\)满足\(p\geq i\)且\(s[p]=s[p+1]=...=s[p+res-1]\)
设\(f[i]\)表示到达状态\(i\)的最小可能长度。设当前状态为\(mask\)
增添一位第\(i\)个字母,则\(f[mask]=p[i][f[mask]]+res\)。
即先跳到对应满足可能存在满足\(i\)字母的位置\(p[i][f[mask]]\)再把连续的\(l\)个位置都变成\(i\)字母。
显然,为了求\(f[i]\)的最小值,对于已知状态\(f[mask]\)有转移方程:
\(f[mask|(1<<i)]=min(f[mask|(1<<i),p[i][f[mask]]+res)\)
时间复杂度\(O(k(2^{k} +n)log_2 n)\)
# include <bits/stdc++.h>
# define inf (1e9)
using namespace std;
const int N=2e5+10;
int n,k,p[26][N],f[1<<18];
char s[N];
bool check(int l) {
for (int c=0;c<k;c++)
for (int i=0;i<=n;i++)
p[c][i]=inf;
for (int c=0;c<k;c++) {
int last=n;
for (int i=n-1;i>=0;i--) {
if (s[i]!='?'&&s[i]!=c+'a') last=i;
p[c][i]=p[c][i+1];
if (last>=i+l) p[c][i]=i;
}
}
for (int mask=0;mask<=(1<<k);mask++) f[mask]=inf;
f[0]=0;
for (int mask=0;mask<(1<<k);mask++) {
if (f[mask]>=n) continue;
for (int i=0;i<k;i++) if (!(mask>>i&1)) {
f[mask|(1<<i)]=min(f[mask|(1<<i)],p[i][f[mask]]+l);
}
}
return f[(1<<k)-1]<=n;
}
int main() {
scanf("%d%d%s",&n,&k,s);
int l=0,r=n,ans;
while (l<=r) {
int mid=(l+r)/2;
if (check(mid)) l=mid+1,ans=mid;
else r=mid-1;
}
printf("%d\n",ans);
return 0;
}
Problem F. Jumping Around
给出\(a_n\)满足严格单调递增,从\(i=s\)出发,每次可以选择一个\(j\)满足\(|a_j-a_i|\in[d-k,d+k]\)跳跃。
给出\(q\)组询问,\(p,k\),询问从\(s\)出发,跳跃参数为\(k\)能否能到达点\(p\)处。(\(d\)是与询问无关的定值)
对于\(100\%\)的数据\(1\leq n,q\leq 2\times 10^5,1\leq s \leq n,1\leq d\leq 10^6\)
首先观察到\(k\)控制了跳跃的范围,\(k\)越大,可以跳跃的范围越大,即\(k\)可以到达的,\(k+1\)必然可以到达。
于是我们考虑两点之间怎样的\(k\)可以到达,显然是有一个下限值,若大于等于这个值就可以连通。
因此,实际上一共只有\(n-1\)条边是有效地,用\(Prim\)算法可以做到\(O(n^2)\)构建出这样的树。
考虑到从\(s\)点到\(p\)点需要的最小的\(k\)值为途径边的最大\(k\)值,只要\(dfs\)预处理,可实现\(O(1)\)查询。
考虑优化构建生成树的算法,考虑并查集、连通块思想。
最小生成树的本质就是每一次找两个连通块,用最小的边连接起来,让所有点都连通。
考虑求出最小的\(w=|d-|a_i-a_j||\),必然对于一个\(a_i\)找出\(|a_i-a_j|\)和\(d\)最为相近的\(j\)。
用set维护一个\(a\)数组,支持插入删除和求lower_bound。
即\(a_j\approx a_i-d,a_j\approx a_i+d\)可以使用lower_bound求出边界附近的\(4\)个值。
为了避免重复,应当将当前连通块内的所有元素先删除,处理后再加回。
时间复杂度为\(O(n log_2^2 n)\)
#include <bits/stdc++.h>
#define forn(i, n) for (int i = 0; i < int(n); i++)
using namespace std;
const int INF = 1e9;
struct edge2{
int u, w;
};
vector<vector<edge2>> g;
struct edge3{
int v, u, w;
};
bool operator <(const edge3 &a, const edge3 &b){
if (a.w != b.w)
return a.w < b.w;
if (min(a.v, a.u) != min(b.v, b.u))
return min(a.v, a.u) < min(b.v, b.u);
return max(a.v, a.u) < max(b.v, b.u);
}
vector<vector<int>> comps;
vector<int> p;
bool unite(int a, int b){
a = p[a], b = p[b];
if (a == b) return false;
if (comps[a].size() < comps[b].size()) swap(a, b);
for (int v : comps[b]){
p[v] = a;
comps[a].push_back(v);
}
comps[b].clear();
return true;
}
vector<int> mn;
void dfs(int v, int p, int d){
mn[v] = d;
for (auto e : g[v]) if (e.u != p)
dfs(e.u, v, max(d, e.w));
}
int main() {
int n, q, s, d;
scanf("%d%d%d%d", &n, &q, &s, &d);
--s;
vector<int> a(n);
forn(i, n) scanf("%d", &a[i]);
vector<int> idx(a[n - 1] + 1);
forn(i, n) idx[a[i]] = i;
comps.resize(n);
p.resize(n);
forn(i, n) comps[i] = vector<int>(1, i), p[i] = i;
g.resize(n);
set<int> pos(a.begin(), a.end());
int cnt = n;
while (cnt > 1){
vector<edge3> es;
for (const vector<int> &comp : comps) if (!comp.empty()){
for (int i : comp)
pos.erase(a[i]);
edge3 mn = {-1, -1, INF};
for (int i : comp){
for (int dx : {-d, d}){
auto it = pos.lower_bound(a[i] + dx);
if (it != pos.end())
mn = min(mn, {i, idx[*it], abs(abs(a[i] - *it) - d)});
if (it != pos.begin()){
--it;
mn = min(mn, {i, idx[*it], abs(abs(a[i] - *it) - d)});
}
}
}
for (int i : comp)
pos.insert(a[i]);
assert(mn.v != -1);
es.push_back(mn);
}
for (auto e : es){
if (unite(e.v, e.u)){
--cnt;
g[e.v].push_back({e.u, e.w});
g[e.u].push_back({e.v, e.w});
}
}
}
mn.resize(n);
dfs(s, -1, 0);
forn(_, q){
int i, k;
scanf("%d%d", &i, &k);
--i;
puts(mn[i] <= k ? "Yes" : "No");
}
return 0;
}