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对于int
和boolean
的区别真的严格,一点都不好用
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直接写爆
标签:147,Educational,Rated,const,int,ans,include,RI,define From: https://www.cnblogs.com/cjjsb/p/17356887.html