Preface
补题,这场本来周日晚上打算现场打的
但一来第二天要上课怕熬太晚影响早上的微积分和电分,二来那天晚上开了DP专题然后就在手速抢一血
过程中被两道DFS搞红温了,本来打CF的计划也咕咕咕了
今天差不多想起来好久没做CF了赶紧补一场
看了下自己补题的时候2h也才堪堪做完D1,虽然后面上大物课的时候想出了E的做法,不过真到比赛时估计也是一眼都不会看的说
感觉现在切2000左右的题的速度还是太慢太慢,而且经常有想法但是容易写假就很难受
A. Divisible Array
SB题,令\(a_i=2i\),总和为\(n\times(n+1)\)显然为\(n\)的倍数
#include<cstdio>
#include<iostream>
#define RI register int
#define CI const int&
using namespace std;
const int N=205;
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) printf("%d%c",2*i," \n"[i==n]);
}
return 0;
}
B. Permutation Swap
这题好像就最近的某一场出过思想基本一样的东西的说
考虑令\(t_i=|i-p_i|\),不难发现如果可以把\(i\)换到对应的位置则一定有\(k|t_i\)
因此答案就是所有\(t_i\)的\(\gcd\),当时写的时候闹抽了还写了暴力对每个\(t_i\)分解质因数,属实智障了
#include<cstdio>
#include<iostream>
#include<algorithm>
#define RI register int
#define CI const int&
using namespace std;
const int N=100005;
int t,n,a[N],c[N],tot;
int main()
{
//freopen("CODE.in","r",stdin); freopen("CODE.out","w",stdout);
for (scanf("%d",&t);t;--t)
{
RI i; for (scanf("%d",&n),tot=0,i=1;i<=n;++i) c[i]=0;
auto resolve=[&](CI x)
{
for (RI i=1;i*i<=x;++i) if (x%i==0)
if (++c[i],i!=x/i) ++c[x/i];
};
for (i=1;i<=n;++i) scanf("%d",&a[i]),tot+=abs(a[i]-i)!=0,resolve(abs(a[i]-i));
for (i=n;i>=1;--i) if (c[i]==tot) { printf("%d\n",i); break; }
}
return 0;
}
C. Counting Orders
这场的ABC水的一逼啊,10min就全切了
显然我们从大到小考虑\(b_i\),求出大于\(b_i\)且未被使用的\(a_j\)的个数,然后累乘起来即可
把\(a,b\)都排序后,two pointers
即可
#include<cstdio>
#include<iostream>
#include<algorithm>
#define RI register int
#define CI const int&
using namespace std;
const int N=200005,mod=1e9+7;
int t,n,a[N],b[N],ans;
int main()
{
//freopen("CODE.in","r",stdin); freopen("CODE.out","w",stdout);
for (scanf("%d",&t);t;--t)
{
RI i,j; for (scanf("%d",&n),ans=1,i=1;i<=n;++i) scanf("%d",&a[i]);
for (i=1;i<=n;++i) scanf("%d",&b[i]); sort(a+1,a+n+1); sort(b+1,b+n+1);
for (i=j=n;i>=1;--i)
{
while (j>=1&&a[j]>b[i]) --j; ans=1LL*ans*(n-j-(n-i))%mod;
}
printf("%d\n",ans);
}
return 0;
}
D1. Range Sorting (Easy Version)
刚开始从区间DP入手然后其实已经想出针对D2的关键转化了,结果还是拘泥于DP的形式没有看到本质的东西
首先很容易想到设\(f_{l,r}\)表示\([l,r]\)的答案,转移的话就是枚举一个\(k\),满足\(\max_\limits{l\le i\le k} a_i<\min_\limits{k<j\le r} a_j\),则有\(f_{l,r}=\max(f_{l,k}+f_{k+1,r})\)
然后对着样例手玩一下就会发现我们只要找到某个分界点然后直接转移一定不会更劣,因为能划分区间就尽量划分即可
因此现在问题就是怎么对每个区间求出符合上面式子的分界点\(k\)
可以先枚举\(a_k\),然后双指针统计合法的左右区间的取法即可,但是这里我傻逼了还是以为一定要具体的求出所有的转移点信息,写了个\(O(n^2\log n)\)的东西结果爆内存了
#include<cstdio>
#include<iostream>
#include<vector>
#include<algorithm>
#define RI register int
#define CI const int&
using namespace std;
typedef pair <int,int> pi;
const int N=5005;
struct Data
{
int l,r,v;
inline Data(CI L=0,CI R=0,CI V=0)
{
l=L; r=R; v=V;
}
friend inline bool operator < (const Data& A,const Data& B)
{
return A.l<B.l;
}
}; vector <Data> s[N]; int t,n,a[N],f[N][N],pos[N][N],mx[N],mi[N];
inline int DP(CI l,CI r)
{
if (l>=r) return 0; if (~f[l][r]) return f[l][r];
return f[l][r]=~pos[l][r]?DP(l,pos[l][r])+DP(pos[l][r]+1,r):r-l;
}
int main()
{
//freopen("CODE.in","r",stdin); freopen("CODE.out","w",stdout);
for (scanf("%d",&t);t;--t)
{
RI i,j; for (scanf("%d",&n),i=1;i<=n;++i) scanf("%d",&a[i]);
for (i=1;i<=n;++i) for (s[i].clear(),j=1;j<=n;++j) f[i][j]=pos[i][j]=-1;
for (i=1;i<n;++i)
{
for (mx[i]=a[i],j=i-1;j>=1;--j) mx[j]=max(mx[j+1],a[j]);
for (mi[i+1]=a[i+1],j=i+2;j<=n;++j) mi[j]=min(mi[j-1],a[j]);
RI l=i,r=n; for (;l>=1;--l)
{
while (r>=i+1&&mx[l]>mi[r]) --r;
if (r>=i+1) s[l].push_back(Data(i+1,r,i));
}
}
for (i=1;i<n;++i)
{
sort(s[i].begin(),s[i].end()); int lst=0;
for (auto [l,r,v]:s[i])
{
for (j=max(l,lst+1);j<=r;++j) pos[i][j]=v; lst=max(lst,r);
}
}
//for (i=1;i<=n;++i) for (j=i;j<=n;++j) printf("l=%d r=%d : %d\n",i,j,pos[i][j]);
long long ret=0; for (i=1;i<=n;++i) for (j=i;j<=n;++j) ret+=DP(i,j);
printf("%lld\n",ret);
}
return 0;
}
后面仔细一想DP个狗屁东西,显然只要有一对三元组\((l,r,k)\)满足上面的限制,答案就可以减\(1\),因此直接算贡献即可
#include<cstdio>
#include<iostream>
#define RI register int
#define CI const int&
using namespace std;
typedef pair <int,int> pi;
const int N=5005;
int t,n,a[N],mx[N],mi[N];
int main()
{
//freopen("CODE.in","r",stdin); freopen("CODE.out","w",stdout);
for (scanf("%d",&t);t;--t)
{
RI i,j; for (scanf("%d",&n),i=1;i<=n;++i) scanf("%d",&a[i]);
long long ret=0; for (i=1;i<=n;++i) for (j=i;j<=n;++j) ret+=j-i;
for (i=1;i<n;++i)
{
for (mx[i]=a[i],j=i-1;j>=1;--j) mx[j]=max(mx[j+1],a[j]);
for (mi[i+1]=a[i+1],j=i+2;j<=n;++j) mi[j]=min(mi[j-1],a[j]);
RI l=i,r=n; for (;l>=1;--l)
{
while (r>=i+1&&mx[l]>mi[r]) --r;
if (r>=i+1) ret-=r-(i+1)+1;
}
}
printf("%lld\n",ret);
}
return 0;
}
当然其实D1还有另一种思路的单调栈做法,就是枚举左端点,然后考虑在右边加入元素的过程中
如果新加入的数比之前都大,那么它就自成一段,否则找到它前面大于它的位置合并贡献即可
可以用一个单调栈来维护这个过程,不过这个反而不好优化到正解的说
#include<cstdio>
#include<iostream>
#define RI register int
#define CI const int&
using namespace std;
typedef pair <int,int> pi;
const int N=5005;
int t,n,a[N],stk[N],top;
int main()
{
//freopen("CODE.in","r",stdin); freopen("CODE.out","w",stdout);
for (scanf("%d",&t);t;--t)
{
RI i,j; for (scanf("%d",&n),i=1;i<=n;++i) scanf("%d",&a[i]);
long long ret=0; for (i=1;i<=n;++i) for (top=0,j=i;j<=n;++j)
{
int cur=a[j]; while (top&&stk[top]>a[j]) cur=max(cur,stk[top--]);
stk[++top]=cur; ret+=j-i+1-top;
}
printf("%lld\n",ret);
}
return 0;
}
D2. Range Sorting (Hard Version)
考虑上面的三元组的个数如何统计,这里看了Tutorial才知道可以这样搞,感觉有点玄妙的说
考虑枚举\(\min_\limits{k<j\le r} a_j\)的值\(a_i\),然后可以用单调栈求出它左右第一个小于它的位置,记为\(x,y\),这样右端点的取法就确定了
然后考虑此时左端点的取值范围,设\(a_t\)为\(x\)之前的第一个大于\(a_i\)的数,显然此时\(l\)不能取\(\le t\)的值
那么对答案的贡献就是\((x-t)\times (y-i)\),而找\(t\)的过程可以写一个类似于ST表的倍增
总复杂度\(O(n\log n)\)
#include<cstdio>
#include<iostream>
#define RI register int
#define CI const int&
using namespace std;
const int N=300005;
int t,n,a[N],mx[N][20],stk[N],top,L[N],R[N]; long long ans;
int main()
{
//freopen("CODE.in","r",stdin); freopen("CODE.out","w",stdout);
for (scanf("%d",&t);t;--t)
{
RI i,j; for (scanf("%d",&n),ans=0,i=1;i<=n;++i)
scanf("%d",&a[i]),mx[i][0]=a[i],ans+=1LL*i*(i-1)/2LL;
for (j=1;(1<<j)<=n;++j) for (i=n;i-(1<<j)+1>=1;--i)
mx[i][j]=max(mx[i][j-1],mx[i-(1<<j-1)][j-1]);
for (stk[top=0]=0,i=1;i<=n;++i)
{
while (top&&a[stk[top]]>a[i]) --top;
L[i]=stk[top]; stk[++top]=i;
}
for (stk[top=0]=n+1,i=n;i>=1;--i)
{
while (top&&a[stk[top]]>a[i]) --top;
R[i]=stk[top]; stk[++top]=i;
}
for (i=1;i<=n;++i)
{
int pos=L[i]; for (j=19;~j;--j)
if (pos-(1<<j)+1>=1&&mx[pos][j]<a[i]) pos-=(1<<j);
ans-=1LL*(L[i]-pos)*(R[i]-i);
}
printf("%lld\n",ans);
}
return 0;
}
E. Palindrome Partition
不是这题的思路怎么和D题一个模子里出来的,都是只有一个转移点的区间DP
首先还是很容易想到设\(f_{l,r}\)表示\([l,r]\)的答案,但是这题有一个很显然的贪心性质,我们找到最小的\(k\in[l,r]\)满足\([l,k]\)是even palindrome的,则可以直接转移\(f_{l,r}=f_{k,r}+1\)
因此可以直接改写状态,\(f_i\)表示以\(i\)开头的子串中even palindrome的个数,设它的转移点为\(pos_i\),则\(f_i=f_{pos_i}+1\)
那么现在的问题就是怎么对每个位置求出其转移点,不难想到先用Manacher求出以\(i\)为中心的最大回文半径\(r_i\)
显然\(i\)能影响的就是\([i-r_i+1,i]\)这段区间的\(pos\),只要把区间内的\(pos\)向\(i\)取\(\min\)即可
当然实现起来我们不需要写类似于吉司机线段树这样的东西,直接从大到小枚举\(i\),这样相当于每次做区间赋值即可,直接用线段树即可解决
总复杂度\(O(n\log n)\)
#include<cstdio>
#include<iostream>
#define RI register int
#define CI const int&
using namespace std;
const int N=500005;
int t,n,f[N]; char s[N];
namespace Manacher
{
char ns[N<<1]; int r[N<<1],len,pos,mx;
inline void expand(char *s)
{
ns[0]='#'; ns[len=1]='$'; pos=mx=0;
for (RI i=1;i<=n;++i) ns[++len]=s[i],ns[++len]='$'; ns[len+1]='%';
}
inline void solve(char *s)
{
expand(s); for (RI i=1;i<=len;++i)
{
if (i<mx) r[i]=min(r[2*pos-i],mx-i); else r[i]=1;
while (ns[i-r[i]]==ns[i+r[i]]) ++r[i];
if (i+r[i]>mx) mx=i+r[i],pos=i;
}
}
};
using namespace Manacher;
class Segment_Tree
{
private:
struct segment
{
int val,tag;
}node[N<<2];
#define V(x) node[x].val
#define T(x) node[x].tag
#define TN CI now=1,CI l=1,CI r=n
#define LS now<<1,l,mid
#define RS now<<1|1,mid+1,r
inline void apply(CI now,CI mv)
{
V(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)
{
V(now)=-1; T(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);
}
inline int query(CI pos,TN)
{
if (l==r) return V(now); int mid=l+r>>1; pushdown(now);
if (pos<=mid) return query(pos,LS); else return query(pos,RS);
}
#undef V
#undef T
#undef TN
#undef LS
#undef RS
}SEG;
int main()
{
//freopen("CODE.in","r",stdin); freopen("CODE.out","w",stdout);
for (scanf("%d",&t);t;--t)
{
scanf("%d%s",&n,s+1); solve(s);
RI i; for (SEG.build(),i=n-1;i>=1;--i)
if (r[2*i+1]=(r[2*i+1]-1)/2) SEG.modify(i-r[2*i+1]+1,i,i);
long long ans=0; for (f[n+1]=0,i=n;i>=1;--i)
{
int pos=SEG.query(i); if (!~pos) f[i]=0;
else f[i]=f[2*pos-i+2]+1; ans+=f[i];
}
printf("%lld\n",ans);
}
return 0;
}
Postscript
补场CF放松下,滚回去写DP专题了qwq
标签:CI,const,int,873,Codeforces,--,Div,include,define From: https://www.cnblogs.com/cjjsb/p/17413031.html