Educational Codeforces Round 147 (Rated for Div. 2)

Preface

补题

这周比赛挺少,不过后面可能就很少有时间补以前的比赛了的说

现在除了要做学校的集训专题外,还要刷一下蓝桥杯国赛的题,可以说是很忙碌了

而且由于JAVA的期末考试要来了,为了熟悉下语法以后补题的时候前面的题目可能会用JAVA来写(其实我写的JAVA看起来和C++真没啥区别)


A. Matching

傻逼题,若开头为0则一定无解,否则统计下?的个数,出现在开头的?要乘上\(9\),否则乘上\(10\)

import java.util.*;
public class hl666 {
    public static void main(String args[]) {
        Scanner In=new Scanner(System.in);
        int t=In.nextInt();
        In.nextLine();
        for (;t>0;--t) {
            String s=In.nextLine();
            int ret=1;
            for (int i=1;i<s.length();i++) {
                if (s.charAt(i)=='?') ret*=10;
            }
            if (s.charAt(0)=='?') ret*=9;
            if (s.charAt(0)=='0') ret=0;
            System.out.println(ret);
        }
    }
}

B. Sort the Subarray

傻逼题,由于题目保证有解,我们考虑枚举所有保证\(b_i\)不降的极长区间,然后只要检验前后缀是否匹配即可

吐槽一下JAVA对于intboolean的区别真的严格,一点都不好用

import java.util.*;
public class hl666 {
    public static void main(String args[]) {
        Scanner In=new Scanner(System.in);
        int t=In.nextInt();
        for (;t>0;--t) {
            int n=In.nextInt();
            int[] a=new int[n];
            int[] b=new int[n];
            boolean[] pre=new boolean[n];
            boolean[] suf=new boolean[n];
            for (int i=0;i<n;++i) a[i]=In.nextInt();
            for (int i=0;i<n;++i) b[i]=In.nextInt();
            pre[0]=a[0]==b[0]; suf[n-1]=a[n-1]==b[n-1];
            for (int i=1;i<n;++i) pre[i]=pre[i-1]&&(a[i]==b[i]);
            for (int i=n-2;i>=0;--i) suf[i]=suf[i+1]&&(a[i]==b[i]);
            int mxlen=0,ansl=0,ansr=0;
            for (int l=0,r=0;r<n;++r) {
                if (l!=r&&b[r]<b[r-1]) l=r;
                //System.out.println(l+" "+r);
                if ((l>0?pre[l-1]:true)&&(r<n-1?suf[r+1]:true)) {
                    if (r-l+1>mxlen) {
                        mxlen=r-l+1; ansl=l; ansr=r;
                    }
                }
            }
            System.out.println((ansl+1)+" "+(ansr+1));
        }
    }
}

C. Tear It Apart

显然直接枚举最后剩下的那一种字符是什么,然后考虑这些剩下的位置给整个串分成了若干个段

不同段之间显然是不会相互影响的,因此只要考虑需要操作次数最多的段即可

\(f(x)\)表示长度为\(x\)的连续段需要操作的次数,显然\(f(x)=f(\lfloor\frac{x}{2}\rfloor)+1\)

#include<cstdio>
#include<iostream>
#include<cstring>
#define RI register int
#define CI const int&
using namespace std;
const int N=200005;
int t,n,f[N],ans; char s[N];
int main()
{
	//freopen("CODE.in","r",stdin); freopen("CODE.out","w",stdout);
	RI i,j; for (i=1;i<=200000;++i) f[i]=f[i>>1]+1;
	for (scanf("%d",&t);t;--t)
	{
		for (scanf("%s",s+1),n=strlen(s+1),ans=1e9,j=0;j<26;++j)
		{
			int ret=0,lst=0; for (i=1;i<=n;++i) if (s[i]-'a'==j)
			ret=max(ret,f[i-lst-1]),lst=i; ans=min(ans,max(ret,f[n-lst]));
		}
		printf("%d\n",ans);
	}
	return 0;
}

D. Black Cells

首先一个很naive的想法就是枚举最后结束的那个区间,这个区间可以不全部取完,但前面的区间一定是要选都选的

然后把贡献的式子写一下我们很容易发现,如果一个区间长度大于等于\(2\),那么先把它取了一定不会让答案变劣

因此我们统计一下长度为\(1\)的区间个数再枚举即可

#include<cstdio>
#include<iostream>
#include<cstring>
#define int long long
#define RI register int
#define CI const int&
using namespace std;
const int N=200005,INF=1e18;
int t,n,k,l[N],r[N],ans;
signed main()
{
	//freopen("CODE.in","r",stdin); freopen("CODE.out","w",stdout);
	for (scanf("%lld",&t);t;--t)
	{
		RI i; for (scanf("%lld%lld",&n,&k),i=1;i<=n;++i) scanf("%lld",&l[i]);
		for (i=1;i<=n;++i) scanf("%lld",&r[i]);
		int cur=0,num=0; for (ans=INF,i=1;i<=n;++i)
		{
			if (l[i]==r[i]) ++num; cur+=r[i]-l[i]+1;
			if (cur>=k)
			{
				int left=cur-k; ans=min(ans,r[i]-left+2*i-min(left,num));
			}
		}
		printf("%lld\n",ans!=INF?ans:-1);
	}
	return 0;
}

E. Rearrange Brackets

挺有意思的一个题,一边和ztc扯皮然后突然灵感乍现就会了

对于括号序列有两种经典的模型转化,一种是比较熟知的针对是否合法的前缀和模型

还有一种其实在处理合法括号序列时很常用,就是嵌套模型

即如果我们把每一对匹配的左右括号看作一个点,然后令它外层第一个嵌套住它的括号向它连边,就可以得到一个森林

然后我们仔细分析题意之后就会发现如果没有修改,答案就是这个森林中所有点的深度和(令根节点深度为\(0\)

那么如果有修改呢,很显然我们修改一定是把某个最外层(即不被任何括号嵌套的)一对括号直接合并掉,反映到森林上就是让一棵树的根节点断开所有与它的儿子的边

很显然这个减少的贡献就是看每棵树的大小决定的,这里由于数据范围很小,可以每次重新DFS求解,复杂度\(O(nk)\)

然后ztc吐槽说这个东西数据范围太假了,其实后面想了下可以用堆优化这个过程,应该可以跑更大的数据范围的说

当然我一般都是什么能过就写什么,这里就直接暴力了

#include<cstdio>
#include<iostream>
#include<vector>
#include<cstring>
#define RI register int
#define CI const int&
using namespace std;
const int N=200005;
int t,k,n,stk[N],top,sz[N],dep[N]; bool vis[N]; char s[N]; vector <int> v[N];
inline void DFS(CI now,CI fa=0)
{
	sz[now]=1; vis[now]=1; for (int to:v[now])
	if (to!=fa&&!vis[to]) dep[to]=dep[now]+1,DFS(to,now),sz[now]+=sz[to];
}
int main()
{
	//freopen("CODE.in","r",stdin); freopen("CODE.out","w",stdout);
	for (scanf("%d",&t);t;--t)
	{
		RI i,j; for (scanf("%d%s",&k,s+1),n=strlen(s+1),i=1;i<=n;++i)
		if (s[i]=='(') stk[++top]=i; else
		v[stk[top-1]].push_back(stk[top]),--top;
		for (j=1;j<=k;++j)
		{
			for (i=1;i<=n;++i) vis[i]=dep[i]=0;
			for (i=1;i<=n;++i) if (!vis[i]) DFS(i);
			int pos=0; for (i=1;i<=n;++i) if (sz[i]>sz[pos]) pos=i;
			v[pos].clear();
		}
		for (i=1;i<=n;++i) vis[i]=dep[i]=0;
		for (i=1;i<=n;++i) if (!vis[i]) DFS(i);
		long long ans=0; for (i=1;i<=n;++i) ans+=dep[i],v[i].clear();
		printf("%lld\n",ans);
	}
	return 0;
}

F. Timber

又被数数题腐乳了,不过转念一想反正我每次都做不来数数那就躺好等队友C吧(doge

这题显然一眼转化题意就是用\(m\)个长为\(k+1\)的区间覆盖一个长度为\(n\)的线段,每个区间不能有交

然后我只能秒出一个\(O(n^2)\)DP后然后开始挂机,思考无果后去看Tutorial

然后一个区间对应一个关键点可以在区间的任意一个端点处取,求所有本质不同的关键点取法

如果只是求区间划分的方法的话就是个插板法秒杀的题,但由于区分是在关键点的位置上,就需要一点trick了

我们不妨先考虑如何检验对于一种关键点的分布,怎么验证其合法性

很显然可以直接贪心,对于每个关键点能向左选就尽量向左选,若被占了才考虑向右,这个正确性显然

然后我们可以沿用这个思路来这样考虑,枚举一种方案中至少\(x\)个关键点是向左选

那么什么情况会出现一个点一定会向左选呢,很显然当它左边空出的位置较多,达到\(2k+1\)个时就一定会向左选

因此现在我们可以把题意转化为有\(x\)个长为\(2k+1\)的区间,\(m-x\)个长为\(k+1\)的区间,覆盖长度为\(n\)的线段的方案数

根据插板法以及\(m-x\)个区间的两种方向选择,我们可以得到至少有\(x\)个关键点是向左选的方案数

\[f(x)=C_m^x\times C_{n-x(2k+1)-(m-x)(k+1)+m}^m\times 2^{m-x} \]

然后要得到最后的答案我们再做一个容斥即可:

\[ans=\sum_{i=0}^m f(i)\times (-1)^i \]

#include<cstdio>
#include<iostream>
#define RI register int
#define CI const int&
using namespace std;
const int N=600005,mod=998244353;
int n,m,k,fac[N],ifac[N],pw[N],ans;
inline int quick_pow(int x,int p=mod-2,int mul=1)
{
	for (;p;p>>=1,x=1LL*x*x%mod) if (p&1) mul=1LL*mul*x%mod; return mul;
}
inline void init(CI n)
{
	RI i; for (fac[0]=i=1;i<=n;++i) fac[i]=1LL*fac[i-1]*i%mod;
	for (ifac[n]=quick_pow(fac[n]),i=n-1;~i;--i) ifac[i]=1LL*ifac[i+1]*(i+1)%mod;
	for (pw[0]=i=1;i<=n;++i) pw[i]=2LL*pw[i-1]%mod;
}
inline int C(CI n,CI m)
{
	return 1LL*fac[n]*ifac[m]%mod*ifac[n-m]%mod;
}
int main()
{
	//freopen("CODE.in","r",stdin); freopen("CODE.out","w",stdout);
	RI i; for (scanf("%d%d%d",&n,&m,&k),init(n+m),i=0;i<=m;++i)
	{
		long long left=n-1LL*i*(2*k+1)-1LL*(m-i)*(k+1);
		if (left<0) continue; int ret=1LL*C(m,i)*C(left+m,m)%mod*pw[m-i]%mod;
		if (i&1) (ans+=mod-ret)%=mod; else (ans+=ret)%=mod;
	}
	return printf("%d",ans),0;
}

Postscript

写题还是爽的,等五一来我TM直接写爆

posted @ 2023-04-26 17:56  空気力学の詩  阅读(31)  评论(0编辑  收藏  举报