杂题

不知道扔哪的题就扔这

大概是个乱搞专题(

CF1651D

给你$n$个点,对每个点找曼哈顿距离最近的没有被占用的点

sol:

考虑到一个点最近的空点的出现

考虑从左往右,我们贪心的想,我们肯定是一直往四个方向bfs,直到遇到一个空点为止

但是如果对每个有的点都这么做一遍显然会T飞

于是我们反过来做,考虑用空点去扩展。

考虑到空点附近一定有一个有的点

所以可以从周围有空点的有的点开始,遍历所有有的点,做一遍bfs即可。

#include<bits/stdc++.h>
using namespace std;
int dx[]={0,1,-1,0,0};
int dy[]={0,0,0,1,-1};
pair<int,int>ans[200005]; 
queue<pair<int,int> >bfs;
pair<int,int> a[200005];
bool vis[200005];
map<pair<int,int>,int>mp;
int N;
int main(){
    scanf("%d",&N);
    for (int i=1;i<=N;i++){
        scanf("%d%d",&a[i].first,&a[i].second);
        mp[a[i]]=i;
    }
    for (int i=1;i<=N;i++){
        for (int j=1;j<=4;j++){
            int x=a[i].first+dx[j],y=a[i].second+dy[j];
            pair<int,int> nww={x,y};
            if (!mp[nww]) {
                vis[i]=1;
                ans[i]=nww;
                break;
            }
        }
        if (vis[i]==1) bfs.push(a[i]);
    }
    while (!bfs.empty()){
        pair<int,int> s=bfs.front();
        bfs.pop();
        if (!mp[s]) continue;
        int x=mp[s];
        for (int j=1;j<=4;j++){
            pair<int,int> nww;
            nww={s.first+dx[j],s.second+dy[j]};
            if (mp[nww] && !vis[mp[nww]]){
                vis[mp[nww]]=1;
                ans[mp[nww]].first=ans[mp[s]].first;
                ans[mp[nww]].second=ans[mp[s]].second;
                bfs.push(nww);
                }
            }
        }
    for (int i=1;i<=N;i++) printf("%d %d\n",ans[i].first,ans[i].second);
    return 0;
}

 CF1668D


划分问题,有个最暴力的$dp$

$dp_{i,j}$表示考虑到第$i$位,划分了$j$段的方案

转移显然

发现在这题里,划分段数没有约束

于是状态修改为$dp_i$表示第$i$个位置结尾的最优

转移就是

$dp_r = max{dp[l] + (r-l+1)} (sum_r > sum_{l-1}) $

$dp_r = max(dp[l] - (r-l+1) (sum_r < sum_{l-1})$

$dp_r = max{dp_l} sum[r]==sum[l-1]$

然后把有$l$的部分扔到一起去,会发现其实只要找后缀$sum_i - i $和前缀$sum_i + i$的最值

这个东西离散化树状数组一下就解决了。

#include<bits/stdc++.h>
using namespace std;
int Lowbit(int x){return (x&(-x));}
int mx;
int dp[1000005];
int Ans[1000005];
struct Node{
    int Tree[1000005];
    int Add(int x,int y){for (int i=x;i<=mx;i+=Lowbit(i)) Tree[i]=max(Tree[i],y);}
    int query(int x){int ans=-1e9; for (int i=x;i;i-=Lowbit(i)) ans=max(ans,Tree[i]); return ans;}
    void Clear(){for (int i=1;i<=mx;i++) Tree[i]=-1e9; }
}Tree1,Tree2;
long long Sum[1000005];
long long a[1000005],b[1000005];
int main(){
    int T;
    cin>>T;
    while (T--){
    int N;
    cin>>N;
    mx=N<<1;
    Tree1.Clear(),Tree2.Clear();
    for (int i=1;i<=N;i++)
        Ans[i]=-1e9,dp[i]=-1e9;
    for (int i=1;i<=N;i++){
        scanf("%lld",&a[i]);
        b[i]=a[i];
        Sum[i]=Sum[i-1]+a[i];
        a[i]=Sum[i];
    }
    //for (int i=1;i<=N;i++){
    //    cout<<Sum[i]<<" ";
    //}
    //cout<<endl;
    sort(Sum+1,Sum+N+1);
    int N1=unique(Sum+1,Sum+N+1)-Sum-1;
    for (int i=1;i<=N;i++){
        a[i]=lower_bound(Sum+1,Sum+N1+1,a[i])-Sum;
    }
    long long Summ=0;
    for (int i=1;i<=N;i++){
        dp[i]=Tree1.query(a[i]-1)+i;
        //cout<<a[i]-1<<" ";
        //cout<<Tree1.query(a[i]-1)<<endl;
        dp[i]=max(dp[i],Tree2.query(N1-a[i])-i);
        dp[i]=max(dp[i],Ans[a[i]]);
        Summ+=b[i];
        if (Summ==0) dp[i]=max(dp[i],0);
        else if (Summ>0) dp[i]=max(dp[i],i);
        else dp[i]=max(dp[i],-i);
        Tree1.Add(a[i],dp[i]-i);
        Tree2.Add(N1-a[i]+1,dp[i]+i);
        Ans[a[i]]=max(Ans[a[i]],dp[i]);
    }
        cout<<dp[N]<<endl;
    }
    return 0;
}

 $CF1701D$

首先一个比较显然的推论,每个点可以放置的数字范围是可以直接算出来的

现在问题就转化成,把$1~n$这$n$个数字放进$n$个坑里,使得每个坑都满足条件

把每个点的范围扔到数轴上,然后从$1~n$的去考虑放置这些数字

那么我们贪心的想,一定是优先解决限制的比较死的区间是比较优秀的

也就是说,在一个数字同时能满足多个区间的时候,它一定优先满足那个右端点最靠左的

这东西拉个小根堆维护就好了。

#include<bits/stdc++.h>
using namespace std;
int T,N;
struct Node{
    int L,pos;
}a[500005];
int ans[500005],b[500005];
int temp(Node a,Node b){
    return (a.L<b.L);
}
priority_queue<pair<int,int>,vector<pair<int,int> >,greater<pair<int,int> > > que;
int main(){
    scanf("%d",&T);
    while (T--){
        scanf("%d",&N);
        for (int i=1;i<=N;i++){
            scanf("%d",&b[i]);
            a[i].L=i/(b[i]+1)+1;
            a[i].pos=i;
        }
    sort(a+1,a+N+1,temp);
    int nw=1;
    for (int i=1;i<=N;i++){
        while (nw<=N&&a[nw].L<=i){
            int id=a[nw].pos;
            if (b[id]!=0) que.push({id/b[id],id});
            else que.push({N,id});
            nw++;
        }
        pair<int,int> As=que.top();
        que.pop();
        ans[As.second]=i;
    }
    for (int i=1;i<=N;i++)
        cout<<ans[i]<<" ";
    cout<<endl;
    }
    return 0;
}

$CF1695D2$

老年人智商不行了

首先可以明确一个点,显然取全部的叶子节点一定可以确定所有的$x$,因为如果把树拆成一堆链的话,叶子节点就是它的链头和链尾,那么对于任意一条链,知道链头和链尾就能知道整个树

现在的问题是:显然有一部分叶子节点是不必要的,那么要怎么找到那些不必要的点呢

考虑一个多分叉的树,如果它有两个及以上的子树内不存在关键点的话,那么这两个子树就一定不能被分辨出来,因为他们到剩下的子树下所有点的距离是相等的。

于是,反过来说,对于一个有两个以上子树的点,这个点只能在一个子树内节省一个关键点。(*)

那么其实,就可以理解为,先把所有叶子节点选上,然后能省则省

具体做法就是,从每个度数为$1$的节点不断暴力的跳,直到遇到第一个有多分叉的点,并且做标记。如果到这个点的时候,这个点还没被做过标记的话,就说明这个叶子节点可以作为被节省的那个点,就不用$+1$,然后大力$dfs$就好了。

#include<bits/stdc++.h>
using namespace std;
int N;
int T;
bool vis[200005];
int d[200005],ans;
vector<int> E[200005];
void dfs(int Now,int Fa){
    for (auto v:E[Now]){
        if (v==Fa) continue;
        if (d[v]<=2) dfs(v,Now);
        else{
            if (vis[v]) ans++;
            vis[v]=true;
            return;
        }
    }
}
int main(){
    cin>>T;
    while (T--){
        ans=0;
        scanf("%d",&N);
        if (N==1) {
            printf("0\n");
            continue;
        }
        for (int i=1;i<=N;i++){
            E[i].clear();
            vis[i]=false;
            d[i]=0;
        }
        for (int i=1;i<N;i++){
            int x,y;
            scanf("%d%d",&x,&y);
            E[x].push_back(y);
            E[y].push_back(x);
            d[x]++;
            d[y]++;
        }
        for (int i=1;i<=N;i++){
            if (d[i]==1){
                dfs(i,i);
            }
        }
        if (ans==0) ans++;
        printf("%d\n",ans);
    }
    return 0;
}

 CF1696D

很奇妙的一个题(

考虑对于一个区间来说,一个点$mx$,一个点$mn$

不妨设$mx$在左边,$mn$在右边

$mx$左边的点一定没有路径链到$mx$右边

因为如果要链到$mx$右边的话,就说明左边或者右边有一个点比$mx$更大

那么同理,$mn$也是

那么就会发现,走$mx$到$mn$一定是最优的

于是我们就把$[1,n]$分成了$[1,mx]$和$[mx,mn]$和$[mn,N]$

于是我们就可以直接往下分治了

因为这样每次总长度至少减少$1$,所以最多是$n$次

至于最大最小值的位置,随便拉个数据结构维护就好了。

#include <bits/stdc++.h>
using namespace std;
int pos[250005],Treemx[4*250060],a[250005],Treemn[4*250060],N;
void Build(int Now,int l,int r){
    if (l==r){
        Treemx[Now]=a[l];
        Treemn[Now]=a[r];
        return;
    }
    int mid=(l+r)>>1;
    Build(Now<<1,l,mid);
    Build(Now<<1|1,mid+1,r);
    Treemx[Now]=max(Treemx[Now<<1],Treemx[Now<<1|1]);
    Treemn[Now]=min(Treemn[Now<<1],Treemn[Now<<1|1]);
}
int GetMn(int Now,int l,int r,int L,int R){
    if (L<=l&&r<=R) return Treemn[Now];
    int mid=(l+r)>>1;
    int ans=1e9;
    if (L<=mid) ans=min(ans,GetMn(Now<<1,l,mid,L,R));
    if (mid<R) ans=min(ans,GetMn(Now<<1|1,mid+1,r,L,R));
    return ans; 
}
int GetMx(int Now,int l,int r,int L,int R){
    if (L<=l&&r<=R) return Treemx[Now];
    int mid=(l+r)>>1;
    int ans=0;
    if (L<=mid) ans=max(ans,GetMx(Now<<1,l,mid,L,R));
    if (mid<R) ans=max(ans,GetMx(Now<<1|1,mid+1,r,L,R));
    return ans; 
}
int Getans(int l,int r){
    if (l>=r) return 0;
    if (l+1==r) return 1;
    int mn=GetMn(1,1,N,l,r);
    int mx=GetMx(1,1,N,l,r);
    int L=pos[mn],R=pos[mx];
    if (L>R) swap(L,R);
    return Getans(l,L)+Getans(R,r)+1;
}
int main(){
    int T;
    cin>>T;
    while(T--){
        scanf("%d",&N);
        for (int i=1;i<=N;i++){
            scanf("%d",&a[i]);
            pos[a[i]]=i;
        }
        //cout<<"qwq"<<endl;
        Build(1,1,N);
        printf("%d\n",Getans(1,N));
    }
    return 0;
}
/*
1
10
7 4 8 1 6 10 3 5 2 9
*/

 CF1689D

开始看到最大值最小想到的是二分

然后发现二分完,每个点占据的区域是一个斜的正方形

然后想到转45°坐标

然后想到转切比雪夫距离

切比雪夫距离是最大的$max(|xi-x|,|yi-y|)$

发现这个新图可以把$x$和$y$拆开讨论,然后就是最大的最大,直接取全部里的最大就好了。

转完之后取$max$操作可以分开算,然后找到每个黑点,暴力找到这个新图的黑点边界,暴力枚举每个点计算最大的$x$向距离和$y$向距离就好了。

#include <bits/stdc++.h>
using namespace std;
int N,M;
char c[5005][5005];
int T;
int A,B,C,D;
int main(){
    cin>>T;
    while (T--){
        scanf("%d%d",&N,&M);
        A=-1e9,B=1e9+7,C=-1e9,D=1e9+7;
        for (int i=1;i<=N;i++)
            for (int j=1;j<=M;j++){
                cin>>c[i][j];
                if (c[i][j] == 'B'){
                    int nx=i+j,ny=i-j;
                    A=max(nx,A);
                    B=min(nx,B);
                    C=max(ny,C);
                    D=min(ny,D);
                }
            }
        //cout<<A<<" "<<B<<" "<<C<<" "<<D<<endl;
        int ans=1e9+7,X=0,Y=0;
        for (int i=1;i<=N;i++)
            for (int j=1;j<=M;j++){
                int nx=i+j,ny=i-j;
                int dis=max(max(abs(ny-C),abs(ny-D)),max(abs(nx-A),abs(nx-B)));
                //cout<<dis<<" "<<ans<<endl;
                if (dis<ans){
                    ans=dis;
                    X=i;
                    Y=j;
                }
            }
        printf("%d %d\n",X,Y);
    }
    return 0;
}

 cf1717D

数论题

把那个$lcm$写出来

设$g$为$gcd(a,b)$

$lcm(c,g) = \frac{c*g}{(c,g)}$

显然,$a$和$b$地位同等,$c$比较特殊,于是我们考虑枚举一下$c$

当枚举完$c$之后,我们就会发现有$a+b = n-c$成立

那么,不妨设$(a,b) = k$,同时$a+b = n-c$成立

假装我们已经知道$k$了,那么我们只需要知道有多少组$k$就可以算答案。

设$n-c = t$

则有$(a,t - a) = k$

套路化的两边除个$k$

$(\frac{a}{k},\frac{t-a}{k}) = 1$

根据这个式子,可以知道一件事:

$a$是$k$的倍数,同时$t-a$也是$k$的倍数

通过这两个条件可以发现,$t$是$k$的倍数(同余)

于是我们就把$k$的枚举条件缩小到了$t$的所有约数里

但是我们还需要知道有多少组这样的$k$

设$\frac{a}{k} = m$

$(m,\frac{t}{k} - m)  = 1$

$(m,x-m) = 1$,$x$是常数

$(m,x-m) = (m,x) = 1$

所以即等价于$phi(x)$,即$phi(\frac{t}{k})$

#include <bits/stdc++.h>
using namespace std;
const long long fish = 1e9+7;
vector<long long> Div[100005];
int IsPrime[100005],Prime[100005],Index,phi[100005];
void Pre(int mx){
    phi[1] = 1;
    for (int i = 2 ; i <= mx ; i++){
        if (!IsPrime[i]) {Prime[++Index] = i;phi[i] = i - 1;}
        for (int j = 1;j<=Index && i * Prime[j] <= mx ; j++){
            IsPrime[i*Prime[j]] = true;
            if (i%Prime[j] == 0){
                phi[i*Prime[j]] = phi[i] * Prime[j];
            }
            else phi[i*Prime[j]] = phi[i] * (Prime[j] - 1);
        }
    }
    for (int i=1;i<=mx;i++)
        for (int j = i ; j <= mx ; j +=i){
            Div[j].push_back(i);
    }
    phi[1] = 0; 
}
int main(){
    int N;
    cin>>N;
    Pre(N);
    long long ans = 0;
    for (long long c = 1 ; c <= N; c++){
        int x = N-c;
        for (auto g : Div[x]){
            long long as = 1ll*g*c/__gcd(g,c);
            long long times = phi[x/g];
            ans = (ans + as*times)%fish;
        }
    }
    cout<<ans;
    return 0;
}

 CF1749D

发现一定有一种答案是{1,1,1,1,1.....}

于是问题就变成:如何找到一种{1,1,1,1,1....}以外的合法方案

于是问题就变成:

对于一个位置$i$,因为它在{1,1,1,1,1}中会遍历$1..i$的所有数字

也就是说只要存在一个数$x$在$1$到$i$之间,有$(x,a_i) = 1$

就意味着一定有另一组解

也就是说该数字和任何一个数互素就好

但是这个显然不太好讨论

于是我们就讨论它与前面所有数都不互素

于是就包含前面的所有素因子,然后容斥计数一下就好了。

边扫边求答案就好

#include <bits/stdc++.h>
using namespace std;
const int mx = 3e5;
const long long fish = 998244353;
int IsPrime[mx+5],f[mx+5];
int main(){
    long long N,M;
    for (int i = 2 ; i <= mx ; i ++)    
        for(int j = i+i ; j <= mx ; j +=i)
            IsPrime[j] = 1;
    cin>>N>>M;
    long long nw = 1,nw1=1,ans2=1,ans=1,ans3=0;
    for (int i = 2 ; i  <= N ; i ++){
        //-1337 424242424242
        //cout<<nw<<endl;
        if (!IsPrime[i] && nw <= M){
            nw = 1ll*nw * i;
            if (nw > M) {ans = 0;}
            nw1 = M/nw; 
        }
        long long y = nw1 % fish,z=M%fish;
        ans = 1ll* ans%fish * 1ll * y%fish;
        ans2 = 1ll* ans2 * 1ll * z % fish; 
        ans3 = (ans3 + 1ll*z*((ans2-ans+fish)%fish)%fish)%fish;
        //cout<<ans<<" "<<ans2<<" "<<ans3<<" "<<nw<<" "<<nw1<<" "<<y<<endl;
    }
    cout<<ans3%fish;
    return 0;
}

 CF1754E

我怎么就不会概率题啊呜呜

感觉是很经典的模型

参考了官方题解和评论区的题解。

发现最后的序列一定是$00000.....11111$的形式

然后发现该题给出的交换方式,前$cnt_0$个$0$一定是不会被交换到后面去的

于是就可以根据这个划分状态。

设$f[i]$表示当前还有几个$0$不在前$cnt_0$个

那么,考虑某次交换会导致一个0会被换到前$cnt_0$个的概率

$p = \frac{i*i}{\frac{N*(N-1)}{2}}$(因为前面$i$个$1$一定有$0$和它配对,那么前后一共可以产生$i*i$组有价值的交换,剩下的就是$C_N^2$

于是有了$p$之后,就考虑$f$的转移

$f[i] = f[i+1]*p + f[i]*(1-p)$

移项,发现$f[i] = f[i+1] + \frac{1}{p}$

递推即可。

#include<bits/stdc++.h>
using namespace std;
int N;
int T;
long long dp[200005]; 
const long long fish = 998244353;
long long Pow(long long x,int y){
    long long ans = 1; 
    for (;y;y>>=1){
        if (y&1) ans = 1ll * ans * x%fish;
        x = 1ll * x * x %fish;
    }
    return ans;
}
int a[200005];
int main(){
    cin>>T;
    while (T--){
        cin>>N;
        for (int i = 1; i <= N ; i ++)    
            cin>>a[i];
        int cnt0=0;
        for (int i = 1; i <= N ; i ++)
            if (a[i] == 0) cnt0 ++;
        int cnt00 = 0;
        for (int i = 1 ; i <= N ; i ++)
            if (a[i] == 0 && i > cnt0) cnt00 ++;
        dp[cnt00] = 0;
        long long inv = 1ll*N*(N-1)%fish*Pow(2,fish-2)%fish;
        for (int i = cnt00-1 ; i >= 0 ; i--){
            dp[i] = (dp[i+1]+1ll * inv * Pow(1ll*(i+1)*1ll*(i+1)%fish,fish-2)%fish)%fish;
            //cout<<dp[i]<<endl;
        }
        cout<<dp[0] <<'\n'; 
    }
    return 0;
}

 cf1646D

套路化的树dp

发现其实题给条件等价于树上独立集,直接树dp求

问题是方案怎么输出

方案的话,多记录一个$w$数组表示当前点的最小权值和(在选和不选的前提下)

转移的时候注意一下细节就好。

算完之后再跑一遍dp求一下方案。

#include <bits/stdc++.h>
using namespace std;
int cnt,dp[400005][3],ans,ww[400005][3],w[400005],las[400005],Arrive[400005],d[400005],nex[400005],ans1=0;
void jt(int x,int y){
    cnt++;
    nex[cnt] = las[x];
    las[x] = cnt;
    Arrive[cnt] = y;
    d[x] ++;
}
void dfs1(int Now,int fa){
    dp[Now][1] = 1;
    for (int i = las[Now]; i ; i = nex[i]){
        int v = Arrive[i];
        if (v == fa) continue;
        dfs1(v,Now); 
        dp[Now][1] = dp[Now][1] + dp[v][0];
        dp[Now][0] = dp[Now][0] + max(dp[v][0],dp[v][1]);
        ww[Now][1] = ww[Now][1]+ww[v][0];
        if (dp[v][0] > dp[v][1]) ww[Now][0] = ww[Now][0] + ww[v][0];
        if (dp[v][0] < dp[v][1]) ww[Now][0] = ww[Now][0] + ww[v][1];
        if (dp[v][0] == dp[v][1]) ww[Now][0] = ww[Now][0]+min(ww[v][0],ww[v][1]);
    }
    ww[Now][0] += 1;
    ww[Now][1] += d[Now];
}
void dfs2(int Now,int val,int fa){
    if (val == 1){
        w[Now] = d[Now];
        for (int i = las[Now] ; i ; i =nex[i]){
            int v = Arrive[i];
            if (v == fa) continue;
            dfs2(v,0,Now); 
        }
    }else{
        w[Now] = 1;
        for (int i = las[Now];i;i=nex[i]){
            int v = Arrive[i];
            if (v == fa) continue;
            if (dp[v][0] > dp[v][1]) dfs2(v,0,Now);
            if (dp[v][0] < dp[v][1]) dfs2(v,1,Now);
            if (dp[v][0] == dp[v][1]){
                if (ww[v][0] > ww[v][1]) dfs2(v,1,Now);
                else dfs2(v,0,Now);
            }
        }
    }
    ans += w[Now];
}
int main(){
    int N;
    cin>>N;
    for (int i = 1 ; i < N ; i ++){
        int u,v;
        scanf("%d%d",&u,&v);
        jt(u,v);
        jt(v,u);
    }
    if (N == 2){
        cout<<"2 2\n";
        cout<<"1 1";
        return 0;
    }
    dfs1(1,1);
    cout<<max(dp[1][1],dp[1][0])<<" ";
    if (dp[1][1] > dp[1][0]) {cout<<ww[1][1]<<endl;dfs2(1,1,1);}
    if (dp[1][1] < dp[1][0]) {cout<<ww[1][0]<<endl;dfs2(1,0,1);}
    if (dp[1][1] == dp[1][0]){
        if (ww[1][0] < ww[1][1]) {cout<<ww[1][0]<<endl;dfs2(1,0,1);}
        else {cout<<ww[1][1]<<endl;dfs2(1,1,1);}
    }
    for (int i = 1; i <= N; i ++){
        cout<<w[i]<<" ";
    }
    return 0;
}

 cf1646E

考虑重复数字为什么会出现

可以比较显然的发现,只有形如最左边的数字为$a^k$,在$k$不同的情况下,才会出现在$m$列里有重复

比如第$2$行和第$2^2 = 4$行就会有重复。

那么考虑我们要怎么计算不重复的。

如果我们单独的考虑一个数,比如以2为例。

$2,2^2,2^3$

它们会产生的数字是

$2^k,2^{2k},2^{3k}.....$以此类推。

那么我们就会发现,本质上这个问题就变成:

给你前$x$个自然数,会生成$k*x$,$k$从$1$取到$m$,问有多少个不重复的数字。

于是我们类似筛法的去算这个数就好了,每次暴力的循环$1~m$,由于$n$最多$10^6$,所以这个$k$最多取到$20$。

对于$n$个数字,每次统计的时候计算它的$k$是多少,然后加上预处理的答案即可。

注意别用$map$,被卡麻了

#include <bits/stdc++.h>
using namespace std;
int N,M;
bool vis[40000005];
bool vis1[2000005];
long long dp[55]={};
int main(){
    cin>>N>>M;
    for (int i = 1; i <= 20 ; i ++){
        for (int j = i ,k=0; k < M ; k ++,j += i)
            if (!vis[j]){
                vis[j] = true;
                dp[i] ++;
        }
        //cout<<"qwq"<<endl;
        dp[i] += dp[i-1];
    }
    long long ans = 0;
    for (long long i = 2; i <= N ; i ++){
        if (vis1[i]) continue;
        int cnt1 = 0;
        for (long long j =  i ; j <= N ; j  *= i){
                if (j>N) break;
                vis1[j] = true;
                cnt1++;
        }
        //cout<<i<<" "<<cnt1<<endl;
        ans += dp[cnt1];
    }
    cout<<ans+1;
}

 CF1562E


首先发现一个性质,就是一个点如果选了,可以先假装把它的后缀的前缀全选上,作为最初状态,这一定是一个合法状态

然后考虑转移

这个有点类似于最长上升子序列,$dp[i]$表示到$i$这个点为止的最长上升列的长度。

那么考虑$j$如何转移$i$

一定是$j$的后缀的前缀中,和$i$的后缀的前缀,从某个位置开始存在一个$i$比$j$大的情况,此时$j$可以转移到$i$

这也是$lcp$数组的用处。

实际上,我们比较两个后缀的$lcp$,然后比较它们的后一位,如果$j$比$i$小,说明$j$可以放在$i$前面,只需要把$i$和$j$的$lcp$的部分去掉即可。

于是就有了转移。

至于求$lcp$数组,因为我们这里是求两两的$lcp$,所以直接$n^2$枚举点对,递推转移即可。

$lcp[i][j] = (lcp[i+1][j+!]+1)*(s[i] == s[j])$

#include<bits/stdc++.h>
using namespace std;
int T;
int lcp[5005][5005];
int dp[5005];
int main(){
    cin>>T;
    while (T--){
        int N;
        char ss[5005];
        cin>>N;
        N--;
        cin >> ss;
        for (int i = N ; i >= 0 ; i --){
            for (int j = N ; j >= 0 ; j --)
                lcp[i][j] = (1+lcp[i+1][j+1]) * (ss[i] == ss[j]);
        }
        int ans = 0;
        for (int i = 0 ; i <= N ; i ++){
            dp[i] = N-i+1;
            for (int j = i-1; j>=0 ; j --){
                if (ss[i+lcp[i][j]]>ss[j+lcp[i][j]]) dp[i] = max(dp[i],dp[j] + N - i + 1 - lcp[i][j]);
            }
            ans = max(ans,dp[i]);
        }
        cout<<ans<<'\n';
        for (int i = 0 ; i <= N ; i ++)
            dp[i] = 0;
        for (int i = 0 ;i <= N; i ++)
            for (int j = 0 ; j <= N ; j ++){
                lcp[i][j] = 0;
            }
    }
    return 0;
} 

 CF1748E

 

没想出来(把原本序列的笛卡尔树建出来

然后会发现,其实每个点的子树表示了它作为最大值的区间。

而这时候再分析一下原题这个条件,其实就等价于新序列的树与原序列笛卡尔树相同。

$dp[i][j]$表示$i$号点最大为$j$的方案数

树形$dp$转移即可。

感觉笛卡尔树的运用都好巧妙(

#include <bits/stdc++.h>
using namespace std;
int T;
int N,M;
unordered_map<int,int> dp[300005];
int st[300005];
int a[300005],ls[300005],rs[300005];
const int fish = 1e9+7;
void Clear(){
    for (int i = 1; i <= N ; i ++){
        dp[i].clear();
    }
    for (int i = 0 ; i <= M ;i ++){
        dp[0][i] = 1;
    }
    for (int i = 1; i <= N; i ++){
        ls[i] = rs[i] = 0;
    }
} 
int Build(){
    int tp = 0;
    for (int i = 1; i  <= N ; i ++){
        while (tp && a[st[tp]] < a[i]) ls[i] = st[tp--];
        if (tp) rs[st[tp]] = i;
        st[++tp] = i;
    }
    return st[1];
}
void dfs(int Now){
    if (ls[Now] != 0 ) dfs(ls[Now]);
    if (rs[Now] != 0 ) dfs(rs[Now]);
    for (int i = 1; i <= M ; i ++){
        dp[Now][i] = (1ll*dp[ls[Now]][i-1] * dp[rs[Now]][i]%fish)%fish;
        (dp[Now][i] += dp[Now][i-1])%=fish;
    }
}
int main(){
    cin>>T;
    while (T--){
        scanf("%d%d",&N,&M);
        Clear(); 
        for (int i = 1; i <= N ; i ++)    
            scanf("%d",&a[i]);
        int rt = Build();
        dfs(rt);
        printf("%d\n",dp[rt][M]);
    }
    return 0;
}

 CF1775E

 

链接:https://codeforces.com/contest/1775/problem/E

诈骗题(

这种某些地方$+1$,并且在某些地方$-1$的题,有点类似差分。所以我们考虑把差分数组还原回原序列

于是就是求该数组的前缀和。

于是这题就变成:每次选择一些区间,区间$+1$或者区间$-1$,问最少几次

分析到这之后思路就偏了,跑去类似于积木大赛那个题的做了。但实际上是想的太复杂了(

考虑把区间长度变为1,问题就等价于每次选一些数$+1$或一些数$-1$,最少几次让前缀和全$0$

显然分正负算一下几次就好了。

#include <bits/stdc++.h>
using namespace std;
long long Sum[300005];
int main(){
    int T;
    cin>>T;
    while (T--){
        int N;
        cin>>N;
        long long mx = 0,mn = 0;
        for (int i = 1 ; i <= N ; i ++){
            int x;
            scanf("%d",&x);
            Sum[i] = Sum[i-1] + x;
            if (Sum[i] > 0) mx = max(Sum[i],mx);
            else mn=max(abs(Sum[i]),mn); 
        }
        cout<<mx+mn<<endl;
    }
    return 0;
}

 CF1796D

https://codeforces.com/contest/1796


把子段和拆成两个前缀和的形式

$sum_r - sum_{l-1}$

然后显然我们如果固定了在前缀里选几个数,那当前的前缀和就固定了

于是我们只需要知道前缀的最小前缀和即可。

这个显然可以开个数组存一下

那每次无非就直接枚举当前位置用了几次去转移这个数组即可。

更新答案的时候需要后缀能够完成剩下的次数才能更新答案

注意第一个数的特判

没了(

#include <bits/stdc++.h>
using namespace std;
int T;
long long dp[200005][25];
long long Sum[200005],a[200005];
int main(){
    cin>>T;
    while (T--){
        long long ans = 0;
        long long N,K,x;
        cin>>N>>K>>x;
        for (int i = 1 ; i <= N ; i ++)
            scanf("%lld",&a[i]);
        for (int i = 1 ; i <= N ; i ++)
            for (int j = 0 ; j <= K ; j ++)
                dp[i][j] = 1e9;
        Sum[0] = 0;
        for (int i = 1 ; i <= N ; i ++)
            Sum[i] = Sum[i-1] + a[i];
        dp[1][0] = a[1] - x;
        dp[1][1] = a[1] + x;
        if (K >0 && N-1 >= K-1) ans = max(ans,a[1]+x);
        if (N-1 >= K)ans = max(ans,a[1]-x);
        for (int i = 2 ; i <= N ; i ++){
            for (int j = 0 ; j <= K ; j ++){
                if (j > i) break;
                dp[i][j] = min(dp[i-1][j],Sum[i] + j*x - (i-j) * x);
                if (j-1 >=0) dp[i][j] = min(dp[i][j],min(dp[i-1][j-1],Sum[i] + j*x - (i-j) * x));
                if (N-i >= K-j) ans = max(ans,Sum[i] + 1ll*j*x - 1ll*(1ll*i-1ll*j) * 1ll*x-min(dp[i][j],0ll));
        }
    }
        cout<<ans<<'\n';
    }
    return 0;
}

 

posted @ 2022-03-11 16:46  si_nian  阅读(75)  评论(0)    收藏  举报