Preface
期末考终于结束了,终于可以回来写题了
这场是刚考完最后一门的那个晚上打的,因为太久没有写代码了所以不是很熟练而且脑子也不太灵光,只能堪堪写到D题而且手速感人
上次CF第二个号因为Rating被roll了导致从紫名掉下来了,这场就把它又打上去了,再这样后面兴许要用第三个号打了
由于过两天还有军训(虽然我昨天骑车把手脚全摔了不知道能不能去伤病连),所以在一轮集训前可能也没法很集中地写题,等一轮集训的时候应该会加大更新频率吧
A. Forbidden Integer
细节比较多的A题,首先当\(n=1\or k=1\)则一定无解
否则此时\(n\ge 2\and k\ge 2\),考虑如果\(x\ne 1\),则直接用\(n\)个\(1\)即可
否则考虑\(x=1\)的情形,此时若\(2|n\)则用\(\frac{n}{2}\)个\(2\)即可
否则此时\(2\nmid n\),因此\(n>3\),我们只要判断如果\(k\ge 3\)则可以用一个\(3\)和\(\frac{n-3}{2}\)个\(2\),否则就无解
#include<cstdio>
#include<iostream>
#define RI register int
#define CI const int&
using namespace std;
int t,n,k,x;
int main()
{
//freopen("CODE.in","r",stdin); freopen("CODE.out","w",stdout);
for (scanf("%d",&t);t;--t)
{
RI i; scanf("%d%d%d",&n,&k,&x);
if (n==1||k==1) { puts("NO"); continue; }
if (x!=1)
{
puts("YES"); printf("%d\n",n);
for (i=1;i<=n;++i) printf("%d%c",1," \n"[i==n]);
continue;
}
if (n%2==0)
{
puts("YES"); printf("%d\n",n/2);
for (i=1;i<=n/2;++i) printf("%d%c",2," \n"[i==n/2]);
continue;
}
if (k<3) puts("NO"); else
{
puts("YES"); printf("%d\n",1+(n-3)/2); printf("%d ",3);
for (i=1;i<=(n-3)/2;++i) printf("%d%c",2," \n"[i==(n-3)/2]);
}
}
return 0;
}
B. Come Together
首先考虑用一个最小的矩形\((x_L,y_L),(x_R,y_R)\)将\(B,C\)两点恰好包含,然后分类讨论
如果\(A\)点在矩形内部的话那么只有起点是公共点,答案就是\(1\)
否则就分\(A\)点相对于矩形的位置讨论下即可,要么到角上再分开要么到某条边再分开,具体看代码吧
#include<cstdio>
#include<iostream>
#define RI register int
#define CI const int&
using namespace std;
int t,xA,yA,xB,yB,xC,yC;
int main()
{
//freopen("CODE.in","r",stdin); freopen("CODE.out","w",stdout);
for (scanf("%d",&t);t;--t)
{
scanf("%d%d%d%d%d%d",&xA,&yA,&xB,&yB,&xC,&yC);
int xL=min(xB,xC),xR=max(xB,xC),yL=min(yB,yC),yR=max(yB,yC);
if (xL<=xA&&xA<=xR&&yL<=yA&&yA<=yR) { puts("1"); continue; }
if (xA<=xL&&yA<=yL) printf("%d\n",(xL-xA)+(yL-yA)+1); else
if (xA<=xL&&yA>=yR) printf("%d\n",(xL-xA)+(yA-yR)+1); else
if (xA>=xR&&yA>=yR) printf("%d\n",(xA-xR)+(yA-yR)+1); else
if (xA>=xR&&yA<=yL) printf("%d\n",(xA-xR)+(yL-yA)+1); else
if (xA<xL) printf("%d\n",xL-xA+1); else
if (xA>xR) printf("%d\n",xA-xR+1); else
if (yA<yL) printf("%d\n",yL-yA+1); else
if (yA>yR) printf("%d\n",yA-yR+1);
}
return 0;
}
C. Strong Password
刚开始写DP方程不取\(\max\)而是直接赋值WA了一发给我逗乐了,还检查了10min真是苦路西
首先很容易想出一个DP的做法,设\(f_{i,j}\)表示处理了密码的前\(i\)位,其中第\(i\)位填的是数字\(j\),此时将密码的前\(i\)位在原串中做匹配时最靠前的匹配位置
转移的话考虑枚举上一位选的数字\(k\),我们只要找出从原串的第\(f_{i-1,k}\)个位置之后第一个数字\(j\)的位置然后转移即可
这个东西可以通过倒着枚举原串然后预处理出来,最后只要看是否有状态的值大于\(|S|\)即可
总复杂度\(O(|S|\sum+m\sum^2)\),其中\(\sum=10\)为字符集大小
#include<cstdio>
#include<iostream>
#include<cstring>
#define RI register int
#define CI const int&
using namespace std;
const int N=300005,M=15;
int t,n,m,nxt[N][12],f[M][12],bkt[12]; char s[N],l[M],r[M];
int main()
{
//freopen("CODE.in","r",stdin); freopen("CODE.out","w",stdout);
for (scanf("%d",&t);t;--t)
{
RI i,j,k; for (scanf("%s",s+1),n=strlen(s+1),i=0;i<10;++i) bkt[i]=n+1;
for (i=n;i>=1;--i) for (bkt[s[i]-'0']=i,j=0;j<10;++j) nxt[i][j]=bkt[j];
for (scanf("%d%s%s",&m,l+1,r+1),i=l[1]-'0';i<=r[1]-'0';++i) f[1][i]=nxt[1][i];
for (i=2;i<=m;++i) for (j=l[i]-'0';j<=r[i]-'0';++j)
for (f[i][j]=0,k=l[i-1]-'0';k<=r[i-1]-'0';++k)
if (f[i-1][k]>=n) f[i][j]=max(f[i][j],n+1); else f[i][j]=max(f[i][j],nxt[f[i-1][k]+1][j]);
bool flag=0; for (i=l[m]-'0';i<=r[m]-'0';++i)
if (f[m][i]>n) flag=1; puts(flag?"YES":"NO");
}
return 0;
}
D. Rating System
其实做法早就想出来了,但就是懒得去写线段树上二分,结果YY了其它的方法好久无果最后去写一下就写完了
一眼答案可能有单峰性质,然后写了个暴力跑了下样例发现确实是,结果交了一发惨遭WA掉,后面仔细一想好像确实不对劲,遂跑路去写正解
首先不难发现设\(pfx_i\)为\(a_i\)的前缀和,则可能的\(k\)的取值只可能在\(pfx_0\sim pfx_n\)之中,并且最后的答案一定是\(pfx_i+suf_{i+1}\)的形式,其中\(suf_i\)表示从\(i\)开始向后累加Rating,如果出现\(<0\)的情形就重置到\(0\),最后的累加和
正着做感觉不好处理,我们可以考虑逆推,不妨设当前处理的是\(suf_i\),我们已经求出了以\(i\)为开头的前缀和数组\(pfx'\)
若\(pfx'_i\sim pfx'_n\)均\(\ge 0\),则此时\(suf_i=pfx'_n\),否则我们可以找出第一个使得\(pfx'_j<0\)的\(j\),则可以直接递推出\(suf_i=suf_{j+1}\)
实现的时候可以用线段树维护\(pfx'\),然后查询的话就是经典的线段树上二分即可,后面想了下好像可以直接单调栈+二分来做,不过没再细想了
#include<cstdio>
#include<iostream>
#define int long long
#define RI register int
#define CI const int&
using namespace std;
const int N=300005;
int t,n,a[N],pfx[N],suf[N],stk[N],top,ans,k,dlt;
class Segment_Tree
{
private:
struct segment
{
int mi,tag;
}node[N<<2];
#define MI(x) node[x].mi
#define T(x) node[x].tag
#define TN CI now=1,CI l=0,CI r=n
#define LS now<<1,l,mid
#define RS now<<1|1,mid+1,r
inline void pushup(CI now)
{
MI(now)=min(MI(now<<1),MI(now<<1|1));
}
inline void apply(CI now,CI mv)
{
MI(now)+=mv; T(now)+=mv;
}
inline void pushdown(CI now)
{
if (T(now)) apply(now<<1,T(now)),apply(now<<1|1,T(now)),T(now)=0;
}
public:
inline void build(TN)
{
T(now)=MI(now)=0; if (l==r) return;
int mid=l+r>>1; build(LS); build(RS);
}
inline void modify(CI beg,CI end,CI mv,TN)
{
if (beg<=l&&r<=end) return apply(now,mv); int mid=l+r>>1; pushdown(now);
if (beg<=mid) modify(beg,end,mv,LS); if (end>mid) modify(beg,end,mv,RS); pushup(now);
}
inline int query(TN)
{
if (MI(now)>=0) return n+1; if (l==r) return l; int mid=l+r>>1; pushdown(now);
if (MI(now<<1)<0) return query(LS); return query(RS);
}
#undef M
#undef T
#undef TN
#undef LS
#undef RS
}SEG;
signed main()
{
//freopen("CODE.in","r",stdin); freopen("CODE.out","w",stdout);
for (scanf("%lld",&t);t;--t)
{
RI i; for (scanf("%lld",&n),i=1;i<=n;++i)
scanf("%lld",&a[i]),pfx[i]=pfx[i-1]+a[i];
for (suf[n+1]=0,i=n,ans=-1e18,SEG.build();i>=0;--i)
{
SEG.modify(i,n,a[i]); int pos=SEG.query();
if (pos>n) suf[i]=pfx[n]-pfx[i-1]; else suf[i]=suf[pos+1];
if (pfx[i]+suf[i+1]>ans) ans=pfx[i]+suf[i+1],k=pfx[i];
}
printf("%lld\n",k);
}
return 0;
}
E. Boxes and Balls
好题,不过据说\(O(n^2k)\)大暴力也能水过的说
首先发现两个重要性质,其一是不论怎么操作所有球的相对顺序不会变化
其二是如果我们强制统计某个状态时要满足移动的步数最小,则算出此时移动\(1,2,3,\cdots,k\)步的状态数,最后把所有和\(k\)奇偶性相同的状态加起来就是答案了
这样的原因是当用较少的步数得到一个状态后,可以随便找一个位置来回移动消耗偶数次步数而保证状态不变
转化成最小步数之后就有很好的性质了,我们设初始时\(1\)的位置为\(s_1,s_2,\cdots,s_m\),某个目标状态中\(1\)的位置为\(t_1,t_2,\cdots,t_m\),则最小步数为\(\sum_{j=1}^m |s_j-t_j|\)
由此我们很容易写出一个暴力DP的做法,设\(f_{i,j,k}\)表示已经处理了最终状态的前\(i\)个位置,其中放了\(j\)个球,总最小操作步数为\(k\)的方案数,转移就分要不要在位置\(i\)放球即可
据说用滚动数组压一下空间就能跑过,但这题其实有更好的做法
考虑令\(pfx_i\)表示初始时\(a_i\)的前缀和,则有结论最终状态的这一位的前缀和\(pfx'_i\)可能的取值为\([pfx_i-O(\sqrt k),pfx_i+O(\sqrt k)]\)
原因其实很简单,因为不管是把\(1\)移入还是移出,操作\(t\)个数所需的步数最少都是\(\frac{t(t+1)}{2}\),而总操作数只有\(k\),因此取\(S=O(\sqrt k)=100\)就没问题了
然后再用上面的DP,总复杂度\(O(nk\sqrt k)\)
#include<cstdio>
#include<iostream>
#include<algorithm>
#define RI register int
#define CI const int&
using namespace std;
const int N=1505,mod=1e9+7,S=100;
int n,k,a[N],pfx[N],f[N][N],pos[N],cnt,ans;
int main()
{
//freopen("CODE.in","r",stdin); freopen("CODE.out","w",stdout);
RI i,j,t; for (scanf("%d%d",&n,&k),i=1;i<=n;++i)
if (scanf("%d",&a[i]),pfx[i]=pfx[i-1]+a[i],a[i]) pos[++cnt]=i;
for (f[0][0]=i=1;i<=n;++i)
{
int l=max(1,pfx[i]-S),r=min(i,min(cnt,pfx[i]+S));
for (j=r;j>=l;--j)
{
int cur=abs(pos[j]-i);
for (t=k;t>=cur;--t) (f[j][t]+=f[j-1][t-cur])%=mod;
}
}
for (i=k;i>=0;i-=2) (ans+=f[cnt][i])%=mod;
return printf("%d",ans),0;
}
Postscript
之前欠下的CF和Atcoder有时间再补吧,现在手头还有学校的字符串和几何专题要写
标签:151,suf,Educational,Rated,const,int,pfx,include,define From: https://www.cnblogs.com/cjjsb/p/17519374.html