CF停课刷题记录

CF停课刷题记录

DAY1

CF1400E Clear the Multiset

题意

给定一个长度为\(n\)数列\(\{a_n\}\),你可以进行如下操作:

  • 任意选择一个区间\([l,r](l\le r)\),使区间内的每一个数减\(1\)
  • 任意选择一个点\(p\)和一个正整数\(x(x\ge 1\),使\(a_p\)减去\(x\)

求把原数列全部变为\(0\)的最少的操作次数。
\(1\le n\le 5000\)\(0\le a_i\le 10^9\)

分析

发现清空一个区间 \([l,r]\) 有两种方式,第一种是每次把一个数变为 \(0\),第二种是整段减最小值,这样原来的区间就变成了左右两个小区间,中间一个元素是 \(0\)。只有这两种做法可能最优,第一种很好理解,第二种因为如果在大区间都没有整段减,那在小区间就更不可能用了。分治计算即可。

核心代码

int n,a[MAXN];
int solve(int l,int r){
    if(l>r) return 0;if(l==r) return !!a[l];int p=l;
    for(int i=l;i<=r;i++) if(a[p]>a[i]) p=i;
    int x=a[p];for(int i=l;i<=r;i++) a[i]-=x;
    return qmin(r-l+1,solve(l,p-1ll)+solve(p+1ll,r)+x);
}
signed main(){
    qread(n);int i,j;for(i=1;i<=n;i++) qread(a[i]);
    printf("%lld\n",solve(1,n));
    return 0;
}

record

CF1110D Jongmah

题意

你在玩一个叫做 Jongmah 的游戏,你手上有 \(n\) 个麻将,每个麻将上有一个在 \(1\)\(m\) 范围内的整数 \(a_i\)

为了赢得游戏,你需要将这些麻将排列成一些三元组,每个三元组中的元素是相同的或者连续的。如 \(7,7,7\)\(12,13,14\) 都是合法的。你只能使用手中的麻将,并且每个麻将只能使用一次。

请求出你最多可以形成多少个三元组。

数据范围:\(1\le n,m\le 10^6\)\(1\le a_i\le m\)

分析

考虑 dp,设 \(dp_i\) 表示用了编号前 \(i\) 的麻将能组成的最多三元组数,但发现不好转移,于是状态中再记 \((i,i+1,i+2)\)\((i-1,i,i+1)\) 的个数,又发现如果这两种三元组个数 \(\geq 3\) 就一定能替换成数量相等的 \((i,i,i)\) 三元组,于是只用枚举到 \(2\) 即可。

核心代码

qread(n,m);int i,j,k,l;
for(i=1;i<=n;i++){
    int x;qread(x);cnt[x]++;
}for(i=1;i<=m;i++) for(j=0;j<3;j++) for(k=0;k<3;k++) for(l=0;l<3;l++) 
    if(cnt[i]>=j+k+l) dp[i][k][l]=qmax(dp[i][k][l],dp[i-1][j][k]+(cnt[i]-j-k-l)/3+l);
printf("%d\n",dp[m][0][0]);

record

CF1407D Discrete Centrifugal Jumps

题意

\(n\) 个高楼排成一行,每个楼有一个高度 \(h_i\)。称可以从楼 \(i\) 跳到 楼 \(j\),当 \(i\), \(j\) ( \(i \lt j\) )满足以下三个条件之一:

  • \(i+1=j\)
  • \(\max(h_{i+1},h_{i+2},\cdots,h_{j-1})\lt\min(h_i,h_j)\)
  • \(\min(h_{i+1},h_{i+2},\cdots,h_{j-1})\gt\max(h_i,h_j)\)

现在你在楼 \(1\),请求出跳到楼 \(n\) 最少要跳几次。

\(2 \leq n \leq 3\cdot 10^5\), \(1\leq h_i \leq 10^9\)

分析

发现第二个条件就相当于中间的全大于两边的,第三个条件相当于中间的全小于两边的。于是可以维护一个单调递增的单调栈和一个单调递减的单调栈。对于单调递增的单调栈,发现如果栈顶需要弹,那么栈顶到新加入的这位中间的高度一定都大于这位的高度,也一定大于栈顶之下的元素的高度。设 \(dp_i\) 表示从 \(1\) 跳到 \(i\) 的最少次数,那么此时符合条件二, \(dp_i\) 就能从栈顶之下的 dp 值转移。条件三同理。

核心代码

qread(n);int i,j;for(i=1;i<=n;i++) qread(a[i]);mem(dp,0x7f);
dp[1]=0;t1=t2=1;s1[1]=s2[1]=1;
for(i=2;i<=n;i++){
    dp[i]=dp[i-1]+1;
    while(t1&&a[i]>=a[s1[t1]]){
        if(a[s1[t1]]!=a[i]) dp[i]=qmin(dp[i],dp[s1[t1-1]]+1);t1--;
    }while(t2&&a[i]<=a[s2[t2]]){
        if(a[s2[t2]]!=a[i]) dp[i]=qmin(dp[i],dp[s2[t2-1]]+1);t2--;
    }s1[++t1]=s2[++t2]=i;
}printf("%d\n",dp[n]);

record

CF1389E Calendar Ambiguity

题意

Berland 的一年有 \(m\) 月,每个月有 \(d\) 天,每周有 \(w\) 天。保证一年的第一天一定是周一。
求数对 \((x,y)\) 的个数,使得 \(x\lt y\)\(x\) 月的第 \(y\) 天和 \(y\) 月的第 \(x\) 天在一周中是同一天。

分析

推一波式子:

\[(x-1)d+y \equiv (y-1)d+x\pmod{ w} \\ xd-d+y \equiv yd-d+x\pmod{w} \\ (x-y)d \equiv x-y\pmod{w} \ \ \ \ \ \\ \Longleftrightarrow w\mid(x-y)(d-1) \\ \]

答案就变成了:

\[\sum\limits_{i=1}^{\min\{m,d\}} \left\lfloor \frac{\min\{m,d\}-y}{t} \right\rfloor \\ \]

,直接等差数列求和即可。

核心代码

while(T--){
    qread(m,d,w);
    if(d==1){
        printf("0\n");continue;
    }int t=lcm(d-1ll,w)/(d-1);
    if(t-1>qmin(m,d)){
        printf("0\n");continue;
    }int mn=qmin(m,d),k=mn/t-1;
    int ans=t*((1+k)*k/2);
    ans+=(mn-(k+1)*t)*(k+1);
    printf("%lld\n",ans);
}

record

DAY2

CF1542D Priority Queue

题意

给定一个序列 \(A\)\(A\) 的每一个元素形如 + x-,其中 \(x\) 为一个整数。
对于一个每个元素都形如 + x- 的序列 \(S\),按如下方式计算 \(f(S)\) 的值:

  • 你需要依次遍历 \(S\) 中的元素,并且维护一个可重集 \(T\)
  • 对于每个 \(S\) 中的元素,若其为 + x,那么就将 \(x\) 加入 \(T\),否则就删除 \(T\) 最小的数。特别的,若 \(T\) 中没有数,那么就不进行删除操作。
  • 在遍历完 \(S\) 中的元素后,将可重集 \(T\) 中所有数的和 \(sum\) 算出来。\(sum\) 即为 \(f(S)\) 的值。
    定义 \(b\)\(a\) 的子序列当且仅当 \(b\) 是由 \(a\) 在不改变原有顺序的情况下删除若干元素得到的。现在对于 \(A\) 的所有子序列 \(B\),蓝想让你求出 \(f(B)\) 的和模 \(998244353\) 的值。
    本题有 \(1 \leq n \leq 500\),并且对于每个形如 + x 元素中的 \(x\),有 \(1 \leq x \lt 998244353\)

分析

考虑对于每一位 \(i\),如果这个元素形如 \(+x_i\),就算出这位对于答案的贡献。对于每一位 dp,\(dp_{j,k}\) 表示枚举到第 \(j\) 个元素,\(T\) 中有 \(k\) 个数比 \(x_i\) 小的方案数,最后答案就是 \(x_i\sum\ dp_{n,k}\)。思考如何转移,发现有几种可能的情况:

  1. \(j\) 个元素是 \(-\)

    此时该元素用不用均可,于是有 \(dp_{j,k}=dp_{j-1,k}+dp_{j-1,k+1}\),但要特殊判断一下如果 \(k=0 \and j<i\),说明减掉最小值也不会对小于 \(x_i\) 的数有影响,于是有 \(dp_{j,k}=dp_{j-1,k}\)

  2. \(j\) 个元素是 \(+x_j\)\(x_j>x_i\)

    这样加不加入小于 \(x_i\) 的数都不变,于是有 \(dp_{j,k}=2dp_{j-1,k}\)

  3. \(j\) 个元素是 \(+x_j\)\(x_j<x_i\)

    此时有 \(dp_{j,k}=dp_{j-1,k}+dp_{j,k-1}\),特判一下 \(k=0\) 的情况。

  4. \(j\) 个元素是 \(+x_j\)\(x_j=x_i\)

    如果 \(j<i\) 就相当于情况 \(3\),否则相当于情况 \(2\)

最后将每个 \(+\) 的贡献求和即可。

核心代码

scanf("%lld",&n);int i,j,k;
for(i=1;i<=n;i++){
    scanf("%s",s);
    a[i].f=(s[0]=='+');
    if(a[i].f) scanf("%lld",&a[i].x);else a[i].x=-1;
}for(i=1;i<=n;i++){
    if(!a[i].f) continue;mem(dp,0);dp[0][0]=1;
    for(j=1;j<=n;j++){
        if(j==i){
            memcpy(dp[j],dp[j-1],sizeof(dp[j]));continue;
        }for(k=0;k<=n;k++){
            if(!a[j].f){
                (dp[j][k]=dp[j-1][k]+dp[j-1][k+1])%=mod;
                if(!k&&j<i) (dp[j][k]+=dp[j-1][k])%=mod;
            }else if(a[j].x>a[i].x||(a[j].x==a[i].x&&j>i)) (dp[j][k]=(dp[j-1][k]<<1ll))%=mod;
            else (dp[j][k]=dp[j-1][k]+(k?dp[j-1][k-1]:0))%=mod;
        }
    }int sum=0;for(j=0;j<=n;j++) (sum+=dp[n][j])%=mod;
    (ans+=a[i].x*sum%mod)%=mod;
}printf("%lld\n",ans);

record

CF1557D Ezzat and Grid

题意

一个 \(n\)\(10^9\) 列的表格,\(m\) 次操作,形如 x l r,每次将第 \(x\) 行的 \([l,r]\) 涂黑,问最后至少删掉几行使得每相邻的两行都至少有一列使得这两行的该列都是黑的。\(n,m\leq 3\times 10^5\)

分析

线段树优化 dp。\(dp_i\) 表示以 \(i\) 结尾最多能保留多少行,很显然有转移方程:

\[dp_i=\max\limits_{j<i}\{dp_j\}+1 \]

,且满足第 \(i\) 行和第 \(j\) 行可以相邻。但是发现复杂度是 \(\mathcal O(n^2)\) 的,考虑优化。发现最大的 \(dp_j\) 一定是没有被其他涂层覆盖的,否则从覆盖它的位置转移一定更优。于是开一棵线段树支持区间推平、区间求最大值即可。

核心代码

int n,m;Pair tr[MAXN<<2],tag[MAXN<<2];int g[MAXN];
struct node{
    int l,r;node(){;}
    node(int _l,int _r):l(_l),r(_r){}
};vector<node>a[MAXN];int tmp[MAXN];
#define ls p<<1
#define rs p<<1|1
#define mid ((l+r)>>1)
inline void pup(int p){tr[p]=qmax(tr[ls],tr[rs]);}
inline void f(int p,int l,int r,Pair k){tr[p]=k;tag[p]=k;}
inline void pwn(int p,int l,int r){
    if(!tag[p].first) return;
    f(ls,l,mid,tag[p]);f(rs,mid+1,r,tag[p]);tag[p].first=0;
}void build(int p,int l,int r){
    tag[p]=tr[p]=Pair(0,-1);if(l==r) return;
    build(ls,l,mid);build(rs,mid+1,r);
}void upd(int p,int l,int r,int L,int R,Pair c){
    if(L<=l&&r<=R) return f(p,l,r,c),void();
    pwn(p,l,r);if(L<=mid) upd(ls,l,mid,L,R,c);
    if(R>mid) upd(rs,mid+1,r,L,R,c);pup(p);
}Pair que(int p,int l,int r,int L,int R){
    if(L<=l&&r<=R) return tr[p];pwn(p,l,r);
    Pair res=make_pair(0,-1);if(L<=mid) res=que(ls,l,mid,L,R);
    if(R>mid) res=qmax(res,que(rs,mid+1,r,L,R));return res;
}int tot;bitset<MAXN>vis;
signed main(){
    qread(n,m);int i,j;mem(g,-1);
    for(i=1;i<=m;i++){
        int x,l,r;qread(x,l,r);tmp[++tmp[0]]=l;tmp[++tmp[0]]=r;
        a[x].push_back(node{l,r});
    }sort(tmp+1,tmp+tmp[0]+1);int t=unique(tmp+1,tmp+1+tmp[0])-tmp-1;build(1,1,tmp[0]);
    for(i=1;i<=n;i++) for(j=0;j<a[i].size();j++)
        a[i][j].l=lower_bound(tmp+1,tmp+1+t,a[i][j].l)-tmp,
        a[i][j].r=lower_bound(tmp+1,tmp+1+t,a[i][j].r)-tmp;
    for(i=1;i<=n;i++){
        Pair tmp=Pair(0,-1);
        for(auto j:a[i]) tmp=qmax(tmp,que(1,1,t,j.l,j.r));
        g[i]=tmp.second;tmp.first++;tmp.second=i;
        for(auto j:a[i]) upd(1,1,t,j.l,j.r,tmp);
    }Pair ans=que(1,1,t,1,t);printf("%lld\n",n-ans.first);
    for(i=ans.second;i!=-1;i=g[i]) vis[i]=true;
    for(i=1;i<=n;i++) if(!vis[i]) printf("%lld ",i);
    return 0;
}

record

CF1034B Little C Loves 3 II

题意

给定一个\(n\times m\)的棋盘,每次可以在上面放一对棋子\((x,y)(x',y')\),要求\(\mid x-x'\mid+\mid y-y'\mid==3\),问最多可以放多少个棋子。

分析

手推出几个较小棋盘的最优摆放方式,大的拿这些拼合,归纳总结即可。

record

CF1335F Robots on a Grid

题意

给出一个 \(n\times m\) 的网格,每个格子被染成黑色或白色,并且在每个格子上都有一个方向。在一些格子中放置机器人,则每个时刻机器人会按照当前格子的方向行走一步。注意:两个机器人不能同时走到一个格子里。
请你寻找一个合适的放置方案,使得机器人数量和机器人占据的黑格数量均最多。

分析

发现如果机器人走到同一个环中,那么它们的距离一定一直不变,一定无法相遇;而如果走到不同环中,各自都出不来,也一定无法相遇。于是两个机器人只能在走到环的路上相遇。于是最多走 \(n\times m\) 次就能保证该相遇的都相遇了。倍增求能走到的位置即可。

核心代码

while(T--){
    scanf("%d%d",&n,&m);vis[0].reset();vis[1].reset();
    for(i=1;i<=n;i++){
        scanf("%s",s+1);
        for(j=1;j<=m;j++) a[(i-1)*m+j]=(s[j]^48)&1;
    }for(i=1;i<=n;i++){
        scanf("%s",s+1);
        for(j=1;j<=m;j++){
            if(s[j]=='U') nxt[0][(i-1)*m+j]=(i-1)*m+j-m;
            if(s[j]=='D') nxt[0][(i-1)*m+j]=(i-1)*m+j+m;
            if(s[j]=='L') nxt[0][(i-1)*m+j]=(i-1)*m+j-1;
            if(s[j]=='R') nxt[0][(i-1)*m+j]=(i-1)*m+j+1;
        }
    }for(i=1;i<=20;i++)
        for(j=1;j<=n*m;j++) nxt[i][j]=nxt[i-1][nxt[i-1][j]];
    for(i=1;i<=n*m;i++){
        int pos=i;
        for(j=20;j>=0;j--){
            if((n*m)&(1<<j)) pos=nxt[j][pos];
        }vis[a[i]][pos]=true;
    }int tot=0,ans=0;
    for(i=1;i<=n*m;i++){
        if(vis[0][i]) ans++,tot++;
        else if(vis[1][i]) tot++;
    }printf("%d %d\n",tot,ans);
}

record

posted @ 2022-09-21 19:33  l_x_y  阅读(92)  评论(0编辑  收藏  举报