Educational Codeforces Round 111 (Rated for Div. 2)

D. Excellent Arrays

题目描述

点此看题

对于一个长度为 (n) 的数组 (a),定义 (F(a)) 为满足 (1leq i<jleq n)(a_i+a_j=i+j) 的二元组个数。

求有多少满足下列条件的数列 (a)

  • 对于所有 (a_i),满足 (a_i ot=i)
  • (lleq a_ileq r)
  • (F(a)) 是所有长度为 (n) 的合法数列中的最大权值。

(nleq 2cdot 10^5)

解法

我们考虑什么情况下 (a_i+a_j=i+j) 的对数最大化,先移项,设 (k_i=a_i-i),那么问题是满足 (k_i=-k_j) 的数对个数,所有 (a_i ot=i) 可以翻译成 (k_i ot=0),那么最大权值一定是一半的数拿了正的 (k),一半的数拿了负的 (k)

值域的限制在 (a_i) 上面,我们考虑把限制转到 (k) 上面,我们先考虑什么情况每个数既能选正又能选负,如果选负 (1) 是最容易爆出 (l) 的,如果选正 (n) 是最容易爆出 (r) 的,那么有下列的不等式:

[-kgeq l-1,kleq r-n ]

那么当所有数都能任选的时候 (k)(min(l-1,r-n)) 种合法取值,再乘上 ({nchoose n/2}) 即可((n) 为奇还要考虑 ({nchoose n/2+1})

现在如果 (k) 增大,有数就只能选正或者选负了,而且只能选正的一定是一段前缀,只能选负的一定是一段后缀,我们可以把端点算出来,设 ([lg,rg]) 表示现在能任选的端点:

[-kgeq l-lg,kleq r-rg ightarrow lggeq l+k,rggeq r-k ]

在这些任选的数中选出为负的即可,因为 (k) 增大 (n) 左右组合数方案数就是 (0) 了,所以时间复杂度 (O(n))

总结

两个点的限制,把有关量拆到等号两边。算方案时,考虑何处最先去到端点值。

#include <cstdio>
#include <iostream>
using namespace std;
const int M = 200005;
const int MOD = 1e9+7;
#define int long long
int read()
{
	int x=0,f=1;char c;
	while((c=getchar())<'0' || c>'9') {if(c=='-') f=-1;}
	while(c>='0' && c<='9') {x=(x<<3)+(x<<1)+(c^48);c=getchar();}
	return x*f;
}
int T,n,m,l,r,ans,fac[M],inv[M];
void init(int n)
{
	fac[0]=inv[0]=inv[1]=1;
	for(int i=2;i<=n;i++) inv[i]=inv[MOD%i]*(MOD-MOD/i)%MOD;
	for(int i=2;i<=n;i++) inv[i]=inv[i-1]*inv[i]%MOD;
	for(int i=1;i<=n;i++) fac[i]=fac[i-1]*i%MOD;
}
int C(int n,int m)
{
	if(n<0 || m<0 || n<m) return 0;
	return fac[n]*inv[m]%MOD*inv[n-m]%MOD;
}
void work()
{
	n=read();l=read();r=read();
	m=n/2;ans=0;
	int t=min(1-l,r-n);
	ans=t*C(n,m)%MOD;
	if(n%2) ans=(ans+t*C(n,m+1))%MOD;
	for(int k=t+1;;k++)
	{
		int lg=max(1ll,k+l),rg=min(n,r-k);
		if(lg>rg+1) break;//attention
		ans=(ans+C(rg-lg+1,m-(lg-1)))%MOD;
		if(n%2) ans=(ans+C(rg-lg+1,m+1-(lg-1)))%MOD;
	}
	printf("%lld
",(ans+MOD)%MOD);
}
signed main()
{
	T=read();init(2e5);
	while(T--) work();
}

E. Stringforces

题目描述

点此看题

给一个长度为 (n) 的字符集为 (k) 的串,把问号替换成小写字符,使得每个字符最长连续段的最小值最大。

(nleq 2cdot 10^5,kleq 17)

解法

不难想到二分答案,检查的时候题目都告诉你了要状压((kleq 17)),那么就想状压呗。

你可能不知道状压要干什么,可以逆向思维,状压的在这道题的功能应该是把枚举阶乘的 (n!) 降为 (2^{n}),这说明这道题要枚举顺序,枚举什么顺序呢?枚举让每个字符合法的顺序,既然都有顺序了我们从左到右的让字符合法即可。

(g[i][j]) 表示从位置 (i) 开始要到哪里才能让字符 (j) 合法,这个可以简单预处理。

(dp[s]) 表示让字符集合 (s) 合法的最小右端点,转移:(dp[s|c]leftarrow g[dp[s]][c])

正向的思维过程是考虑枚举字符的顺序,然后从左到右把字符放进去。

总结

考试时候傻了,以为枚举顺序之后要贪心放进最好的位置中。其实是我对枚举的理解出了问题,枚举就是考虑所有情况取其最优,所以枚举的东西就不用贪心了,思维过程是:先枚举,以 (dp) 代替枚举。

#include <cstdio>
#include <iostream>
using namespace std;
const int M = 200005;
int read()
{
	int x=0,f=1;char c;
	while((c=getchar())<'0' || c>'9') {if(c=='-') f=-1;}
	while(c>='0' && c<='9') {x=(x<<3)+(x<<1)+(c^48);c=getchar();}
	return x*f;
}
int n,k,ls[20],p[M][20],dp[1<<20];char s[M];
int check(int d)
{
	for(int i=0;i<k;i++) p[n][i]=ls[i]=n+1;
	for(int i=n-1;i>=0;i--)
	{
		int mi=n;
		if(s[i]!='?') ls[s[i]-'a']=i;
		//这一段中都不能有其他字符 
		//考虑前缀和后缀最小值
		//不行只能继承上一层 
		for(int j=0;j<k;j++)
		{ 
			if(i+d<=mi) p[i][j]=i+d;
			else p[i][j]=p[i+1][j];
			mi=min(mi,ls[j]);
		}
		mi=n;
		for(int j=k-1;j>=0;j--)
		{
			if(i+d>mi) p[i][j]=p[i+1][j];
			mi=min(mi,ls[j]);
		}
	}
	dp[0]=0;
	for(int s=1;s<(1<<k);s++)
	{
		dp[s]=n+1;
		for(int i=0;i<k;i++)
			if((s&(1<<i)) && dp[s^(1<<i)]<n)
				dp[s]=min(dp[s],p[dp[s^(1<<i)]][i]);
	}
	return dp[(1<<k)-1]<=n;
}
signed main()
{
	n=read();k=read();
	scanf("%s",s);
	int l=1,r=n,ans=0;
	while(l<=r)
	{
		int mid=(l+r)>>1;
		if(check(mid))
			ans=mid,l=mid+1;
		else r=mid-1;
	}
	printf("%d
",ans);
}

F. Jumping Around

题目描述

点此看题

(n) 个石头,第 (i) 个石头的位置是 (a_i),青蛙可以跳到与当前石头距离 (in[d-k,d+k]) 的石头,初始时在石头 (s),你知道 (d),每次询问给你 (k)(t),问你是否能在此种情况下跳到石头 (t)

(n,qleq 2cdot 10^5)

解法

题目误导性极强,他给定了起点,我以为正解一定要从这个起点去扩展,结果根本不需要定起点的条件。所以思维要发散,敢于把以前想的全部推倒重来。

题目的重中之重是 (k),你发现 (k) 越大每个点能跳到的范围越大,(u) 能到 (v) 当且仅当 (k) 正好到某个值的时候,我们把这个值看成边的权值,转化成图论模型后,问题是问 (s)(t) 的连通性,所以可以考虑最小生成树,找到路径上权值最大的边即可。

关键是建出最小生成树,权值的秘密在点所以考虑 ( t Borůvka) 算法(这个我不展开)。维护一个 ( t set),考虑到当前连通块后把所有点从 ( t set) 中拿掉,然后枚举点 ( t lower\_bound) 找权值最小的连出去的边,做完以后再插入回 ( t set) 中即可,时间复杂度 (O(nlog^2n))

总结

边权和点相关的最小生成树用 ( t Borůvka) 算法。

#include <cstdio>
#include <vector>
#include <algorithm>
#include <map>
#include <set>
using namespace std;
const int M = 200005;
int read()
{
	int x=0,f=1;char c;
	while((c=getchar())<'0' || c>'9') {if(c=='-') f=-1;}
	while(c>='0' && c<='9') {x=(x<<3)+(x<<1)+(c^48);c=getchar();}
	return x*f;
}
int n,m,q,s,d,a[M],mx[M],fa[M];map<int,int> mp;
struct node
{
	int v,c;
};vector<node> g[M];
int Abs(int x)
{
	return x>0?x:-x;
}
int find(int x)
{
	if(x!=fa[x]) fa[x]=find(fa[x]);
	return fa[x];
}
void solve()
{
	set<int> s;vector<int> v[M];
	for(int i=1;i<=n;i++)
	{
		v[find(i)].push_back(i);
		s.insert(a[i]);
	}
	for(int i=1;i<=n;i++)
	{
		if(!v[i].size()) continue;
		for(int x:v[i]) s.erase(a[x]);
		int mn=1e9,p=0,q=0;
		for(int x:v[i])
		{
			for(int dx:{-d,d})
			{
				set<int>::iterator it=s.lower_bound(a[x]+dx);
				if(it!=s.end())
				{
					int val=Abs(Abs(a[x]-(*it))-d);
					if(val<mn) mn=val,p=x,q=mp[*it];
				}
				if(it!=s.begin())
				{
					it--;
					int val=Abs(Abs(a[x]-(*it))-d);
					if(val<mn) mn=val,p=x,q=mp[*it];
				}
			}
		}
		if(mn<1e9 && find(p)!=find(q))
		{
			fa[find(p)]=find(q);m--;
			g[p].push_back(node{q,mn});
			g[q].push_back(node{p,mn});
		}
		for(int x:v[i]) s.insert(a[x]);
	}
}
void dfs(int u,int fa)
{
	for(node x:g[u])
	{
		if(x.v==fa) continue;
		mx[x.v]=max(x.c,mx[u]);
		dfs(x.v,u);
	}
}
signed main()
{
	n=m=read();q=read();s=read();d=read();
	for(int i=1;i<=n;i++)
		a[i]=read(),fa[i]=i,mp[a[i]]=i;
	while(m>1) solve();
	dfs(s,0);
	while(q--)
	{
		int u=read(),k=read();
		if(mx[u]<=k) puts("YES");
		else puts("NO");
	}
}
原文地址:https://www.cnblogs.com/C202044zxy/p/15022119.html