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;
}
posted @ 2021-07-17 21:55  Maystern  阅读(83)  评论(0编辑  收藏  举报