隐藏页面特效

Codeforces Round #842 (Div. 2)

1|0Preface


唉现在这是是做稍微难点的SB题(指Div2正常场的CD难度)总是要犯病

因此Rating上不去不说,比赛的时候连EF题面都没机会看一眼

这场先是C交上去忘记本机调试的时候把数组开小了挂了一发(本地IDE不能看很大的数组,辣鸡Dev)

然后一个D思路不顺畅,交上去WA了一发后思考了好久才发现原来的判断方法的问题,虽然在结束前两分钟改出来了但是分数已经惨不忍睹了

补题的这场的EF还行的说,顺手就都写掉了(唉又回想起以前OI全盛期的时候刷Div2都是倒着往前做的,现在菜成这个狗样)


2|0A. Greatest Convex


因为x!+(x1)!=(x+1)×(x1)!,因此直接输出k1即可

#include<cstdio> #include<iostream> #define RI register int #define CI const int& using namespace std; const int N=100005; int t,k; int main() { //freopen("CODE.in","r",stdin); freopen("CODE.out","w",stdout); for (scanf("%d",&t);t;--t) scanf("%d",&k),printf("%d\n",k-1); return 0; }

3|0B. Quick Sort


先直接上结论,找出以1开头的最长的按顺序出现的1,2,,len的长度len,则剩下的nlen个位置都必须被操作

证明的话很容易,首先所有在1之前的数都一定要操作,然后不在这个顺序里的数也必须要被拎出来

至于操作顺序总能找到一个次数最少的,最后答案就是nlenk

#include<cstdio> #include<iostream> #define RI register int #define CI const int& using namespace std; const int N=100005; int t,n,k,a[N],pos[N]; int main() { //freopen("CODE.in","r",stdin); freopen("CODE.out","w",stdout); for (scanf("%d",&t);t;--t) { RI i; for (scanf("%d%d",&n,&k),i=1;i<=n;++i) scanf("%d",&a[i]),pos[a[i]]=i; int len=1; while (len<n&&pos[len]<pos[len+1]) ++len; printf("%d\n",(n-len+k-1)/k); } return 0; }

4|0C. Elemental Decompress


这种C题一般想到什么思路直接冲一发基本就没问题了,我还傻愣愣地去证做法的正确性,导致写完这题排名已经1000+了

首先判断掉一些显然无解的情况,比如某个数出现了3次及以上

然后考虑所有出现了两次的数,显然两个串在这对应的位置上必须要出现这个数,至于两个串里对应位置的分配对答案没有影响

那么剩下的就是只出现了一次的数了,我们不妨把它们全部扔到一个串里去,因为这些数之间肯定是能构成排列的(没有重复)

比如样例的第二个情况,在做完上面的两个操作后得到(用.代表不确定的位置)

5 3 4 2 . . . . . 5

然后我们考虑怎么填上每个串里各自位置的空缺,以上面的例子填上第二个串的空缺为例

我们把第二个串所有没1出现过的数1 2 3 4,依次按对应的大小顺序填到第一个串对应的数的对应位置上

比如填完后第二个串就变成4 2 3 1 5,然后检查一下是否会出现有位置上的数大于第一个串的即可

对于第一个串的空位也是和上面一样的方法,如法炮制即可

#include<cstdio> #include<iostream> #include<vector> #include<algorithm> #define RI register int #define CI const int& using namespace std; const int N=200005; struct element { int val,id; friend inline bool operator < (const element& A,const element& B) { return A.val<B.val; } }p[N]; int T,n,cnt,c[N],tp[N],a[N],b[N]; bool va[N],vb[N]; vector <int> t[N]; int main() { //freopen("CODE.in","r",stdin); freopen("CODE.out","w",stdout); for (scanf("%d",&T);T;--T) { RI i; for (scanf("%d",&n),i=1;i<=n;++i) a[i]=b[i]=va[i]=vb[i]=0,t[i].clear(); for (i=1;i<=n;++i) scanf("%d",&c[i]),t[c[i]].push_back(i); bool flag=1; for (i=1;i<=n&&flag;++i) if (t[i].size()>2) flag=0; if (!flag) { puts("NO"); continue; } for (i=1;i<=n;++i) if (t[i].size()==2) a[t[i][0]]=i,b[t[i][1]]=i; else if (t[i].size()==1) a[t[i][0]]=i; for (i=1;i<=n;++i) va[a[i]]=vb[b[i]]=1,tp[i]=a[i]; for (cnt=0,i=1;i<=n;++i) if (b[i]) p[++cnt]=(element){b[i],i}; int lst=1; for (sort(p+1,p+cnt+1),i=1;i<=cnt&&flag;++i) { while (va[lst]) ++lst; if (lst>p[i].val) flag=0; else va[lst]=1,a[p[i].id]=lst; } for (cnt=0,i=1;i<=n;++i) if (tp[i]) p[++cnt]=(element){tp[i],i}; lst=1; for (sort(p+1,p+cnt+1),i=1;i<=cnt&&flag;++i) { while (vb[lst]) ++lst; if (lst>p[i].val) flag=0; else vb[lst]=1,b[p[i].id]=lst; } if (!flag) { puts("NO"); continue; } for (puts("YES"),i=1;i<=n;++i) printf("%d%c",a[i]," \n"[i==n]); for (i=1;i<=n;++i) printf("%d%c",b[i]," \n"[i==n]); } return 0; }

5|0D. Lucky Permutation


首先考虑如果不是恰好一个逆序对这个限制,而是把整个序列复原有序,操作步数是多少

说到交换以及排列,很容易想到用置换环,具体地,我们把每个位置i和其上的值pi连边,这样就可以得到若干个联通块,其中每个联通块一定是一个简单环

我们很容易发现,对于每一个简单环,需要用size1次操作来复原环上的所有元素(每一次交换操作恢复一个位置,最后一次操作恢复两个)

有了这个结论之后我们考虑恰好一个逆序对的条件,不难发现恰有一个逆序对的排列就是把1,2,,n的排列的某两个相邻的数交换下得到的

那么我们枚举i,i+1,考虑交换它们两个之后带来的影响,不难发现其实就是断开原来图中(i,ai),(i+1,ai+1)的边,改连成(i,ai+1),(i+1,ai)

首先我们发现如果i,i+1在原来的图中不再一个环内,那么这般操作后这两个环会合并,这样答案就是原来的答案+1

如果i,i+1在一个环内,就要视情况讨论,如果可以使得它们所在的环分成两个小的,那么答案就是原来的答案1

考虑怎么判断这种情况,我的做法是把环上的点都找出来,按顺序排成一个序列

然后找到其中要删除的两条边,这两条边把原来的环分成了两个部分

考虑新加入的边是在这两部分内部连边还是在两部分之间连边即可,具体的实现当时脑子不清实现的可能有些复杂,不过大意是这样的没问题的说

#include<cstdio> #include<iostream> #include<vector> #define RI register int #define CI const int& using namespace std; const int N=200005; int t,n,a[N],fa[N],sz[N],ans,ret,vis[N],num[N]; vector <int> v[N],pt[N]; inline int getfa(CI x) { return fa[x]!=x?fa[x]=getfa(fa[x]):x; } inline void addedge(CI x,CI y) { v[x].push_back(y); v[y].push_back(x); } inline void DFS(vector <int>& p,CI now,CI ct=1) { p.push_back(now); vis[now]=1; num[now]=ct; for (int to:v[now]) if (!vis[to]) DFS(p,to,ct+1); } int main() { //freopen("CODE.in","r",stdin); freopen("CODE.out","w",stdout); for (scanf("%d",&t);t;--t) { RI i; for (scanf("%d",&n),i=1;i<=n;++i) sz[i]=vis[i]=num[i]=0,fa[i]=i,v[i].clear(); for (i=1;i<=n;++i) scanf("%d",&a[i]),fa[getfa(i)]=getfa(a[i]),addedge(i,a[i]); for (i=1;i<=n;++i) ++sz[fa[i]=getfa(i)]; for (ans=1e9,ret=0,i=1;i<=n;++i) if (fa[i]==i) ret+=sz[i]-1,DFS(pt[i],i); for (i=1;i<n;++i) { int cur=ret; if (fa[i]==fa[i+1]) { if (sz[fa[i]]==2) --cur; else { int S=sz[fa[i]],A=num[i],B=num[a[i]]; if (A==S&&B!=S-1) B=S+1; if (B==S&&A!=S-1) A=S+1; if (A>B) swap(A,B); int C=num[i+1],D=num[a[i+1]]; if (C==S&&D!=S-1) D=S+1; if (D==S&&C!=S-1) C=S+1; if (C>D) swap(C,D); if (A>C) swap(A,C),swap(B,D); if (B<=num[i]&&num[i]<=C&&B<=num[a[i+1]]&&num[a[i+1]]<=C) --cur; else if (B<=num[i+1]&&num[i+1]<=C&&B<=num[a[i]]&&num[a[i]]<=C) --cur; } } else cur=ret+1; ans=min(ans,cur); } printf("%d\n",ans); } return 0; }

6|0E. Partial Sorting


知道思路就豁然开朗的数数题,感觉比赛的时候不写D开E可能会更好点

首先我们发现答案的上界为3,因为不管是怎样的排列,我们都可以通过依次进行操作1,操作2,操作1来使得它有序

证明也很简单,第一次执行操作1后所有大于2n的元素一定在[n+1,3n]上了,这样在第二次执行操作2后所有大于2n的数都会归位,然后再执行一次操作3就都有序了

那么我们只要考虑统计出操作次数为0,1,2,3的排列的个数即可统计答案了

首先是操作次数为0的排列,其个数t0=1是显然的

然后是操作次数为1的排列,记其个数为t1,我们可以用容斥的思想,先分别考虑以下两种情况:

  • n个元素已经排列好了,只用考虑后面的2n个元素
  • n个元素已经排列好了,只用考虑前面的2n个元素

但这样得到的部分会有重复,因此要减去前n个元素已经排列好且后n个元素已经排列好的排列数目

而且这样得到的个数是操作次数小于等于1的排列个数,因此要减去t0

接下来考虑操作次数为2的排列个数t2,还是和上面类似,我们直接先考虑操作次数小于2的:

  • 所有小于等于n的元素已经在[1,2n]中了,此时只要先给前2n个元素排序,这样前n个数归位后,再给后2n个数排序即可
  • 所有大于2n的元素已经在[2n+1,3n]中了,理由同上

然后类似的我们也要容斥掉同时满是上面两种情况的排列的个数,不妨枚举[n+1,2n]内有多少个小于等于n的数来计算

当然得到的结果要减去t0+t1才是操作次数等于2的方案数

最后操作次数等于3的方案数就直接t3=(3n)!t0t1t2即可,总复杂度O(n)

#include<cstdio> #define RI register int #define CI const int& using namespace std; const int N=3000005; int n,mod,fact[N],ifact[N],t0,t1,t2,t3; 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 int sum(CI x,CI y) { return x+y>=mod?x+y-mod:x+y; } inline int sub(CI x,CI y) { return x-y<0?x-y+mod:x-y; } inline void init(CI n) { RI i; for (fact[0]=i=1;i<=n;++i) fact[i]=1LL*fact[i-1]*i%mod; for (ifact[n]=quick_pow(fact[n]),i=n-1;~i;--i) ifact[i]=1LL*ifact[i+1]*(i+1)%mod; } inline int C(CI n,CI m) { return 1LL*fact[n]*ifact[m]%mod*ifact[n-m]%mod; } int main() { scanf("%d%d",&n,&mod); init(3*n); t0=1; t1=sub(sub(2LL*fact[2*n]%mod,fact[n]),t0); t2=2LL*C(2*n,n)*fact[n]%mod*fact[2*n]%mod; for (RI i=0;i<=n;++i) t2=sub(t2,1LL*C(n,i)*C(n,n-i)%mod*fact[n]%mod*C(2*n-i,n)%mod*fact[n]%mod*fact[n]%mod); t2=sub(t2,sum(t0,t1)); t3=sub(fact[3*n],sum(sum(t0,t1),t2)); return printf("%d",(1LL*t1+2LL*t2+3LL*t3)%mod),0; }

7|0F. Wonderful Jump


感觉很像那种经典的决策单调性优化DP的模型,但是之前是区间和乘上距离差的平方,这里改成了区间最小值一时不知所措

看了Tutorial才发现ain的深意所在,妙哉妙哉

考虑设fi表示从1跳到i的最小代价,考虑ij转移来,首先有两个关键结论要注意到:

  • minjkiak等于aiaj,否则我们可以找到(j,i)中最小值的位置p,从j转移到p再从p转移到i一定可以使得答案更优
  • 当满足minjkiak×(ij)2>n×(ij)时,一定不可能从j转移到i,因为此时一步步转移的代价更小

考虑第二个结论的意义所在,我们化简以下会发现此时ijnminjkiak,那么我们发现当minjkiak较大的时候j的移动范围就很小了

因此很容易想到根号分治,我们考虑根据aiS=n的大小情况来讨论:

  • aiS时,ijS,因此可以直接O(S)暴力转移
  • aiS时,分ai为最小值和aj为最小值的情况讨论:
    • aj为最小值,我们可以直接用单调栈来维护前面的转移点,这样的转移点不会超过O(S)
    • ai为最小值,我们可以直接暴力往前枚举,一旦遇到ajai就停下,这样的均摊复杂度是O(nS)

关于均摊复杂度的计算其实也很简单,我们考虑每个位置和每个小于S的值,每个位置向同一个值最多转移一次

比如存在ai,aj,ak,其中ai<aj=ak,那么i只会向j转移而不会向k转移,因此复杂度均摊下来是对的

总复杂度就是O(nS)=O(nn)

#include<cstdio> #include<iostream> #define RI register int #define CI const int& using namespace std; const int N=400005,S=650; int n,a[N],stk[N],top; long long f[N]; int main() { RI i,j; for (scanf("%d",&n),i=1;i<=n;++i) scanf("%d",&a[i]); for (stk[top=1]=1,i=2;i<=n;++i) { int mi=a[i]; for (f[i]=1e18,j=i-1;j>=1&&i-j<=S;--j) mi=min(mi,a[j]),f[i]=min(f[i],f[j]+1LL*mi*(i-j)*(i-j)); for (j=1;j<=top;++j) f[i]=min(f[i],f[stk[j]]+1LL*a[stk[j]]*(i-stk[j])*(i-stk[j])); if (a[i]<=S) { for (j=i-1;j>=1;--j) if (f[i]=min(f[i],f[j]+1LL*a[i]*(i-j)*(i-j)),a[j]<=a[i]) break; while (top&&a[stk[top]]>=a[i]) --top; stk[++top]=i; } } for (i=1;i<=n;++i) printf("%lld ",f[i]); return 0; }

8|0Postscript


小掉了13pts,下场争取加把劲


__EOF__

本文作者hl666
本文链接https://www.cnblogs.com/cjjsb/p/17031688.html
关于博主:复活的ACM新生,目前爱好仅剩Gal/HBR/雀魂/单机/OSU
版权声明:转载请注明出处
声援博主:欢迎加QQ:2649020702来DD我
posted @   空気力学の詩  阅读(64)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· 分享一个免费、快速、无限量使用的满血 DeepSeek R1 模型,支持深度思考和联网搜索!
· 基于 Docker 搭建 FRP 内网穿透开源项目(很简单哒)
· ollama系列1:轻松3步本地部署deepseek,普通电脑可用
· 按钮权限的设计及实现
· Apache Tomcat RCE漏洞复现(CVE-2025-24813)
点击右上角即可分享
微信分享提示