算法提高课模板

算法提高课模板

第一章 动态规划

275. 传纸条(数字三角形模型

纸条要经由许多同学传到对方手里,小渊坐在矩阵的左上角,坐标 (1,1),小轩坐在矩阵的右下角,坐标 (m,n)。
从小渊传到小轩的纸条只可以向下或者向右传递,从小轩传给小渊的纸条只可以向上或者向左传递。 
在活动进行中,小渊希望给小轩传递一张纸条,同时希望小轩给他回复。
班里每个同学都可以帮他们传递,但只会帮他们一次,也就是说如果此人在小渊递给小轩纸条的时候帮忙,那么在小轩递给小渊的时候就不会再帮忙,反之亦然。 
还有一件事情需要注意,全班每个同学愿意帮忙的好感度有高有低(注意:小渊和小轩的好心程度没有定义,输入时用 0 表示),可以用一个 0∼100 的自然数来表示,数越大表示越好心。
小渊和小轩希望尽可能找好心程度高的同学来帮忙传纸条,即找到来回两条传递路径,使得这两条路径上同学的好心程度之和最大。
现在,请你帮助小渊和小轩找到这样的两条路径。

#include <iostream>
#include <cstring>
#include <algorithm>
using namespace std;
const int N = 55;
int dp[N+N][N][N];
int n,m,mp[N][N];
int main(){
    cin>>n>>m;
    for(int i=1;i<=n;i++)
        for(int j=1;j<=m;j++)cin>>mp[i][j];
    //dp[2][1][1]=0;
    
    for(int k=3;k<=m+n-1;k++){
        for(int i1=1;i1<n;i1++){
            for(int i2=i1+1;i2<=n;i2++){
                
                int j1=k-i1,j2=k-i2;
                if(j1<=0||j1>m||j2<=0||j2>=m)continue;
                int &x=dp[k][i1][i2];
                int w=mp[i1][j1]+mp[i2][j2];
                x=max(x,dp[k-1][i1][i2]+w);
                x=max(x,dp[k-1][i1-1][i2]+w);
                x=max(x,dp[k-1][i1][i2-1]+w);
                x=max(x,dp[k-1][i1-1][i2-1]+w);
            }
        }
    }
    
    cout<<dp[n+m-1][n-1][n];
    //走了k步的时候i1走到了终点的上方,i2走到了终点的下方
    
}

1057. 股票买卖 IV(状态机模型

定一个长度为 N 的数组,数组中的第 i 个数字表示一个给定股票在第 i 天的价格。

设计一个算法来计算你所能获取的最大利润,你最多可以完成 k 笔交易。

注意:你不能同时参与多笔交易(你必须在再次购买前出售掉之前的股票)。一次买入卖出合为一笔交易。

#include <iostream>
#include <cstring>
#include <algorithm>
using namespace std;
int n,k,dp[100010][151][2],w[100010];
int main(){
    cin>>n>>k;
    for(int i=1;i<=n;i++)cin>>w[i];
    memset(dp,0xcf,sizeof dp);
    for(int i=0;i<=n;i++)dp[i][0][0]=0;
    
    for(int i=1;i<=n;i++){
        for(int j=1;j<=k;j++){
            dp[i][j][0]=max(dp[i-1][j][0],dp[i-1][j][1]+w[i]);
            dp[i][j][1]=max(dp[i-1][j][1],dp[i-1][j-1][0]-w[i]);
        }
    }
    int ans=0;
    for(int i=0;i<=k;i++)ans=max(dp[n][i][0],ans);
    cout<<ans;
}

1064. 小国王

在 n×n 的棋盘上放 k 个国王,国王可攻击相邻的 8 个格子,求使它们无法互相攻击的方案总数。

#include <iostream>
#include <cstring>
#include <algorithm>
#define int long long
using namespace std;
int n,k;
const int N = 1<<10;
int dp[12][150][N];
vector<int>state;
vector<int>head[N];
bool check(int x){
    for(int i=0;i<n;i++){
        if( (x>>i&1)&&( x>>i+1 &1) )return 0;
    }
    return 1;
}
int count(int x){
    int ans=0;
    for(int i=0;i<n;i++){
        ans+=((x>>i )&1);
    }
    return ans;
}
signed main(){
    cin>>n>>k;
    for(int i=0;i<1<<n;i++){
        if(check(i))state.push_back(i);
    }
    
    for(int i=0;i<state.size();i++){
        for(int j=0;j<state.size();j++){
            int a=state[i],b=state[j];
            if((a&b)==0&&check(a|b)){
                head[i].push_back(j);
            }
        }
    }
    dp[0][0][0]=1;
    for(int i=1;i<=n+1;i++){
        for(int j=0;j<=k;j++){
            for(int b=0;b<state.size();b++){
                for(int a:head[b]){
                    int c=count(state[a]);
                    if(j>=c){
                        dp[i][j][b]+=dp[i-1][j-c][a];
                    }
                }
            }
        }
    }
    cout<<dp[n+1][k][0];
}

327. 玉米田

农夫约翰的土地由 M×N 个小方格组成,现在他要在土地里种植玉米。
非常遗憾,部分土地是不育的,无法种植。
而且,相邻的土地不能同时种植玉米,也就是说种植玉米的所有方格之间都不会有公共边缘。
现在给定土地的大小,请你求出共有多少种种植方法。
土地上什么都不种也算一种方法。

#include <iostream>
#include <cstring>
#include <algorithm>
using namespace std;
const int N = 1<<12;
const int mod=1e8;
int m,n;
int dp[15][N];
int g[N];
vector<int>state;
vector<int>head[N];
bool check(int s){
    for(int i=0;i<n;i++){
        if((s>>i&1)&&(s>>i+1&1))return 0;
    }
    return 1;
}
int main(){
    cin>>m>>n;
    for(int i=0;i<1<<n;i++){
        if(check(i))state.push_back(i);
    }
    
    for(int i=1;i<=m;i++){
        for(int j=0;j<n;j++){
            //cin>>mp[i][j];
            int t;
            cin>>t;
            g[i]+=!t<<j;
            
        }
    }
    for(int i=0;i<state.size();i++){
        for(int j=0;j<state.size();j++){
            int a=state[i];
            int b=state[j];
            if(a&b)continue;
            head[i].push_back(j);
        }
    }
    dp[0][0]=1;
    for(int i=1;i<=m+1;i++){
        for(int a=0;a<state.size();a++){
            for(int b:head[a]){
                if(state[a]&g[i])continue;
                dp[i][a]+=dp[i-1][b];
            }
        }
    }
    cout<<dp[m+1][0]%mod;
}

292. 炮兵阵地

将军们规划如何部署炮兵部队,在防止误伤的前提下(保证任何两支炮兵部队之间不能互相攻击,即任何一支炮兵部队都不在其他支炮兵部队的攻击范围内),在整个地图区域内最多能够摆放多少我军的炮兵部队。

#include <cstring>
#include <iostream>
#include <algorithm>
#include <vector>

using namespace std;

const int N = 10, M = 1 << 10;

int n, m;
int g[1010];
int f[2][M][M];
vector<int> state;
int cnt[M];

bool check(int state)
{
    for (int i = 0; i < m; i ++ )
        if ((state >> i & 1) && ((state >> i + 1 & 1) || (state >> i + 2 & 1)))
            return false;
    return true;
}

int count(int state)
{
    int res = 0;
    for (int i = 0; i < m; i ++ )
        if (state >> i & 1)
            res ++ ;
    return res;
}

int main()
{
    cin >> n >> m;
    for (int i = 1; i <= n; i ++ )
        for (int j = 0; j < m; j ++ )
        {
            char c;
            cin >> c;
            g[i] += (c == 'H') << j;
        }

    for (int i = 0; i < 1 << m; i ++ )
        if (check(i))
        {
            state.push_back(i);
            cnt[i] = count(i);
        }

    for (int i = 1; i <= n; i ++ )
        for (int j = 0; j < state.size(); j ++ )
            for (int k = 0; k < state.size(); k ++ )
                for (int u = 0; u < state.size(); u ++ )
                {
                    int a = state[j], b = state[k], c = state[u];
                    if (a & b | a & c | b & c) continue;
                    if (g[i] & b | g[i - 1] & a) continue;
                    f[i & 1][j][k] = max(f[i & 1][j][k], f[i - 1 & 1][u][j] + cnt[b]);
                }

    int res = 0;
    for (int i = 0; i < state.size(); i ++ )
        for (int j = 0; j < state.size(); j ++ )
            res = max(res, f[n & 1][i][j]);

    cout << res << endl;

    return 0;
}

524. 愤怒的小鸟

Kiana 最近沉迷于一款神奇的游戏无法自拔。

简单来说,这款游戏是在一个平面上进行的。

有一架弹弓位于 (0,0) 处,每次 Kiana 可以用它向第一象限发射一只红色的小鸟, 小鸟们的飞行轨迹均为形如 y=ax2+bx 的曲线,其中 a,b 是 Kiana 指定的参数,且必须满足 a<0。

当小鸟落回地面(即 x 轴)时,它就会瞬间消失。

在游戏的某个关卡里,平面的第一象限中有 n 只绿色的小猪,其中第 i 只小猪所在的坐标为 (xi,yi)。

如果某只小鸟的飞行轨迹经过了 (xi, yi),那么第 i 只小猪就会被消灭掉,同时小鸟将会沿着原先的轨迹继续飞行;

如果一只小鸟的飞行轨迹没有经过 (xi, yi),那么这只小鸟飞行的全过程就不会对第 i 只小猪产生任何影响。

例如,若两只小猪分别位于 (1,3) 和 (3,3),Kiana 可以选择发射一只飞行轨迹为 y=−x2+4x 的小鸟,这样两只小猪就会被这只小鸟一起消灭。

而这个游戏的目的,就是通过发射小鸟消灭所有的小猪。

这款神奇游戏的每个关卡对 Kiana 来说都很难,所以 Kiana 还输入了一些神秘的指令,使得自己能更轻松地完成这个这个游戏。

这些指令将在输入格式中详述。

假设这款游戏一共有 T 个关卡,现在 Kiana 想知道,对于每一个关卡,至少需要发射多少只小鸟才能消灭所有的小猪。

由于她不会算,所以希望由你告诉她。

注意:本题除 NOIP 原数据外,还包含加强数据。

输入格式
第一行包含一个正整数 T,表示游戏的关卡总数。

下面依次输入这 T 个关卡的信息。

每个关卡第一行包含两个非负整数 n,m,分别表示该关卡中的小猪数量和 Kiana 输入的神秘指令类型。

接下来的 n 行中,第 i 行包含两个正实数 (xi,yi),表示第 i 只小猪坐标为 (xi,yi),数据保证同一个关卡中不存在两只坐标完全相同的小猪。


#include<bits/stdc++.h>
#define x first
#define y second
#define eps 1e-12
using namespace std;

typedef pair<double, double> PDD;
PDD p[18];
int line[18][18],f[1<<18],n,m,t;
int cmp(double x, double y)
{
    if (abs(x - y) < eps) return 0;
    if (x < y) return -1;
    return 1;
}
int main(){
    cin>>t;
    while(t--){
        memset(line ,0,sizeof line);
        //memset(p,0,sizeof p);
        cin>>n>>m;
        for(int i=0;i<n;i++){
            cin>>p[i].x>>p[i].y;
        }
        for(int i=0;i<n;i++){
            line[i][i]=1<<i;
            for(int j=0;j<n;j++){
                if(i==j)continue;
                double x1=p[i].x,y1=p[i].y;
                double x2=p[j].x,y2=p[j].y;
                double a=(y1/x1-y2/x2)/(x1-x2);
                //if(a-eps>=0)continue;
                if(cmp(a,0)>=0)continue;
                double b=y1/x1-a*x1;
                int state=0;
                for(int k=0;k<n;k++){
                    double xx=p[k].x;
                    double y=p[k].y;
                    double yy=a*xx*xx+b*xx;
                    //if(yy-eps  < y <  yy+eps)state|=1<<k;
                    if(!cmp(y,yy))state|=1<<k;
                }
                line[i][j]=state;
                line[j][i]=state;
            }
        }
        memset(f,0x3f,sizeof f);
        //预处理完毕,现在用状态dp来优化
        f[0]=0;
        for(int i=0;i<1<<n;i++){
            int x=0;
            for(int j=0;j<n;j++){
                if(! (i>>j&1) ){
                    x=j;
                    break;
                }
            }
            for(int j=0;j<n;j++){
                f[i|line[x][j] ]=min(f[i|line[x][j]],f[i]+1);
            }
        }
        cout<<f[(1<<n)-1]<<endl;
        
    }
}

3300. 食材运输

在 T 市有很多个酒店,这些酒店对于不同种类的食材有不同的需求情况,莱莱公司负责每天给这些酒店运输食材。
由于酒店众多,如何规划运输路线成为了一个非常重要的问题。你作为莱莱公司的顾问,请帮他们解决这个棘手的问题。
T 市有 N 个酒店,这些酒店由 N−1 条双向道路连接,所有酒店和道路构成一颗树。
不同的道路可能有不同的长度,运输车通过该道路所需要的时间受道路的长度影响。
在 T 市,一共有 K 种主流食材。
莱莱公司有 K 辆车,每辆车负责一种食材的配送,不存在多辆车配送相同的食材。
由于不同酒店的特点不同,因此不同酒店对食材的需求情况也不同,比如可能 1 号酒店只需要第 1,5 种食材, 2 号酒店需要全部的 K 种食材。
莱莱公司每天给这些公司运输食材。
对于运输第 i 种食材的车辆,这辆车可以从任意酒店出发,然后将食材运输到所有需要第 i 种食材的酒店。
假设运输过程中食材的装卸不花时间,运输车足够大使得其能够在出发时就装满全部所需食材,并且食材的重量不影响运输车的速度。
为了提高配送效率,这 K 辆车可以从不同的酒店出发。
但是由于 T 市对于食品安全特别重视,因此每辆车在配送之前需要进行食品安全检查。
鉴于进行食品安全检查的人手不足,最多可以设置 M 个检查点。
现在莱莱公司需要你制定一个运输方案:选定不超过 M 个酒店设立食品安全检查点,确定每辆运输车从哪个检查点出发,规划每辆运输车的路线。
假设所有的食材运输车在进行了食品安全检查之后同时出发,请制定一个运输方案,使得所有酒店的等待时间的最大值最小。
酒店的等待时间从运输车辆出发时开始计算,到该酒店所有需要的食材都运输完毕截至。
如果一个酒店不需要任何食材,那么它的等待时间为 0。
输入格式
第一行包含 3 个正整数 N,M,K,含义见题目描述。
接下来 N 行,每行包含 K 个整数。每行输入描述对应酒店对每种食材的需求情况,1 表示需要对应的食材, 0 表示不需要。
接下来 N−1 行,每行包含 3 个整数 u,v,w,表示存在一条通行时间为 w 的双向道路连接 u 号酒店和 v 号酒店。
保证输入数据是一颗树,酒店从 1 编号到 N。

#include <iostream>
#include <cstring>
#include <algorithm>

#define x first
#define y second

using namespace std;

typedef pair<int, int> PII;
const int N = 110, M = 10, S = 1 << M;

int n, m, k;
int need[N][M];
int h[N], e[N * 2], w[N * 2], ne[N * 2], idx;
int d[N][M];
int f[S], state[N];

void add(int a, int b, int c)
{
    e[idx] = b, w[idx] = c, ne[idx] = h[a], h[a] = idx ++ ;
}

PII dfs(int u, int fa, int v)
{
    PII res(0, -1);
    if (need[u][v]) res.y = 0;
    for (int i = h[u]; ~i; i = ne[i])
    {
        int j = e[i];
        if (j == fa) continue;
        auto t = dfs(j, u, v);
        if (t.y != -1)
        {
            res.x += t.x + w[i] * 2;
            res.y = max(res.y, t.y + w[i]);
        }
    }
    return res;
}

bool check(int mid)
{
    memset(state, 0, sizeof state);
    for (int i = 1; i <= n; i ++ )
        for (int j = 0; j < k; j ++ )
            if (d[i][j] <= mid)
                state[i] |= 1 << j;
    memset(f, 0x3f, sizeof f);
    f[0] = 0;
    for (int i = 0; i < 1 << k; i ++ )
        for (int j = 1; j <= n; j ++ )
            f[i | state[j]] = min(f[i | state[j]], f[i] + 1);
    return f[(1 << k) - 1] <= m;
}

int main()
{
    cin >> n >> m >> k;
    for (int i = 1; i <= n; i ++ )
        for (int j = 0; j < k; j ++ )
            cin >> need[i][j];
    memset(h, -1, sizeof h);
    for (int i = 0; i < n - 1; i ++ )
    {
        int a, b, c;
        scanf("%d%d%d", &a, &b, &c);
        add(a, b, c), add(b, a, c);
    }

    for (int i = 1; i <= n; i ++ )
        for (int j = 0; j < k; j ++ )
        {
            auto t = dfs(i, -1, j);
            if (t.y != -1) d[i][j] = t.x - t.y;
        }

    int l = 0, r = 2e8;
    while (l < r)
    {
        int mid = l + r >> 1;
        if (check(mid)) r = mid;
        else l = mid + 1;
    }

    printf("%d\n", r);
    return 0;
}

作者:yxc
链接:https://www.acwing.com/activity/content/code/content/979737/
来源:AcWing
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。

529. 宝藏

参与考古挖掘的小明得到了一份藏宝图,藏宝图上标出了 n 个深埋在地下的宝藏屋,也给出了这 n 个宝藏屋之间可供开发的 m 条道路和它们的长度。 
小明决心亲自前往挖掘所有宝藏屋中的宝藏。
但是,每个宝藏屋距离地面都很远,也就是说,从地面打通一条到某个宝藏屋的道路是很困难的,而开发宝藏屋之间的道路则相对容易很多。
小明的决心感动了考古挖掘的赞助商,赞助商决定免费赞助他打通一条从地面到某个宝藏屋的通道,通往哪个宝藏屋则由小明来决定。 
在此基础上,小明还需要考虑如何开凿宝藏屋之间的道路。
已经开凿出的道路可以任意通行不消耗代价。
每开凿出一条新道路,小明就会与考古队一起挖掘出由该条道路所能到达的宝藏屋的宝藏。
另外,小明不想开发无用道路,即两个已经被挖掘过的宝藏屋之间的道路无需再开发。
新开发一条道路的代价是:  
这条道路的长度 × 从赞助商帮你打通的宝藏屋到这条道路起点的宝藏屋所经过的宝藏屋的数量(包括赞助商帮你打通的宝藏屋和这条道路起点的宝藏屋)。 
请你编写程序为小明选定由赞助商打通的宝藏屋和之后开凿的道路,使得工程总代价最小,并输出这个最小值。
输入格式
第一行两个用空格分离的正整数 n 和 m,代表宝藏屋的个数和道路数。
接下来 m 行,每行三个用空格分离的正整数,分别是由一条道路连接的两个宝藏屋的编号(编号为 1∼n),和这条道路的长度 v。

#include <cstdio>
#include <cstring>
#include <iostream>
#include <algorithm>

using namespace std;

const int N = 12, M = 1 << 12, INF = 0x3f3f3f3f;

int n, m;
int d[N][N];
int f[M][N], g[M];

int main()
{
    scanf("%d%d", &n, &m);

    memset(d, 0x3f, sizeof d);
    for (int i = 0; i < n; i ++ ) d[i][i] = 0;

    while (m -- )
    {
        int a, b, c;
        scanf("%d%d%d", &a, &b, &c);
        a --, b --;
        d[a][b] = d[b][a] = min(d[a][b], c);
    }

    for (int i = 1; i < 1 << n; i ++ )
        for (int j = 0; j < n; j ++ )
            if (i >> j & 1)
            {
                for (int k = 0; k < n; k ++ )
                    if (d[j][k] != INF)
                        g[i] |= 1 << k;
            }

    memset(f, 0x3f, sizeof f);
    for (int i = 0; i < n; i ++ ) f[1 << i][0] = 0;

    for (int i = 1; i < 1 << n; i ++ )
        for (int j = (i - 1); j; j = (j - 1) & i)
            if ((g[j] & i) == i)
            {
                int remain = i ^ j;
                int cost = 0;
                for (int k = 0; k < n; k ++ )
                    if (remain >> k & 1)
                    {
                        int t = INF;
                        for (int u = 0; u < n; u ++ )
                            if (j >> u & 1)
                                t = min(t, d[k][u]);
                        cost += t;
                    }

                for (int k = 1; k < n; k ++ ) f[i][k] = min(f[i][k], f[j][k - 1] + cost * k);
            }

    int res = INF;
    for (int i = 0; i < n; i ++ ) res = min(res, f[(1 << n) - 1][i]);

    printf("%d\n", res);
    return 0;
}

3735. 构造完全图

给定一个由 n 个点和 m 条边构成的无向连通图。
我们希望通过一系列操作将其变为一个完全图(即每对不同的顶点之间都恰有一条边相连)。
每次操作时,可以选择其中一个点,找到所有和它直接相连的点,使这些点两两之间连边(若两点之间已经存在边,则无需重复连接)。
请问,至少多少次操作以后,可以将整个图变为一个完全图?

#include<bits/stdc++.h>
#define x first
#define y second

using namespace std;
const int N = 22, M=1<<22;
typedef pair<int,int> PII;
int n,m;
int f[M];
int e[N];
PII g[M];
int main(){
    cin>>n>>m;
    if(2*m==n*(n-1)){
        puts("0");
        return 0;
    }
    memset(f,0x3f,sizeof f);
    for(int i=0;i<n;i++)e[i]=1<<i;
    for(int i=0;i<m;i++){
        int x,y;
        cin>>x>>y;
        x--,y--;
        e[x]|=1<<y;
        e[y]|=1<<x;
    }
    for(int i=0;i<n;i++)f[e[i]]=1,g[e[i]]={0,i};
    //每次操作时,可以选择其中一个点,找到所有和它直接相连的点,使这些点两两之间连边
    for(int i=0;i<1<<n;i++){
        //枚举每个状态连接的点集
        for(int j=0;j<n;j++){
            if(f[i]==0x3f3f3f3f)continue;
            if(i>>j&1){//团i连接着j点
                int k=i|e[j];//新的更大的团是由原来的团i并上小团j所得到的
                if(f[k]>f[i]+1){
                    f[k]=f[i]+1;//状态k是由状态i连上点j所得到的
                    g[k]={i,j};//状态k是由状态i连上点j所得到的
                }
            }
        }
    }
    int k=(1<<n)-1;
    cout<<f[k]<<endl;
    
    while(k){
        cout<<g[k].y+1<<" ";
        k=g[k].x;
    }
    //递归输出,状态k没连上j之前是状态x,那么状态x是哪个状态连上哪个点走过来的呢?
    //在这里我们递归处理
    
}

1068. 环形石子合并

将 n 堆石子绕圆形操场排放,现要将石子有序地合并成一堆。
规定每次只能选相邻的两堆合并成新的一堆,并将新的一堆的石子数记做该次合并的得分。
请编写一个程序,读入堆数 n 及每堆的石子数,并进行如下计算:
选择一种合并石子的方案,使得做 n−1 次合并得分总和最大。
选择一种合并石子的方案,使得做 n−1 次合并得分总和最小。
输入格式
第一行包含整数 n,表示共有 n 堆石子。
第二行包含 n 个整数,分别表示每堆石子的数量。

#include <iostream>
#include <cstring>
#include <algorithm>
using namespace std;
const int N = 440,INF=0x3f3f3f3f;
int f[N][N],g[N][N],n,w[N+N],maxv=-INF,minv=INF;
int sum[2*N];
int main(){
    cin>>n;
    for(int i=1;i<=n;i++){
        cin>>w[i];
        w[i+n]=w[i];
        
    }
    for(int i=1;i<=n*2;i++)sum[i]=sum[i-1]+w[i];
    memset(f,-0x3f,sizeof f);
    memset(g,0x3f,sizeof g);
    for(int len=1;len<=n;len++){
        for(int l=1;l+len-1<=2*n;l++){
            int r=l+len-1;
            if(len==1)f[l][r]=g[l][r]=0;
            else{
                for(int k=l;k<r;k++){
                    f[l][r]=max(f[l][r],f[l][k]+f[k+1][r]+sum[r]-sum[l-1]);
                    g[l][r]=min(g[l][r],g[l][k]+g[k+1][r]+sum[r]-sum[l-1]);
                }
            }
            
        }
    }
    for(int i=1;i<=n;i++){
        maxv=max(maxv,f[i][i+n-1]);
        minv=min(minv,g[i][i+n-1]);
    }
    cout<<minv<<endl<<maxv;
}

320. 能量项链

如果前一颗能量珠的头标记为 m,尾标记为 r,后一颗能量珠的头标记为 r,尾标记为 n,则聚合后释放的能量为 m×r×n(Mars 单位),新产生的珠子的头标记为 m,尾标记为 n。
需要时,Mars 人就用吸盘夹住相邻的两颗珠子,通过聚合得到能量,直到项链上只剩下一颗珠子为止。
显然,不同的聚合顺序得到的总能量是不同的,请你设计一个聚合顺序,使一串项链释放出的总能量最大。

#include <iostream>
#include <cstring>
#include <algorithm>
using namespace std;
const int N = 202;
int w[N],dp[N][N];
int n;
int main(){
    cin>>n;
    for(int i=1;i<=n;i++){
        cin>>w[i];
        w[i+n]=w[i];
    }
    //memset(dp,-0x3f,sizeof dp);
    //len表示节点数量
    for(int len=3;len<=n+1;len++){
        for(int L=1;L+len-1<=2*n;L++){
            int R=L+len-1;
            for(int k=L+1;k<R;k++){
                dp[L][R]=max(dp[L][R],dp[L][k]+dp[k][R]+w[L]*w[k]*w[R]);
            }
        }
    }
    int ans=0;
    for(int i=1;i<=n;i++)ans=max(ans,dp[i][i+n]);
    cout<<ans<<endl;
    
}

479. 加分二叉树

设一个 n 个节点的二叉树 tree 的中序遍历为(1,2,3,…,n),其中数字 1,2,3,…,n 为节点编号。

每个节点都有一个分数(均为正整数),记第 i 个节点的分数为 di,tree 及它的每个子树都有一个加分,任一棵子树 subtree(也包含 tree 本身)的加分计算方法如下:

subtree的左子树的加分 × subtree的右子树的加分 + subtree的根的分数

若某个子树为空,规定其加分为 1。

叶子的加分就是叶节点本身的分数,不考虑它的空子树。

试求一棵符合中序遍历为(1,2,3,…,n)且加分最高的二叉树 tree。

要求输出:

(1)tree的最高加分

(2)tree的前序遍历

#include <iostream>
#include <cstring>
#include <algorithm>
using namespace std;
int w[100];
int tree[100][100];
int root[100][100];
int n;
int ans;
int dfs(int l,int r){
    if(tree[l][r])return tree[l][r];
    if(l==r)return w[l];
    if(l>r)return 1;
    for(int i=l;i<=r;i++){
        int t=dfs(l,i-1)*dfs(i+1,r)+w[i];
        if(t>tree[l][r]){
            tree[l][r]=t;
            root[l][r]=i;
        }
    }
    return tree[l][r];
}
void dg(int l,int r){
    if(l>r)return;
    if(l==r){printf("%d ",root[l][r]);return;}
    
    printf("%d ",root[l][r]);
    dg(l,root[l][r]-1);
    dg(root[l][r]+1,r);
}

int main(){
    cin>>n;
    for(int i=1;i<=n;i++)cin>>w[i];
    for(int i=1;i<=n;i++)root[i][i]=i;
    cout<<dfs(1,n)<<endl;
    dg(1,n);
}

1069. 凸多边形的划分(区间dp+高精度

给定一个具有 N 个顶点的凸多边形,将顶点从 1 至 N 标号,每个顶点的权值都是一个正整数。

将这个凸多边形划分成 N−2 个互不相交的三角形,对于每个三角形,其三个顶点的权值相乘都可得到一个权值乘积,试求所有三角形的顶点权值乘积之和至少为多少。
输出仅一行,为所有三角形的顶点权值乘积之和的最小值。
这个答案要高精度

n = int(input())
a = list(map(int,input().split()))
w = [0]+a 
f = [[0 for _ in range(n+1)] for _ in range(n+1)]
for length in range(3,n+1):
    for l in range(1,n-length+2):
        r = l + length - 1
        f[l][r] = float('inf')
        for k in range(l+1,r):
            f[l][r] = min(f[l][r],f[l][k]+f[k][r]+w[l]*w[k]*w[r])

print(f[1][n])

作者:IdealDragon
链接:https://www.acwing.com/activity/content/code/content/134559/
来源:AcWing
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。
#include <cstring>
#include <iostream>
#include <algorithm>

using namespace std;

typedef long long LL;

const int N = 55, M = 35, INF = 1e9;

int n;
int w[N];
LL f[N][N][M];

void add(LL a[], LL b[])
{
    static LL c[M];
    memset(c, 0, sizeof c);
    for (int i = 0, t = 0; i < M; i ++ )
    {
        t += a[i] + b[i];
        c[i] = t % 10;
        t /= 10;
    }
    memcpy(a, c, sizeof c);
}

void mul(LL a[], LL b)
{
    static LL c[M];
    memset(c, 0, sizeof c);
    LL t = 0;
    for (int i = 0; i < M; i ++ )
    {
        t += a[i] * b;
        c[i] = t % 10;
        t /= 10;
    }
    memcpy(a, c, sizeof c);
}

int cmp(LL a[], LL b[])
{
    for (int i = M - 1; i >= 0; i -- )
        if (a[i] > b[i]) return 1;
        else if (a[i] < b[i]) return -1;
    return 0;
}

void print(LL a[])
{
    int k = M - 1;
    while (k && !a[k]) k -- ;
    while (k >= 0) cout << a[k -- ];
    cout << endl;
}

int main()
{
    cin >> n;
    for (int i = 1; i <= n; i ++ ) cin >> w[i];

    LL temp[M];
    for (int len = 3; len <= n; len ++ )
        for (int l = 1; l + len - 1 <= n; l ++ )
        {
            int r = l + len - 1;
            f[l][r][M - 1] = 1;
            for (int k = l + 1; k < r; k ++ )
            {
                memset(temp, 0, sizeof temp);
                temp[0] = w[l];
                mul(temp, w[k]);
                mul(temp, w[r]);
                add(temp, f[l][k]);
                add(temp, f[k][r]);
                if (cmp(f[l][r], temp) > 0)
                    memcpy(f[l][r], temp, sizeof temp);
            }
        }

    print(f[1][n]);

    return 0;
}

作者:yxc
链接:https://www.acwing.com/activity/content/code/content/124469/
来源:AcWing
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。

321. 棋盘分割

将一个 8×8 的棋盘进行如下分割:将原棋盘割下一块矩形棋盘并使剩下部分也是矩形,再将剩下的部分继续如此分割,这样割了 (n−1) 次后,连同最后剩下的矩形棋盘共有 n 块矩形棋盘。(每次切割都只能沿着棋盘格子的边进行)棋盘上每一格有一个分值,一块矩形棋盘的总分为其所含各格分值之和。
现在需要把棋盘按上述规则分割成 n 块矩形棋盘,并使各矩形棋盘总分的均方差最小。

#include <cstring>
#include <iostream>
#include <algorithm>
#include <cmath>

using namespace std;

const int N = 15, M = 9;
const double INF = 1e9;

int n, m = 8;
int s[M][M];
double f[M][M][M][M][N];
double X;

int get_sum(int x1, int y1, int x2, int y2)
{
    return s[x2][y2] - s[x2][y1 - 1] - s[x1 - 1][y2] + s[x1 - 1][y1 - 1];
}

double get(int x1, int y1, int x2, int y2)
{
    double sum = get_sum(x1, y1, x2, y2) - X;
    return (double)sum * sum / n;
}

double dp(int x1, int y1, int x2, int y2, int k)
{
    double &v = f[x1][y1][x2][y2][k];
    if (v >= 0) return v;
    if (k == 1) return v = get(x1, y1, x2, y2);

    v = INF;
    for (int i = x1; i < x2; i ++ )
    {
        v = min(v, get(x1, y1, i, y2) + dp(i + 1, y1, x2, y2, k - 1));
        v = min(v, get(i + 1, y1, x2, y2) + dp(x1, y1, i, y2, k - 1));
    }

    for (int i = y1; i < y2; i ++ )
    {
        v = min(v, get(x1, y1, x2, i) + dp(x1, i + 1, x2, y2, k - 1));
        v = min(v, get(x1, i + 1, x2, y2) + dp(x1, y1, x2, i, k - 1));
    }

    return v;
}

int main()
{
    cin >> n;
    for (int i = 1; i <= m; i ++ )
        for (int j = 1; j <= m; j ++ )
        {
            cin >> s[i][j];
            s[i][j] += s[i - 1][j] + s[i][j - 1] - s[i - 1][j - 1];
        }

    X = (double)s[m][m] / n;
    memset(f, -1, sizeof f);
    printf("%.3lf\n", sqrt(dp(1, 1, 8, 8, n)));

    return 0;
}

1072. 树的最长路径

给定一棵树,树中包含 n 个结点(编号1~n)和 n−1 条无向边,每条边都有一个权值。
现在请你找到树中的一条最长路径。
换句话说,要找到一条路径,使得使得路径两端的点的距离最远。
注意:路径中可以只包含一个点。

#include <cstring>
#include <iostream>
#include <algorithm>

using namespace std;

const int N = 10010, M = N * 2;

int n;
int h[N], e[M], w[M], ne[M], idx;
int ans;

void add(int a, int b, int c)
{
    e[idx] = b, w[idx] = c, ne[idx] = h[a], h[a] = idx ++ ;
}

int dfs(int u, int father)
{
    int dist = 0; // 表示从当前点往下走的最大长度
    int d1 = 0, d2 = 0;

    for (int i = h[u]; i != -1; i = ne[i])
    {
        int j = e[i];
        if (j == father) continue;
        int d = dfs(j, u) + w[i];
        dist = max(dist, d);

        if (d >= d1) d2 = d1, d1 = d;
        else if (d > d2) d2 = d;
    }

    ans = max(ans, d1 + d2);

    return dist;
}

int main()
{
    cin >> n;

    memset(h, -1, sizeof h);
    for (int i = 0; i < n - 1; i ++ )
    {
        int a, b, c;
        cin >> a >> b >> c;
        add(a, b, c), add(b, a, c);
    }

    dfs(1, -1);

    cout << ans << endl;

    return 0;
}

作者:yxc
链接:https://www.acwing.com/activity/content/code/content/125605/
来源:AcWing
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。

1073. 树的中心

给定一棵树,树中包含 n 个结点(编号1~n)和 n−1 条无向边,每条边都有一个权值。

请你在树中找到一个点,使得该点到树中其他结点的最远距离最近。

#include <cstring>
#include <iostream>
#include <algorithm>

using namespace std;

const int N = 10010, M = N * 2, INF = 0x3f3f3f3f;

int n;
int h[N], e[M], w[M], ne[M], idx;
int d1[N], d2[N], p1[N], up[N];
bool is_leaf[N];

void add(int a, int b, int c)
{
    e[idx] = b, w[idx] = c, ne[idx] = h[a], h[a] = idx ++ ;
}

int dfs_d(int u, int father)
{
    d1[u] = d2[u] = -INF;
    for (int i = h[u]; i != -1; i = ne[i])
    {
        int j = e[i];
        if (j == father) continue;
        int d = dfs_d(j, u) + w[i];
        if (d >= d1[u])
        {
            d2[u] = d1[u], d1[u] = d;
            p1[u] = j;
        }
        else if (d > d2[u]) d2[u] = d;
    }

    if (d1[u] == -INF)
    {
        d1[u] = d2[u] = 0;
        is_leaf[u] = true;
    }

    return d1[u];
}

void dfs_u(int u, int father)
{
    for (int i = h[u]; i != -1; i = ne[i])
    {
        int j = e[i];
        if (j == father) continue;

        if (p1[u] == j) up[j] = max(up[u], d2[u]) + w[i];
        else up[j] = max(up[u], d1[u]) + w[i];

        dfs_u(j, u);
    }
}

int main()
{
    cin >> n;
    memset(h, -1, sizeof h);
    for (int i = 0; i < n - 1; i ++ )
    {
        int a, b, c;
        cin >> a >> b >> c;
        add(a, b, c), add(b, a, c);
    }

    dfs_d(1, -1);
    dfs_u(1, -1);

    int res = d1[1];
    for (int i = 2; i <= n; i ++ )
        if (is_leaf[i]) res = min(res, up[i]);
        else res = min(res, max(d1[i], up[i]));

    printf("%d\n", res);

    return 0;
}

作者:yxc
链接:https://www.acwing.com/activity/content/code/content/125606/
来源:AcWing
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。

1075. 数字转换(约数和

如果一个数 x 的约数之和 y(不包括他本身)比他本身小,那么 x 可以变成 y,y 也可以变成 x。

例如,4 可以变为 3,1 可以变为 7。

限定所有数字变换在不超过 n 的正整数范围内进行,求不断进行数字变换且不出现重复数字的最多变换步数。

#include <iostream>
#include <cstring>
#include <algorithm>
using namespace std;
const int N = 50005;
int h[N],ne[N],e[N],n,st[N],sum[N],idx;
int ans;
void add(int a,int b){
    e[idx]=b,ne[idx]=h[a],h[a]=idx++;
}

int dfs(int u){
    int d1=0,d2=0;
    for(int i=h[u];~i;i=ne[i]){
        int j=e[i];
        int d=dfs(j)+1;
        if(d>=d1)d2=d1,d1=d;
        else if(d>d2)d2=d;
        
    }
    ans=max(ans,d1+d2);
    return d1;
}

int main(){
    cin>>n;
    memset(h, -1, sizeof h);
    //利用埃湿筛法的思想,不直接求某个数的约数和,而是求这个数能被哪些整除
    //i枚举的就是一个个约数,j是倍数,然后sum[i*j]+=i
    //为什么不是+=(i+j)呢,因为到时候枚举到i=j的时候又会再加一次了,会出错
    for(int i=1;i<n;i++)
        for(int j=2;j<=n/i;j++)
            sum[i*j]+=i;
    for(int i=1;i<=n;i++)
        if(i>sum[i])add(sum[i],i),st[i]=1;
    
    // for(int i=1;i<=n;i++){
    //     if(!st[i])dfs(i);
    // }
    dfs(1);
    cout<<ans;
        
    
}

1074. 二叉苹果树

有一棵二叉苹果树,如果树枝有分叉,一定是分两叉,即没有只有一个儿子的节点。
这棵树共 N 个节点,编号为 1 至 N,树根编号一定为 1。
我们用一根树枝两端连接的节点编号描述一根树枝的位置。
一棵苹果树的树枝太多了,需要剪枝。但是一些树枝上长有苹果,给定需要保留的树枝数量,求最多能留住多少苹果。
这里的保留是指最终与1号点连通。

#include <iostream>
#include <cstring>
#include <algorithm>
using namespace std;
const int N = 110;
int h[N],e[N*2],w[N*2],ne[N*2],idx,a,b,c,n,k,m,dp[N][N];

void add(int a,int b,int c){
    e[idx]=b,w[idx]=c,ne[idx]=h[a],h[a]=idx++;
}

void dfs(int u,int father){
    for(int i=h[u];~i;i=ne[i]){
        int  j=e[i];
        if(j==father)continue;
        dfs(j,u);
        for(int money=m;money;money--){
            for(int k=0;k<money;k++){
                dp[u][money]=max(dp[u][money] , dp[u][money-k-1]+dp[j][k]+w[i]);
            }
        }
        
    }
}


int main(){
    cin>>n>>m;
    memset(h,-1,sizeof h);
    for(int i=0;i<n-1;i++){
        cin>>a>>b>>c;
        add(a,b,c);
        add(b,a,c);
    }
    dfs(1,-1);
    printf("%d\n",dp[1][m]);
}

323. 战略游戏

#include <iostream>
#include <cstring>
#include <algorithm>
using namespace std;
const int N = 1510;
int h[N],e[N*2],w[N*2],ne[N*2],idx,n,st[N],dp[N][2];
void add(int a,int b,int c){
    e[idx]=b,w[idx]=c,ne[idx]=h[a],h[a]=idx++;
}

void dfs(int u){
    dp[u][0]=0;
    dp[u][1]=1;
    for(int i=h[u];~i;i=ne[i]){
        int j=e[i];
        dfs(j);
        dp[u][0]+=dp[j][1];
        dp[u][1]+=min(dp[j][1],dp[j][0]);
    }
}

int main(){
    while(scanf("%d",&n)==1){
        idx=0;
        memset(h,-1,sizeof h);
        memset(st,0,sizeof st);
        int id,cnt;
        for(int i=1;i<=n;i++){
            scanf("%d:(%d)",&id,&cnt);
            while(cnt--){
                int x;
                cin>>x;
                
                add(id,x,1);
                st[x]=1;
            }
        }
        int root=0;
        while(st[root])root++;
        dfs(root);
        cout<<min(dp[root][0],dp[root][1])<<endl;
        
    }
}

1077. 皇宫看守

太平王世子事件后,陆小凤成了皇上特聘的御前一品侍卫。

皇宫各个宫殿的分布,呈一棵树的形状,宫殿可视为树中结点,两个宫殿之间如果存在道路直接相连,则该道路视为树中的一条边。

已知,在一个宫殿镇守的守卫不仅能够观察到本宫殿的状况,还能观察到与该宫殿直接存在道路相连的其他宫殿的状况。

大内保卫森严,三步一岗,五步一哨,每个宫殿都要有人全天候看守,在不同的宫殿安排看守所需的费用不同。

可是陆小凤手上的经费不足,无论如何也没法在每个宫殿都安置留守侍卫。

帮助陆小凤布置侍卫,在看守全部宫殿的前提下,使得花费的经费最少。

#include <iostream>
#include <cstring>
#include <algorithm>
using namespace std;
const int N = 1501;
int h[N],w[N],ne[N],e[N],idx,n,st[N];
int dp[N][3];
void add(int a,int b){
    e[idx]=b,ne[idx]=h[a],h[a]=idx++;
}

void dfs(int u){
    dp[u][2]=w[u];
    int sum=0;
    for(int i=h[u];~i;i=ne[i]){
        int j=e[i];
        dfs(j);
        dp[u][0]+=min(dp[j][1],dp[j][2]);
        dp[u][2]+=min(min(dp[j][0],dp[j][1]),dp[j][2]);
        sum+=min(dp[j][1],dp[j][2]);
    }
    dp[u][1]=1e9;
    for(int i=h[u];~i;i=ne[i]){
        int j=e[i];
        dp[u][1]=min(dp[u][1], sum-min(dp[j][1],dp[j][2]) + dp[j][2]);
    }
}

int main(){
    cin>>n;
    int id,x,cnt;
    memset(h, -1, sizeof h);
    for(int i=1;i<=n;i++){
        scanf("%d%d%d",&id,&x,&cnt);
        w[id]=x;
        while(cnt--){
            int a;
            cin>>a;
            add(id,a);
            st[a]=1;
        }
    }
    int root=1;
    while(st[root])root++;
    dfs(root);
    cout<<min( dp[root][1] ,dp[root][2])<<endl;
}

1081. 度的数量

求给定区间 [X,Y] 中满足下列条件的整数个数:这个数恰好等于 K 个互不相等的 B 的整数次幂之和。
例如,设 X=15,Y=20,K=2,B=2,则有且仅有下列三个数满足题意:

17=2 ^4+2 ^0
18=2 ^4+2 ^1
20=2 ^4+2 ^2
输入格式
第一行包含两个整数 X 和 Y,接下来两行包含整数 K 和 B。

输出格式
只包含一个整数,表示满足条件的数的个数。
这是一道数位dp问题,对于数位dp问题关键就在于分类讨论。

首先我们把数字 n 对于B进制来进行分解 ,将每一位上的数字存入一个数组中,然后从高位往低位去讨论,
首先 对于第 i 位数字 x 有三种情况

x = 0 :则 i 位上只能取 0 ,所以直接讨论 i-1 位就可以了
x = 1 :则 i 位上取 0 的时候,后面i-1位都可以随意取值 ,取 1 的时候,后面i- 1位要再小于题目的数的前提下取值,并且能取 k-last-1 个 1 ,
x > 1 :则 i 位上 可以取 1 ,0 ,并且后面 i-1 位可以随便取。
这里 i-1位随便取 和 对于i-1 位讨论的区别,就体现在前一个直接用组合数 f[i-1][k-last]就可以,后面则需要再进入循环去讨论。

最后对于最后一位进行单独讨论,如果对于 最后一位时 ,所有的k个1都已经取好了,也就是k==last了,才做res++。

#include <cstring>
#include <iostream>
#include <algorithm>
#include <vector>

using namespace std;

const int N = 35;

int K, B;
int f[N][N];

void init(){//求组合数
    for(int i=0;i<=N;i++){
        for(int j=0;j<=i;j++){
            if(!j)f[i][j]=1;
            else f[i][j]=f[i-1][j-1]+f[i-1][j];
        }
    }
}


int dp(int n){
    if (!n) return 0;//如果n==0,那么就直接放回0
    vector<int> v;
    while (n) v.push_back(n % B), n /= B;
    int res=0;
    int last=0;//表示已经取了多少个1

    for(int i=v.size()-1;i>=0;i--){//从最高位对每一位数讨论
        int x=v[i];
        if(x){
            res += f[i][K - last];//加上第i位取0的时候的组合数,也就是对于后面i位取k-last个1的数量
            if (x > 1)//如果x>1,就可以直接用组合数表示出来,不用进行讨论,也就是i位取1的时候,后面i位随便取k-last-1个1
            {
                if (K - last - 1 >= 0) res += f[i][K - last - 1];
                break;
            }
            else//如果x==1,那么i位取1的时候,还要进行讨论,后面i位不能随便取,也就不是组合数
            {
                last ++ ;
                if (last > K) break;
            }
        }
       if (!i && last == K) res ++ ;// 对于最后一位来特殊来考虑
    }
    return res;
}

int main()
{
    init();

    int l, r;
    cin >> l >> r >> K >> B;

    cout << dp(r) - dp(l - 1) << endl;

    return 0;
}

1082. 数字游戏

某人命名了一种不降数,这种数字必须满足从左到右各位数字呈非下降关系,如 123,446。

现在大家决定玩一个游戏,指定一个整数闭区间 [a,b],问这个区间内有多少个不降数。

#include<iostream>
#include<algorithm>
#include<cstring>
#include<vector>
using namespace std;
const int maxn=15;

int f[maxn][maxn];
///f[i][j]表示最高位是j并且一共有i位的不降数的集合
void init()
{
    for(int i=0;i<=9;i++) f[1][i]=1;

    for(int i=2;i<maxn;i++)
        for(int j=0;j<=9;j++)//j要从0开始
            for(int k=j;k<=9;k++)
                f[i][j]+=f[i-1][k];
}

int dp(int n)
{
    if(!n) return 1;

    vector<int>nums;
    while(n) nums.push_back(n%10),n/=10;
    int res=0;//方案数
    int last=0;//保留一些前缀信息,本题是上一个数是几
    ///从最大数开始枚举
    for(int i=nums.size()-1;i>=0;i--)
    {
        int x=nums[i];//x为当前这位数
        for(int j=last;j<x;j++)  要保障比下一位>=上一位,所以从last开始枚举,最多枚举到x,last为上一位,也即最高位,对下一位的枚举是有限制的
            res+=f[i+1][j];    ///左端的节点有i+1个位数(因为第一位的下标是0)

        if(x<last) break;//如果当前这位数比上一位小,那么后面的都不成立了,直接break退出

        last=x;

        if(!i) res++; //如果能顺利到最后一个数说明树的最右边这一段的每一个数都是小于等于前一位数的,因而++
    }

    return res;
}

int main(void)
{
    init();
    int l,r;
    while(cin>>l>>r)
    {
         cout<<dp(r)-dp(l-1)<<endl;
    }
return 0;    
}

1083. Windy数

Windy 定义了一种 Windy 数:不含前导零且相邻两个数字之差至少为 2 的正整数被称为 Windy 数。

Windy 想知道,在 A 和 B 之间,包括 A 和 B,总共有多少个 Windy 数?
Analysis
可以看到A与B的范围很大,但是想一想他们的数位并不会超过9,这不就正好符合数位DP的套路吗?

首先明确这个状态怎么设计:
每一位的取值都与前一位有关,也解释说当我们枚到第pos层时,这一层一第pos+1层有关,那么我们
就设f[pos][las]为在第pos层以las开头的符合要求的数的数量

诠释过程:
枚举总会有一个限制,这便是边界,我们需要对边界的每一位进行分解,同时用一个变量标记是否有
最高位限制
int ed=(limit) ? k[dep]:9;

其实很好理解。
打个比方,你能吃5432个鸡腿,已经吃了5000个了,还能吃多少个呢?432。即,在百位上最多是4。

然后根据这个范围,再加上题目要求的判断

ret+=dfs(lead&&(!i),limit&&(i==ed),dep-1,i);

//#pragma GCC optimize(3,"inline","Ofast","fast-math","no-stack-protector","unroll-loops")
//#pragma GCC target("sse","sse2","sse3","sse4","avx","avx2","popcnt")
//手动优化
#include<iostream>
#include<cstdio>
#include<algorithm>
#include<cstring>
#include<cmath>
#include<vector>

#define RG register
#define ll long long
#define INF 0x3f3f3f3f

using namespace std;

const int N=35,M=11;

int f[N][M],k[N];//每一位的限制
int a,b;//范围

inline int dfs(bool lead,bool limit,int dep,int las)
{
    if(!dep) return 1;
    if(!lead&&!limit&&(~f[dep][las])) return f[dep][las];

    int ret=0;
    int ed=(limit) ? k[dep]:9;
//  printf("1___ ed= %d\n",ed);
    for (int i=0;i<=ed;i++)
        if(lead||abs(i-las)>=2)
        //只要当前选的数和上一次相差2就行了
        {
            ret+=dfs(lead&&(!i),limit&&(i==ed),dep-1,i);
//          printf("2___ ret= %d\n",ret);
        }


    if(!lead&&!limit) f[dep][las]=ret;
    return ret;
}

inline int dp(int n)
{
    int cnt=0;
    if(!n) return 1;
    while(n) k[++cnt]=n%10,n/=10;//对每一位进行分解

    int res=0;
    for (int i=0;i<=k[cnt];i++)
        res+=dfs((!i),i==k[cnt],cnt-1,i);

    return res;
}

int main()
{
    memset(f,-1,sizeof(f));//初始
    scanf("%d%d",&a,&b);

    printf("%d\n",dp(b)-dp(a-1));
    //差分思想
    return 0;
}

作者:Dear_You
链接:https://www.acwing.com/solution/content/5813/
来源:AcWing
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。

1084. 数字游戏 II

某人又命名了一种取模数,这种数字必须满足各位数字之和 mod N 为 0。

现在大家又要玩游戏了,指定一个整数闭区间 [a.b],问这个区间内有多少个取模数。
对于这一题来说,假设我们当前枚举到的第i位,且第i位上的数字是x,那么对于答案中的第i位数字j来说,可以填两类数:

1.0~x-1
我们用last表示到当前为止,前面数位上的数字之和,对此,当前第i位数字为j,前面数字之和为last,那么
后i位(包括j这一位)数字之和sum与last的关系就是(last+sum)%N == 0,那么sum%N的结果等价于(-last)%N,
所以res += f[i+1][j][(-last%N)]; (后面会提到f数组的处理)
2.x
如果j填x,那么不用枚举了,last += x,再枚举下一位即可
f数组的处理
f[i][j][k] 表示一共有i位,且最高位数字是j,且所有位数字和模N结果为k的数的个数

状态转移: 因为第i位已经是j,且所有数字之和模N为k,所以我们考虑第i-1位,假设第i-1位数字是x,由于j已经知道,
那么剩下的i-1位数字之和模N的结果就是(k-j)%N,那么状态转移方程就是:

f[i][j][k]=∑N−1k=0∑9x=0f[i−1][x][mod(k−j,N)]

#include <iostream>
#include <cstdio>
#include <cstring>
#include <algorithm>
#include <vector>

using namespace std;

const int N = 12, M = 102;

int f[N][10][M];  //f[i][j][k] 表示一共有i位,且最高位数字是j,且所有位数字和模p位k的数字个数
int p;

int mod(int u,int v){
    return (u%v+v)%v;  //c++的%会得到负数,需要做一个偏移
}

void init(){
    memset(f,0,sizeof f);   //多组测试数据,f数组要清空
    for(int i = 0; i<=9; i++) f[1][i][i%p]++;

    for(int i = 2; i<N; i++)
        for(int j = 0; j<=9; j++)
            for(int k = 0; k<p; k++)
                for(int x = 0; x<=9; x++)
                    f[i][j][k] += f[i-1][x][mod(k-j,p)];
}

int dp(int n){
    if(!n) return 1;
    int res = 0,last = 0;

    vector<int> a;
    while(n) a.push_back(n%10),n/=10;

    int len = a.size()-1;
    for(int i = len; i>=0; --i){
        int x =a[i];    
        for(int j = 0; j<x; j++)  //第i位放0~x-1
            res += f[i+1][j][mod(-last,p)]; //0~i位,所以一共有i+1位

        last += x;
        if(!i && last % p == 0) res ++; 
    }
    return res;
}
int main()
{

    int l,r;
    while(cin>>l>>r>>p){
        init();
        cout<<dp(r)-dp(l-1)<<endl;
    }

    return 0;
}

1085. 不要62

杭州人称那些傻乎乎粘嗒嗒的人为 62(音:laoer)。

杭州交通管理局经常会扩充一些的士车牌照,新近出来一个好消息,以后上牌照,不再含有不吉利的数字了,这样一来,就可以消除个别的士司机和乘客的心理障碍,更安全地服务大众。

不吉利的数字为所有含有 4 或 62 的号码。例如:62315,73418,88914 都属于不吉利号码。但是,61152 虽然含有 6 和 2,但不是 连号,所以不属于不吉利数字之列。

你的任务是,对于每次给出的一个牌照号区间 [n,m],推断出交管局今后又要实际上给多少辆新的士车上牌照了。

#include <cstring>
#include <iostream>
#include <algorithm>
#include <vector>

using namespace std;

const int N = 35;

int f[N][10];

void init()
{
    for (int i = 0; i <= 9; i ++ )
        if (i != 4)
            f[1][i] = 1;

    for (int i = 1; i < N; i ++ )
        for (int j = 0; j <= 9; j ++ )
        {
            if (j == 4) continue;
            for (int k = 0; k <= 9; k ++ )
            {
                if (k == 4 || j == 6 && k == 2) continue;
                f[i][j] += f[i - 1][k];
            }
        }
}

int dp(int n)
{
    if (!n) return 1;

    vector<int> nums;
    while (n) nums.push_back(n % 10), n /= 10;

    int res = 0;
    int last = 0;
    for (int i = nums.size() - 1; i >= 0; i -- )
    {
        int x = nums[i];
        for (int j = 0; j < x; j ++ )
        {
            if (j == 4 || last == 6 && j == 2) continue;
            res += f[i + 1][j];
        }

        if (x == 4 || last == 6 && x == 2) break;
        last = x;

        if (!i) res ++ ;
    }

    return res;
}

int main()
{
    init();

    int l, r;
    while (cin >> l >> r, l || r)
    {
        cout << dp(r) - dp(l - 1) << endl;
    }

    return 0;
}

1086. 恨7不成妻

单身!

依然单身!

吉哥依然单身!

DS 级码农吉哥依然单身!

所以,他平生最恨情人节,不管是 214 还是 77,他都讨厌!

吉哥观察了 214 和 77 这两个数,发现:

2+1+4=7
7+7=7×2
77=7×11
最终,他发现原来这一切归根到底都是因为和 7 有关!

所以,他现在甚至讨厌一切和 7 有关的数!

什么样的数和 7 有关呢?

如果一个整数符合下面三个条件之一,那么我们就说这个整数和 7 有关:

整数中某一位是 7;
整数的每一位加起来的和是 7 的整数倍;
这个整数是 7 的整数倍。
现在问题来了:吉哥想知道在一定区间内和 7 无关的整数的平方和。

#include <cstring>
#include <iostream>
#include <algorithm>
#include <vector>

using namespace std;

typedef long long LL;

const int N = 20, P = 1e9 + 7;

struct F
{
    int s0, s1, s2;
}f[N][10][7][7];

int power7[N], power9[N];

int mod(LL x, int y)
{
    return (x % y + y) % y;
}

void init()
{
    for (int i = 0; i <= 9; i ++ )
    {
        if (i == 7) continue;
        auto& v = f[1][i][i % 7][i % 7];
        v.s0 ++, v.s1 += i, v.s2 += i * i;
    }

    LL power = 10;
    for (int i = 2; i < N; i ++, power *= 10)
        for (int j = 0; j <= 9; j ++ )
        {
            if (j == 7) continue;
            for (int a = 0; a < 7; a ++ )
                for (int b = 0; b < 7; b ++ )
                    for (int k = 0; k <= 9; k ++ )
                    {
                        if (k == 7) continue;
                        auto &v1 = f[i][j][a][b], &v2 = f[i - 1][k][mod(a - j * power, 7)][mod(b - j, 7)];
                        v1.s0 = mod(v1.s0 + v2.s0, P);
                        v1.s1 = mod(v1.s1 + v2.s1 + j * (power % P) % P * v2.s0, P);
                        v1.s2 = mod(v1.s2 + j * j * (power % P) % P * (power % P) % P * v2.s0 + v2.s2 + 2 * j * power % P * v2.s1, P);
                    }
        }

    power7[0] = 1;
    for (int i = 1; i < N; i ++ ) power7[i] = power7[i - 1] * 10 % 7;

    power9[0] = 1;
    for (int i = 1; i < N; i ++ ) power9[i] = power9[i - 1] * 10ll % P;
}

F get(int i, int j, int a, int b)
{
    int s0 = 0, s1 = 0, s2 = 0;
    for (int x = 0; x < 7; x ++ )
        for (int y = 0; y < 7; y ++ )
            if (x != a && y != b)
            {
                auto v = f[i][j][x][y];
                s0 = (s0 + v.s0) % P;
                s1 = (s1 + v.s1) % P;
                s2 = (s2 + v.s2) % P;
            }
    return {s0, s1, s2};
}

int dp(LL n)
{
    if (!n) return 0;

    LL backup_n = n % P;
    vector<int> nums;
    while (n) nums.push_back(n % 10), n /= 10;

    int res = 0;
    LL last_a = 0, last_b = 0;
    for (int i = nums.size() - 1; i >= 0; i -- )
    {
        int x = nums[i];
        for (int j = 0; j < x; j ++ )
        {
            if (j == 7) continue;
            int a = mod(-last_a * power7[i + 1], 7);
            int b = mod(-last_b, 7);
            auto v = get(i + 1, j, a, b);
            res = mod(
                res + 
                (last_a % P) * (last_a % P) % P * power9[i + 1] % P * power9[i + 1] % P * v.s0 % P + 
                v.s2 + 
                2 * last_a % P * power9[i + 1] % P * v.s1,
            P);
        }

        if (x == 7) break;
        last_a = last_a * 10 + x;
        last_b += x;

        if (!i && last_a % 7 && last_b % 7) res = (res + backup_n * backup_n) % P;
    }

    return res;
}

int main()
{
    int T;
    cin >> T;

    init();

    while (T -- )
    {
        LL l, r;
        cin >> l >> r;
        cout << mod(dp(r) - dp(l - 1), P) << endl;
    }

    return 0;
}

作者:yxc
链接:https://www.acwing.com/activity/content/code/content/127376/
来源:AcWing
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。
#include<iostream>
#include<cstring>
using namespace std;

typedef long long ll;
const ll P=1e9+7;
int A[25];
ll pw[25];

struct node {ll cnt,sum,sum2;} f[20][7][7]; 

/*
f[I][sum][num]表示的状态为:
从第I位开始,最高位到第I位每位数字之和(%7)为sum,整个数字(%7)为num
如对于数123***,I=3时,sum=6,num=123

注:f存储的是在没有贴合上界的情况下
因为没有贴合上界,即剩下i位可以从00…00~99…99随便填,所以无论数a[]是多少都可以适用,不需要每次都重置f数组

在该状态下,结构体中的
cnt表示与7无关的数的个数
sum表示所有与7无关的数的和
sum2表示所有与7无关的数的平方和
*/

node dfs(int I,int sum,int num,bool lim){                       //当前在第I位,最高位到第I位每位数字之和(%7)为sum,整个数字(%7)为num,lim表示是否贴合上界
    if (!I)  return (node){sum && num , 0 , 0};                 //数字已填完,根据题目要求,若sum和num都不为0(不能被7整除),则算一种方案
    if (!lim && f[I][sum][num].cnt>=0) return f[I][sum][num];  //记忆化,如果不贴合上界(!lim),直接放回记录过的答案

    int up=lim ? A[I] : 9;                                     //第I位最大能填的数
    node ans=(node){0,0,0};
    for (int i=0 ; i<=up ; i++)                                //枚举第I位填的数
    if (i!=7){
        node J=dfs(I-1,(sum+i)%7,(num*10+i)%7,lim && i==up);
        ll B=i*pw[I-1];                                        //B可以理解为当前层的基值,例如第I=5位填6,则B=60000
        (ans.cnt+=J.cnt)%=P;                                   //统计与7无关数出现次数
        (ans.sum+=J.cnt*B+J.sum)%=P;                          

        /*
        统计所有与7无关数的和(用dfs(I-1)已经求出了所有无关数第I-1位到最后一位所组成的数之和,即J.sum,再加上第I位即可,即J.cnt*B)
        例如I=5,已知无关数有**61111,**62222,**63333(随便瞎写的几个数字)
        则B=60000,J.sum=1111+2222+3333,J.cnt=3,ans.sum=61111+62222+63333
        */

        (ans.sum2+=J.cnt*B%P*B%P+J.sum2+2*J.sum%P*B%P)%=P;

        /*
        统计所有与7无关数第I位到最后一位所组成的数的平方和
        例如I=5,已知无关数有**61111,**62222,**63333(随便瞎写的几个数字)
        对于61111^2=(60000+1111)^2=(60000)^2+(1111)^2+2*60000*1111
        62222,63333同理
        则ans.sum2=61111^2+62222^2+63333^2
                  =3*(60000)^2 + (1111^2+2222^2+3333^2) + 2*60000*(1111+2222+3333)
                  =J.cnt*B*B   + J.sum2                 + 2*B*J.sum
        可以发现,我们用后I-1位的sum2即可推算出后I位的sum2
        */
    }
    if (!lim) f[I][sum][num]=ans;                             //记忆化:如果不贴合上界(!lim),则记录
    return ans;
}


ll solve (ll X){        //分解数位
    int len=0;
    for ( ; X ; X/=10) A[++len]=X%10;
    return dfs(len,0,0,1).sum2;
}

int main(){
    int T;
    cin>>T,pw[0]=1,memset(f,-1,sizeof f);                       
    for(int i=1 ; i<21 ; i++) pw[i]=pw[i-1]*10%P;               //预处理10的幂

    for (ll L,R ; T ; T--)
     scanf("%lld%lld",&L,&R),printf("%lld\n",(solve(R)-solve(L-1)+P)%P);
}


作者:Randolph
链接:https://www.acwing.com/solution/content/57811/
来源:AcWing
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。

单调队列优化dp

135. 最大子序和

输入一个长度为 n 的整数序列,从中找出一段长度不超过 m 的连续子序列,使得子序列中所有数的和最大。

注意: 子序列的长度至少是 1。
我们着重理解一下如何单调队列。

我们发现假如说我们的右端点rr,固定好了,那么我们的问题就变化了。

找到一个左端点ll,要求j∈[i−m,i−1]j∈[i−m,i−1].

这句话的意思就是说,我们这个区间长度不可以超过mm,而且区间长度至少为11. 这不是废话嘛

而且呢sum[j]sum[j]要尽量的小,为了使得区间和最大,对吧。

所以说咱们发现,这个jj的取值,是一个区间,而且最主要的是,我们只要这个区间的最值。

区间最值,就可以通过单调队列处理。

我们发现如果说有一个位置kk,这个k<j<ik<j<i.而且sum[k]≥sum[j]sum[k]≥sum[j]
用人话说就是,一个人比你先学习,也就是比你老,而且实力还不如你,那么请问这个人打得赢你吗?

对不起,实力不允许,蒟蒻我打赢大佬你。

我们来理性分析一遍。

k<jk<j,也就是说[k,i][k,i]这个区间的长度,一定是大于[j,i][j,i]这个区间长度的。

sum[k]≥sum[j]sum[k]≥sum[j],这么说来的话,那么sum[i]−sum[k]≥sum[i]−sum[j]sum[i]−sum[k]≥sum[i]−sum[j],也就是说我们的[k,j]区间消耗更大。

既然如此,那么kk是真的很菜,是真的没有用了。

我们的jj,不仅比kk更加接近ii,最主要的是,消耗比kk小。

那么我们还有什么理由选择kk呢。

一个机器,我们当然是选择,又新,消耗又少的,肯定不会选择又老,还消耗大的。

作者:秦淮岸灯火阑珊
链接:https://www.acwing.com/solution/content/888/
来源:AcWing
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。

#include <cstring>
#include <iostream>
#include <algorithm>

using namespace std;

const int N = 300010, INF = 1e9;

int n, m;
int s[N];
int q[N];

int main()
{
    scanf("%d%d", &n, &m);
    for (int i = 1; i <= n; i ++ ) scanf("%d", &s[i]), s[i] += s[i - 1];

    int res = -INF;
    int hh = 0, tt = 0;

    for (int i = 1; i <= n; i ++ )
    {
        if (q[hh] < i - m) hh ++ ;
        res = max(res, s[i] - s[q[hh]]);
        while (hh <= tt && s[q[tt]] >= s[i]) tt -- ;
        q[ ++ tt] = i;
    }

    printf("%d\n", res);

    return 0;
}

作者:yxc
链接:https://www.acwing.com/activity/content/code/content/128103/
来源:AcWing
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。

1087. 修剪草坪

在一年前赢得了小镇的最佳草坪比赛后,FJ 变得很懒,再也没有修剪过草坪。

现在,新一轮的最佳草坪比赛又开始了,FJ 希望能够再次夺冠。

然而,FJ 的草坪非常脏乱,因此,FJ 只能够让他的奶牛来完成这项工作。

FJ 有 N 只排成一排的奶牛,编号为 1 到 N。

每只奶牛的效率是不同的,奶牛 i 的效率为 Ei。

编号相邻的奶牛们很熟悉,如果 FJ 安排超过 K 只编号连续的奶牛,那么这些奶牛就会罢工去开派对。

因此,现在 FJ 需要你的帮助,找到最合理的安排方案并计算 FJ 可以得到的最大效率。

注意,方案需满足不能包含超过 K 只编号连续的奶牛。

#include <cstring>
#include <iostream>
#include <algorithm>

using namespace std;

typedef long long LL;

const int N = 1e5 + 10;

int n, m;
LL s[N];
LL f[N];
int q[N];

LL g(int i)
{
    if (!i) return 0;
    return f[i - 1] - s[i];
}

int main()
{
    scanf("%d%d", &n, &m);
    for (int i = 1; i <= n; i ++ )
    {
        scanf("%lld", &s[i]);
        s[i] += s[i - 1];
    }

    int hh = 0, tt = 0;
    for (int i = 1; i <= n; i ++ )
    {
        if (q[hh] < i - m) hh ++ ;
        f[i] = max(f[i - 1], g(q[hh]) + s[i]);
        while (hh <= tt && g(q[tt]) <= g(i)) tt -- ;
        q[ ++ tt] = i;
    }

    printf("%lld\n", f[n]);

    return 0;
}

作者:yxc
链接:https://www.acwing.com/activity/content/code/content/128401/
来源:AcWing
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。

1088. 旅行问题

John 打算驾驶一辆汽车周游一个环形公路。

公路上总共有 n 个车站,每站都有若干升汽油(有的站可能油量为零),每升油可以让汽车行驶一千米。

John 必须从某个车站出发,一直按顺时针(或逆时针)方向走遍所有的车站,并回到起点。

在一开始的时候,汽车内油量为零,John 每到一个车站就把该站所有的油都带上(起点站亦是如此),行驶过程中不能出现没有油的情况。

任务:判断以每个车站为起点能否按条件成功周游一周。
第一行是一个整数 n,表示环形公路上的车站数;

接下来 n 行,每行两个整数 pi,di,分别表示表示第 i 号车站的存油量和第 i 号车站到 顺时针方向 下一站的距离。

#include <cstring>
#include <iostream>
#include <algorithm>

using namespace std;

typedef long long LL;

const int N = 2e6 + 10;

int n;
int oil[N], dist[N];
LL s[N];
int q[N];
bool ans[N];

int main()
{
    scanf("%d", &n);
    for (int i = 1; i <= n; i ++ )
    {
        scanf("%d%d", &oil[i], &dist[i]);
        s[i] = s[i + n] = oil[i] - dist[i];
    }
    for (int i = 1; i <= n * 2; i ++ ) s[i] += s[i - 1];

    int hh = 0, tt = 0;
    q[0] = n * 2 + 1;
    for (int i = n * 2; i >= 0; i -- )
    {
        if (q[hh] > i + n) hh ++ ;
        if (i < n)
        {
            if (s[i] <= s[q[hh]]) ans[i + 1] = true;
        }
        while (hh <= tt && s[q[tt]] >= s[i]) tt -- ;
        q[ ++ tt] = i;
    }

    dist[0] = dist[n];
    for (int i = 1; i <= n; i ++ ) s[i] = s[i + n] = oil[i] - dist[i - 1];
    for (int i = 1; i <= n * 2; i ++ ) s[i] += s[i - 1];

    hh = 0, tt = 0;
    q[0] = 0;
    for (int i = 1; i <= n * 2; i ++ )
    {
        if (q[hh] < i - n) hh ++ ;
        if (i > n)
        {
            if (s[i] >= s[q[hh]]) ans[i - n] = true;
        }
        while (hh <= tt && s[q[tt]] <= s[i]) tt -- ;
        q[ ++ tt] = i;
    }

    for (int i = 1; i <= n; i ++ )
        if (ans[i]) puts("TAK");
        else puts("NIE");

    return 0;
}

作者:yxc
链接:https://www.acwing.com/activity/content/code/content/128104/
来源:AcWing
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。

1089. 烽火传递

烽火台是重要的军事防御设施,一般建在交通要道或险要处。

一旦有军情发生,则白天用浓烟,晚上有火光传递军情。

在某两个城市之间有 n 座烽火台,每个烽火台发出信号都有一定的代价。

为了使情报准确传递,在连续 m 个烽火台中至少要有一个发出信号。

现在输入 n,m 和每个烽火台的代价,请计算在两城市之间准确传递情报所需花费的总代价最少为多少。

#include <cstring>
#include <iostream>
#include <algorithm>

using namespace std;

const int N = 2e5 + 10, INF = 1e9;

int n, m;
int w[N], q[N];
int f[N];

int main()
{
    scanf("%d%d", &n, &m);
    for (int i = 1; i <= n; i ++ ) scanf("%d", &w[i]);

    int hh = 0, tt = 0;
    for (int i = 1; i <= n; i ++ )
    {
        if (q[hh] < i - m) hh ++ ;
        f[i] = f[q[hh]] + w[i];
        while (hh <= tt && f[q[tt]] >= f[i]) tt -- ;
        q[ ++ tt] = i;
    }

    int res = INF;
    for (int i = n - m + 1; i <= n; i ++ ) res = min(res, f[i]);

    printf("%d\n", res);

    return 0;
}

作者:yxc
链接:https://www.acwing.com/activity/content/code/content/128105/
来源:AcWing
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。

1090. 绿色通道

高二数学《绿色通道》总共有 n 道题目要抄,编号 1,2,…,n,抄第 i 题要花 ai 分钟。

小 Y 决定只用不超过 t 分钟抄这个,因此必然有空着的题。

每道题要么不写,要么抄完,不能写一半。

下标连续的一些空题称为一个空题段,它的长度就是所包含的题目数。

这样应付自然会引起马老师的愤怒,最长的空题段越长,马老师越生气。

现在,小 Y 想知道他在这 t 分钟内写哪些题,才能够尽量减轻马老师的怒火。

由于小 Y 很聪明,你只要告诉他最长的空题段至少有多长就可以了,不需输出方案。

#include <cstring>
#include <iostream>
#include <algorithm>

using namespace std;

const int N = 50010, INF = 1e9;

int n, m;
int w[N];
int f[N], q[N];

bool check(int k)
{
    f[0] = 0;
    int hh = 0, tt = 0;
    for (int i = 1; i <= n; i ++ )
    {
        if (hh <= tt && q[hh] < i - k - 1) hh ++ ;
        f[i] = f[q[hh]] + w[i];
        while (hh <= tt && f[q[tt]] >= f[i]) tt -- ;
        q[ ++ tt] = i;
    }

    int res = INF;
    for (int i = n - k; i <= n; i ++ ) res = min(res, f[i]);

    return res <= m;
}

int main()
{
    scanf("%d%d", &n, &m);

    for (int i = 1; i <= n; i ++ ) scanf("%d", &w[i]);

    int l = 0, r = n;
    while (l < r)
    {
        int mid = l + r >> 1;
        if (check(mid)) r = mid;
        else l = mid + 1;
    }

    printf("%d\n", r);

    return 0;
}

作者:yxc
链接:https://www.acwing.com/activity/content/code/content/128106/
来源:AcWing
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。

1091. 理想的正方形(二位单调队列优化

有一个 a×b 的整数组成的矩阵,现请你从中找出一个 n×n 的正方形区域,使得该区域所有数中的最大值和最小值的差最小。

#include <cstring>
#include <iostream>
#include <algorithm>

using namespace std;

const int N = 1010, INF = 1e9;

int n, m, k;
int w[N][N];
int row_min[N][N], row_max[N][N];
int q[N];

void get_min(int a[], int b[], int tot)
{
    int hh = 0, tt = -1;
    for (int i = 1; i <= tot; i ++ )
    {
        if (hh <= tt && q[hh] <= i - k) hh ++ ;
        while (hh <= tt && a[q[tt]] >= a[i]) tt -- ;
        q[ ++ tt] = i;
        b[i] = a[q[hh]];
    }
}

void get_max(int a[], int b[], int tot)
{
    int hh = 0, tt = -1;
    for (int i = 1; i <= tot; i ++ )
    {
        if (hh <= tt && q[hh] <= i - k) hh ++ ;
        while (hh <= tt && a[q[tt]] <= a[i]) tt -- ;
        q[ ++ tt] = i;
        b[i] = a[q[hh]];
    }
}



int main()
{
    scanf("%d%d%d", &n, &m, &k);

    for (int i = 1; i <= n; i ++ )
        for (int j = 1; j <= m; j ++ )
            scanf("%d", &w[i][j]);

    for (int i = 1; i <= n; i ++ )
    {
        get_min(w[i], row_min[i], m);
        get_max(w[i], row_max[i], m);
    }

    int res = INF;
    int a[N], b[N], c[N];
    for (int i = k; i <= m; i ++ )
    {
        for (int j = 1; j <= n; j ++ ) a[j] = row_min[j][i];
        get_min(a, b, n);

        for (int j = 1; j <= n; j ++ ) a[j] = row_max[j][i];
        get_max(a, c, n);

        for (int j = k; j <= n; j ++ ) res = min(res, c[j] - b[j]);
    }

    printf("%d\n", res);

    return 0;
}

作者:yxc
链接:https://www.acwing.com/activity/content/code/content/128412/
来源:AcWing
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。

斜率优化dp

300. 任务安排1

有 N 个任务排成一个序列在一台机器上等待执行,它们的顺序不得改变。

机器会把这 N 个任务分成若干批,每一批包含连续的若干个任务。

从时刻 0 开始,任务被分批加工,执行第 i 个任务所需的时间是 Ti。

另外,在每批任务开始前,机器需要 S 的启动时间,故执行一批任务所需的时间是启动时间 S 加上每个任务所需时间之和。

一个任务执行后,将在机器中稍作等待,直至该批任务全部执行完毕。

也就是说,同一批任务将在同一时刻完成。

每个任务的费用是它的完成时刻乘以一个费用系数 Ci。

请为机器规划一个分组方案,使得总费用最小。
1≤N≤5000,
0≤S≤50,
1≤Ti,Ci≤100

分析
我们为什么要枚举每一组?是为了得到启动机器的次数进而算费用
我们可以发现,只要我们分一组,后面还未分组的机器一定会增加相应的费用,高兴的是我们现在就可以算出来增加的费用是多少,所以我们只需要提前把这个多出来的费用加上就行了
状态转移方程:f[i]=min(f[j]+(c[i]−c[j])×t[i]+(c[n]−c[j])×s)f[i]=min(f[j]+(c[i]−c[j])×t[i]+(c[n]−c[j])×s),0≤j<i0≤j<i
同上c[i]c[i]和t[i]t[i]是前缀和。

#include<iostream>
#include<algorithm>
#include<cstring>
using namespace std;
const int N=5010;
int t[N],c[N];
int f[N];
int n,s;
int main()
{
    cin>>n>>s;
    for(int i=1;i<=n;i++)
    {
        cin>>t[i]>>c[i];
        t[i]+=t[i-1];
        c[i]+=c[i-1];
    }
    memset(f,0x3f3f3f3f,sizeof f);
    f[0]=0;
    for(int i=1;i<=n;i++)
        for(int j=0;j<i;j++)    
            f[i]=min(f[i],f[j]+(c[i]-c[j])*t[i]+(c[n]-c[j])*s);
    cout<<f[n]<<endl;
    return 0;
}

作者:Fighting_Peter
链接:https://www.acwing.com/solution/content/13036/
来源:AcWing
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。

1≤N≤3×105,
1≤Ti,Ci≤512,
0≤S≤512

#include <cstring>
#include <iostream>
#include <algorithm>

using namespace std;

typedef long long LL;

const int N = 300010;

int n, s;
LL c[N], t[N];
LL f[N];
int q[N];

int main()
{
    scanf("%d%d", &n, &s);
    for (int i = 1; i <= n; i ++ )
    {
        scanf("%lld%lld", &t[i], &c[i]);
        t[i] += t[i - 1];
        c[i] += c[i - 1];
    }

    int hh = 0, tt = 0;
    q[0] = 0;

    for (int i = 1; i <= n; i ++ )
    {
        while (hh < tt && (f[q[hh + 1]] - f[q[hh]]) <= (t[i] + s) * (c[q[hh + 1]] - c[q[hh]])) hh ++ ;
        int j = q[hh];
        f[i] = f[j] - (t[i] + s) * c[j] + t[i] * c[i] + s * c[n];
        while (hh < tt && (__int128)(f[q[tt]] - f[q[tt - 1]]) * (c[i] - c[q[tt - 1]]) >= (__int128)(f[i] - f[q[tt - 1]]) * (c[q[tt]] - c[q[tt - 1]])) tt -- ;
        q[ ++ tt] = i;
    }

    printf("%lld\n", f[n]);

    return 0;
}

作者:yxc
链接:https://www.acwing.com/activity/content/code/content/128994/
来源:AcWing
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。

302. 任务安排3

1≤N≤3×105,
0≤S,Ci≤512,
−512≤Ti≤512

#include <cstring>
#include <iostream>
#include <algorithm>

using namespace std;

typedef long long LL;

const int N = 300010;

int n, s;
LL t[N], c[N];
LL f[N];
int q[N];

int main()
{
    scanf("%d%d", &n, &s);
    for (int i = 1; i <= n; i ++ )
    {
        scanf("%lld%lld", &t[i], &c[i]);
        t[i] += t[i - 1];
        c[i] += c[i - 1];
    }

    int hh = 0, tt = 0;
    q[0] = 0;

    for (int i = 1; i <= n; i ++ )
    {
        int l = hh, r = tt;
        while (l < r)
        {
            int mid = l + r >> 1;
            if (f[q[mid + 1]] - f[q[mid]] > (t[i] + s) * (c[q[mid + 1]] - c[q[mid]])) r = mid;
            else l = mid + 1;
        }

        int j = q[r];
        f[i] = f[j] -   (t[i] + s) * c[j] + t[i] * c[i] + s * c[n];
        while (hh < tt && (double)(f[q[tt]] - f[q[tt - 1]]) * (c[i] - c[q[tt - 1]]) >= (double)(f[i] - f[q[tt - 1]]) * (c[q[tt]] - c[q[tt - 1]])) tt -- ;
        q[ ++ tt] = i;
    }

    printf("%lld\n", f[n]);

    return 0;
}

作者:yxc
链接:https://www.acwing.com/activity/content/code/content/128996/
来源:AcWing
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。
有 NN 个任务等待完成(顺序不改变),这 NN 个任务被分成若干批,每批包含相邻的若干任务。从时刻 00 开始,这些任务被分批加工,第 ii 个任务单独完成所需的时间是 TiTi 。只有一台机器,在每批任务开始前,机器需要启动时间 SS,完成这批任务所需的时间是各个任务需要时间的总和(同一批任务将在同一时刻完成)。每个任务的费用是它的完成时刻乘以它的费用系数 CiCi。请确定一个分组方案,使得总费用最小。

T1
1⩽N⩽5000,0⩽S⩽50,1⩽Ti,Ci⩽1001⩽N⩽5000,0⩽S⩽50,1⩽Ti,Ci⩽100
令 sumT[i]=∑ij=1T[j],sumC[i]=∑ij=1F[j]sumT[i]=∑j=1iT[j],sumC[i]=∑j=1iF[j],那么
f[p][i]=min(f[p−1][j]+(sumT[i]+p×S)×(sumC[i]−sumC[j]))
f[p][i]=min(f[p−1][j]+(sumT[i]+p×S)×(sumC[i]−sumC[j]))

考虑费用提前计算。每次分出一批任务,对后面的每个任务的用时都会产生 SS 的贡献,那么可以提前计算。
f[i]=min(f[j]+sumT[i]×(sumC[i]−sumC[j])+(sumC[n]−sumC[j]))
f[i]=min(f[j]+sumT[i]×(sumC[i]−sumC[j])+(sumC[n]−sumC[j]))
代码

#include <bits/stdc++.h>
using namespace std;
const int N=5010;
int n,s,t[N],c[N],f[N];

int main() 
{
    scanf( "%d%d",&n,&s );
    for ( int i=1; i<=n; i++ )
        scanf( "%d%d",t+i,c+i );

    memset( f,0x3f,sizeof f );
    f[0]=0;
    for ( int i=1; i<=n; i++ )
        t[i]+=t[i-1],c[i]+=c[i-1];
    for ( int i=1; i<=n; i++ )
     for ( int j=0; j<i; j++ )
        f[i]=min( f[i],f[j]+(c[i]-c[j])*t[i]+s*(c[n]-c[j]) );

    printf( "%d\n",f[n] );
} 
T2
1≤N≤3e51≤N≤3e5 1≤Ti,Ci≤512,0≤S≤5121≤Ti,Ci≤512,0≤S≤512
NN 变大了,需要加上斜率优化。
f[i]=min(f[j]+sumT[i]×(sumC[i]−sumC[j])+(sumC[n]−sumC[j]))
f[i]=min(f[j]+sumT[i]×(sumC[i]−sumC[j])+(sumC[n]−sumC[j]))

考虑转化成斜率式子。
f[j]=(sumT[i]+S)×sumC[j]+f[i]−sumC[i]×sumT[i]−sumC[n]×S
f[j]=(sumT[i]+S)×sumC[j]+f[i]−sumC[i]×sumT[i]−sumC[n]×S
代码
#include <bits/stdc++.h>
#define ll long long
#define lb long double
using namespace std;
const int N=3e5+10;
int n,s,q[N],h=0,t=0;
ll sc[N],st[N],f[N];

lb slope( int x,int y ) { return (lb)(f[y]-f[x])/(sc[y]-sc[x]); }

int main()
{
    scanf( "%d%d",&n,&s );
    for ( int i=1; i<=n; i++ )
    {
        scanf( "%lld%lld",&st[i],&sc[i] );
        st[i]+=st[i-1]; sc[i]+=sc[i-1];
    }

    for ( int i=1; i<=n; i++ )
    {
        while ( h<t && slope(q[h],q[h+1])<=( st[i]+s ) ) h++;
        f[i]=f[q[h]]-( st[i]+s )*sc[q[h]]+sc[i]*st[i]+s*sc[n];
        while ( h<t && slope(q[t-1],q[t])>=slope(q[t],i) ) t--;
        q[++t]=i;
    }

    printf( "%lld",f[n] );
}
T3
1≤N≤3e5,0≤S,Ci≤512,|Ti|5121≤N≤3e5,0≤S,Ci≤512,|Ti|512

任务的执行时间 tt 可能是负数,那么斜率不具有单调性,
就不能只保留大于 S+sumT[i]S+sumT[i] 的部分,而应该维护整个凸壳
此时队头不一定是最优决策,需要进行二分查找,求出一个位置,
使左侧的斜率小于 S+sumT[i]S+sumT[i] ,右侧斜率大于 S+sumT[i]S+sumT[i]

注:此题 AcWing 上数据较强,建议把 slope 改为交叉相乘,需要使用 __int128 或者 double .

代码
#include <bits/stdc++.h>
#define ll long long
using namespace std;
const int N=3e5+10;
int n,s,q[N],h=0,t=0;
ll sc[N],st[N];
double f[N];

int main()
{
    scanf( "%d%d",&n,&s );
    for ( int i=1; i<=n; i++ )
    {
        scanf( "%lld%lld",&st[i],&sc[i] );
        st[i]+=st[i-1]; sc[i]+=sc[i-1];
    }

    for ( int i=1; i<=n; i++ )
    {
        int l=h,r=t;
        while ( l<r )
        {
            int mid=(l+r)/2;
            if ( (f[q[mid+1]]-f[q[mid]])>(st[i]+s)*(sc[q[mid+1]]-sc[q[mid]]) ) r=mid;
            else l=mid+1;
        }
        f[i]=f[q[l]]-( st[i]+s )*sc[q[l]]+sc[i]*st[i]+s*sc[n];
        while ( h<t && ( f[q[t]]-f[q[t-1]] )*( sc[i]-sc[q[t]] )>=( f[i]-f[q[t]] )*( sc[q[t]]-sc[q[t-1]]  ) ) t--;
        q[++t]=i;
    }

    printf( "%.0lf",f[n] );
}

作者:RingweEH
链接:https://www.acwing.com/solution/content/23573/
来源:AcWing
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。

303. 运输小猫

小 S 是农场主,他养了 M 只猫,雇了 P 位饲养员。

农场中有一条笔直的路,路边有 N 座山,从 1 到 N 编号。

第 i 座山与第 i−1 座山之间的距离为 Di。

饲养员都住在 1 号山。

有一天,猫出去玩。

第 i 只猫去 Hi 号山玩,玩到时刻 Ti 停止,然后在原地等饲养员来接。

饲养员们必须回收所有的猫。

每个饲养员沿着路从 1 号山走到 N 号山,把各座山上已经在等待的猫全部接走。

饲养员在路上行走需要时间,速度为 1 米/单位时间。

饲养员在每座山上接猫的时间可以忽略,可以携带的猫的数量为无穷大。

例如有两座相距为 1 的山,一只猫在 2 号山玩,玩到时刻 3 开始等待。

如果饲养员从 1 号山在时刻 2 或 3 出发,那么他可以接到猫,猫的等待时间为 0 或 1。

而如果他于时刻 1 出发,那么他将于时刻 2 经过 2 号山,不能接到当时仍在玩的猫。

你的任务是规划每个饲养员从 1 号山出发的时间,使得所有猫等待时间的总和尽量小。

饲养员出发的时间可以为负。
t[i]表示第i只小猫玩耍的时间,d[i]表示第i只小猫到起点的路程
设a[i] = t[i] - d[i]
按a[i]排序
f[i][j] 表示前i个饲养员,最后接的小猫是j
s表示a的前缀和
f[i][j] = f[i - 1][k] + aj - (s[j] - s[k]);
移项
f[i - 1][k] + s[k] = a[j]k + f[i][j] - a[j]*j + s[j]
因为斜率单增,k单增,所以应用单调队列维护凸包,并找出第一个斜率大于当斜率的点,转移即可

还是要搞清更变量是什么含义,LL开全
开始一步转化还是比较神奇的,给你一大堆东西找出来真正有用的是啥
这题每个猫有用的其实就是可以开始接的时间,路程和时间直接转化到一起即可

#include <iostream>
#include <algorithm>
#include <cstring>

using namespace std;

typedef long long LL;
const int N = 1e5 + 5,M = N,P = 110;

int d[N],n,m,p,q[N];
LL a[N],s[N],f[P][N];

LL get_y(int i,int j)
{
    return f[i - 1][j] + s[j];
}

int main()
{
    cin >> n >> m >> p;
    for(int i = 2;i <= n;i ++)
        scanf("%d",&d[i]),d[i] += d[i - 1];

    for(int i = 1;i <= m;i ++)
    {
        int hi,ti;
        scanf("%d%d",&hi,&ti);
        a[i] = ti - d[hi];
    }

    sort(a + 1,a + 1 + m);
    for(int i = 1;i <= m;i ++)
        s[i] = s[i - 1] + a[i];

    memset(f,0x3f,sizeof f);
    for(int i = 0;i <= p;i ++)
        f[i][0] = 0;

    for(int i = 1;i <= p;i ++)
    {
        int tt = 0,hh = 0;
        for(int j = 1;j <= m;j ++)
        {
            while(tt > hh && get_y(i,q[hh + 1]) - get_y(i,q[hh]) <= a[j] * (q[hh + 1] - q[hh]))hh ++;
            int k = q[hh];
            f[i][j] = f[i - 1][k] + (j - k) * a[j] - (s[j] - s[k]);
            while(tt > hh && (get_y(i,q[tt]) - get_y(i,q[tt - 1])) * (j - q[tt])>= (get_y(i,j) - get_y(i,q[tt])) * (q[tt] - q[tt - 1]))tt --;
            q[++ tt] = j;
        }
    }

    cout << f[p][m];
}

作者:羽笙
链接:https://www.acwing.com/solution/content/5371/
来源:AcWing
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。

线段树

245. 你能回答这些问题吗

给定长度为 N 的数列 A,以及 M 条指令,每条指令可能是以下两种之一:

1 x y,查询区间 [x,y] 中的最大连续子段和,即 maxx≤l≤r≤y{∑i=lrA[i]}。
2 x y,把 A[x] 改成 y。
对于每个查询指令,输出一个整数表示答案。

#include <cstdio>
#include <cstring>
#include <iostream>
#include <algorithm>

using namespace std;

const int N = 500010;

int n, m;
int w[N];
struct Node
{
    int l, r;
    int sum, lmax, rmax, tmax;
}tr[N * 4];

void pushup(Node &u, Node &l, Node &r)
{
    u.sum = l.sum + r.sum;
    u.lmax = max(l.lmax, l.sum + r.lmax);
    u.rmax = max(r.rmax, r.sum + l.rmax);
    u.tmax = max(max(l.tmax, r.tmax), l.rmax + r.lmax);
}

void pushup(int u)
{
    pushup(tr[u], tr[u << 1], tr[u << 1 | 1]);
}

void build(int u, int l, int r)
{
    if (l == r) tr[u] = {l, r, w[r], w[r], w[r], w[r]};
    else
    {
        tr[u] = {l, r};
        int mid = l + r >> 1;
        build(u << 1, l, mid), build(u << 1 | 1, mid + 1, r);
        pushup(u);
    }
}

void modify(int u, int x, int v)
{
    if (tr[u].l == x && tr[u].r == x) tr[u] = {x, x, v, v, v, v};
    else
    {
        int mid = tr[u].l + tr[u].r >> 1;
        if (x <= mid) modify(u << 1, x, v);
        else modify(u << 1 | 1, x, v);
        pushup(u);
    }
}

Node query(int u, int l, int r)
{
    if (tr[u].l >= l && tr[u].r <= r) return tr[u];
    else
    {
        int mid = tr[u].l + tr[u].r >> 1;
        if (r <= mid) return query(u << 1, l, r);
        else if (l > mid) return query(u << 1 | 1, l, r);
        else
        {
            auto left = query(u << 1, l, r);
            auto right = query(u << 1 | 1, l, r);
            Node res;
            pushup(res, left, right);
            return res;
        }
    }
}

int main()
{
    scanf("%d%d", &n, &m);
    for (int i = 1; i <= n; i ++ ) scanf("%d", &w[i]);
    build(1, 1, n);

    int k, x, y;
    while (m -- )
    {
        scanf("%d%d%d", &k, &x, &y);
        if (k == 1)
        {
            if (x > y) swap(x, y);
            printf("%d\n", query(1, x, y).tmax);
        }
        else modify(1, x, y);
    }

    return 0;
}

作者:yxc
链接:https://www.acwing.com/activity/content/code/content/167568/
来源:AcWing
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。

246. 区间最大公约数

给定一个长度为 N 的数列 A,以及 M 条指令,每条指令可能是以下两种之一:

C l r d,表示把 A[l],A[l+1],…,A[r] 都加上 d。
Q l r,表示询问 A[l],A[l+1],…,A[r] 的最大公约数(GCD)。
对于每个询问,输出一个整数表示答案。

#include<bits/stdc++.h>
#define node Node
using namespace std;
typedef long long LL;
#define int long long
const int N = 500010;
int n,m,w[N];
struct node{
    int l,r,sum,d;
}tr[N*4];

int gcd(int a,int b){
    return b?gcd(b,a%b):a;
}

void pushup(node & t,node & a,node &b){
    t.sum=a.sum+b.sum;
    t.d=gcd(a.d,b.d);
}
void pushup(int u){
    pushup(tr[u],tr[u<<1],tr[u<<1|1]);
}

void build(int u, int l, int r)
{
    if (l == r)
    {
        LL b = w[r] - w[r - 1];
        tr[u] = {l, r, b, b};
    }
    else
    {
        tr[u].l = l, tr[u].r = r;
        int mid = l + r >> 1;
        build(u << 1, l, mid), build(u << 1 | 1, mid + 1, r);
        pushup(u);
    }
}

void modify(int u,int x,int v){
    if(tr[u].l==x&&tr[u].r==x){
         tr[u].sum+=v;
         tr[u].d+=v;
    }
    else{
        
        int mid= tr[u].l+tr[u].r>>1;
        if(mid<x)modify(u<<1|1,x,v);
        else modify(u<<1,x,v);
        
        pushup(u);
    }
}

Node query(int u, int l, int r)
{
    if (tr[u].l >= l && tr[u].r <= r) return tr[u];
    else
    {
        int mid = tr[u].l + tr[u].r >> 1;
        if (r <= mid) return query(u << 1, l, r);//mid在R的右边,就到左边去找答案
        //(凡是递归到左边的边界判断都用等号
        
        else if (l > mid) return query(u << 1 | 1, l, r);//mid在L的左边,就到右边去找答案
        
        else//如果mid在L和R中间,就分两边去递归找答案
        {
            auto left = query(u << 1, l, r);
            auto right = query(u << 1 | 1, l, r);
            Node res;
            pushup(res, left, right);
            return res;
        }
    }
}



signed main(){
    cin>>n>>m;
    for(int i=1;i<=n;i++)cin>>w[i];
    build(1,1,n);
    while(m--){
        char c;
        int l,r;
        cin>>c>>l>>r;
        if(c=='C'){
            int v;
            cin>>v;
            modify(1,l,v);
            //差分思想,l~r这段区间加上一个数要在l加上去,在r+1减回去
            if(r+1<=n)modify(1,r+1,-v);//边界
        }
        else{
            node left=query(1,1,l);
            node right={0,0,0,0};
            if(l+1<=r)right=query(1,l+1,r);
            cout<<abs( gcd(left.sum ,  right.d )  )<<endl;
            
        }
    }
}

243. 一个简单的整数问题2

给定一个长度为 N 的数列 A,以及 M 条指令,每条指令可能是以下两种之一:

C l r d,表示把 A[l],A[l+1],…,A[r] 都加上 d。
Q l r,表示询问数列中第 l∼r 个数的和。
对于每个询问,输出一个整数表示答案。

#include <cstdio>
#include <cstring>
#include <iostream>
#include <algorithm>

using namespace std;

typedef long long LL;

const int N = 100010;

int n, m;
int w[N];
struct Node
{
    int l, r;
    LL sum, add;
}tr[N * 4];

void pushup(int u)
{
    tr[u].sum = tr[u << 1].sum + tr[u << 1 | 1].sum;
}

void pushdown(int u)
{
    auto &root = tr[u], &left = tr[u << 1], &right = tr[u << 1 | 1];
    if (root.add)
    {
        left.add += root.add, left.sum += (LL)(left.r - left.l + 1) * root.add;
        right.add += root.add, right.sum += (LL)(right.r - right.l + 1) * root.add;
        root.add = 0;
    }
}

void build(int u, int l, int r)
{
    if (l == r) tr[u] = {l, r, w[r], 0};
    else
    {
        tr[u] = {l, r};
        int mid = l + r >> 1;
        build(u << 1, l, mid), build(u << 1 | 1, mid + 1, r);
        pushup(u);
    }
}

void modify(int u, int l, int r, int d)
{
    if (tr[u].l >= l && tr[u].r <= r)
    {
        tr[u].sum += (LL)(tr[u].r - tr[u].l + 1) * d;
        tr[u].add += d;
    }
    else    // 一定要分裂
    {
        pushdown(u);
        int mid = tr[u].l + tr[u].r >> 1;
        if (l <= mid) modify(u << 1, l, r, d);
        if (r > mid) modify(u << 1 | 1, l, r, d);
        pushup(u);
    }
}

LL query(int u, int l, int r)
{
    if (tr[u].l >= l && tr[u].r <= r) return tr[u].sum;

    pushdown(u);
    int mid = tr[u].l + tr[u].r >> 1;
    LL sum = 0;
    if (l <= mid) sum = query(u << 1, l, r);
    if (r > mid) sum += query(u << 1 | 1, l, r);
    return sum;
}


int main()
{
    scanf("%d%d", &n, &m);

    for (int i = 1; i <= n; i ++ ) scanf("%d", &w[i]);

    build(1, 1, n);

    char op[2];
    int l, r, d;

    while (m -- )
    {
        scanf("%s%d%d", op, &l, &r);
        if (*op == 'C')
        {
            scanf("%d", &d);
            modify(1, l, r, d);
        }
        else printf("%lld\n", query(1, l, r));
    }

    return 0;
}

作者:yxc
链接:https://www.acwing.com/activity/content/code/content/167900/
来源:AcWing
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。

1277. 维护序列

老师交给小可可一个维护数列的任务,现在小可可希望你来帮他完成。

有长为 N 的数列,不妨设为 a1,a2,…,aN。

有如下三种操作形式:

把数列中的一段数全部乘一个值;
把数列中的一段数全部加一个值;
询问数列中的一段数的和,由于答案可能很大,你只需输出这个数模 P 的值

#include <cstdio>
#include <cstring>
#include <iostream>
#include <algorithm>

using namespace std;

typedef long long LL;

const int N = 100010;

int n, p, m;
int w[N];
struct Node
{
    int l, r;
    int sum, add, mul;
}tr[N * 4];

void pushup(int u)
{
    tr[u].sum = (tr[u << 1].sum + tr[u << 1 | 1].sum) % p;
}

void eval(Node &t, int add, int mul)
{
    t.sum = ((LL)t.sum * mul + (LL)(t.r - t.l + 1) * add) % p;
    t.mul = (LL)t.mul * mul % p;
    t.add = ((LL)t.add * mul + add) % p;
}

void pushdown(int u)
{
    eval(tr[u << 1], tr[u].add, tr[u].mul);
    eval(tr[u << 1 | 1], tr[u].add, tr[u].mul);
    tr[u].add = 0, tr[u].mul = 1;
}

void build(int u, int l, int r)
{
    if (l == r) tr[u] = {l, r, w[r], 0, 1};
    else
    {
        tr[u] = {l, r, 0, 0, 1};
        int mid = l + r >> 1;
        build(u << 1, l, mid), build(u << 1 | 1, mid + 1, r);
        pushup(u);
    }
}

void modify(int u, int l, int r, int add, int mul)
{
    if (tr[u].l >= l && tr[u].r <= r) eval(tr[u], add, mul);
    else
    {
        pushdown(u);
        int mid = tr[u].l + tr[u].r >> 1;
        if (l <= mid) modify(u << 1, l, r, add, mul);
        if (r > mid) modify(u << 1 | 1, l, r, add, mul);
        pushup(u);
    }
}

int query(int u, int l, int r)
{
    if (tr[u].l >= l && tr[u].r <= r) return tr[u].sum;

    pushdown(u);
    int mid = tr[u].l + tr[u].r >> 1;
    int sum = 0;
    if (l <= mid) sum = query(u << 1, l, r);
    if (r > mid) sum = (sum + query(u << 1 | 1, l, r)) % p;
    return sum;
}

int main()
{
    scanf("%d%d", &n, &p);
    for (int i = 1; i <= n; i ++ ) scanf("%d", &w[i]);
    build(1, 1, n);

    scanf("%d", &m);
    while (m -- )
    {
        int t, l, r, d;
        scanf("%d%d%d", &t, &l, &r);
        if (t == 1)
        {
            scanf("%d", &d);
            modify(1, l, r, 0, d);
        }
        else if (t == 2)
        {
            scanf("%d", &d);
            modify(1, l, r, d, 1);
        }
        else printf("%d\n", query(1, l, r));
    }

    return 0;
}

作者:yxc
链接:https://www.acwing.com/activity/content/code/content/167944/
来源:AcWing
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。

3301. 星际旅行

乔帝要规划一次星际旅行,星际空间可以视为一个 3 维坐标系,乔帝有 n(n≤109) 个动力装置排成一行(下标从 1 到 n)。

第 i 个动力装置可以让他的飞船 3 个维度的坐标分别增加 xi,yi,zi。

一开始这些动力装置的所有参数都是 0。

在规划过程中,乔帝可能会对动力装置进行调整,也可能会对一些动力装置的动力进行评估。

具体来说,乔帝会进行 m(m≤40000) 次操作,每次操作可能是以下四种操作之一:

动力增加:指定一个区间 [L,R] 和三个参数 a,b,c,令区间内所有动力装置的 3 维坐标分别增加 a,b,c
动力强化:指定一个区间 [L,R] 和一个参数 k,令区间内所有动力装置的 3 维坐标分别乘 k
动力转向:指定一个区间 [L,R] ,令区间内所有动力装置的 x,y,z 坐标分别变为原来的 y 坐标,z 坐标,x 坐标
动力查询:指定一个区间 [L,R] ,询问如果使用区间内所有动力装置各一次能将乔帝送到离起点多远的地方(输出距离的平方除以 109+7 的余数)

#include <iostream>
#include <cstring>
#include <algorithm>
#include <vector>

using namespace std;

typedef long long LL;
const int N = 80010, MOD = 1e9 + 7;

int n, m;
struct Q
{
    int t, l, r, k, a, b, c;
}q[N];
vector<int> xs;
struct Node
{
    int l, r, k;
    int a[3], b[3], s[3];

    int get_len()
    {
        return xs[r] - xs[l - 1];
    }
}tr[N * 4];

int get(int x)
{
    return lower_bound(xs.begin(), xs.end(), x) - xs.begin();
}

void pushup(int u)
{
    auto l = tr[u << 1].s, r = tr[u << 1 | 1].s;
    for (int i = 0; i < 3; i ++ )
        tr[u].s[i] = (l[i] + r[i]) % MOD;
}

void rotate(int a[])
{
    int t = a[0];
    a[0] = a[1], a[1] = a[2], a[2] = t;
}

void eval(int u, int k)
{
    k %= 3;
    for (int i = 0; i < k; i ++ )
    {
        rotate(tr[u].a), rotate(tr[u].b), rotate(tr[u].s);
    }
    tr[u].k += k;
}

void eval(int u, int a[], int b[])
{
    for (int i = 0; i < 3; i ++ )
    {
        tr[u].s[i] = ((LL)tr[u].s[i] * a[i] + (LL)tr[u].get_len() * b[i]) % MOD;
        int c = (LL)tr[u].a[i] * a[i] % MOD;
        int d = ((LL)tr[u].b[i] * a[i] + b[i]) % MOD;
        tr[u].a[i] = c, tr[u].b[i] = d;
    }
}

void pushdown(int u)
{
    eval(u << 1, tr[u].k), eval(u << 1 | 1, tr[u].k);
    tr[u].k = 0;

    eval(u << 1, tr[u].a, tr[u].b), eval(u << 1 | 1, tr[u].a, tr[u].b);
    for (int i = 0; i < 3; i ++ )
        tr[u].a[i] = 1, tr[u].b[i] = 0;
}

void build(int u, int l, int r)
{
    tr[u] = {l, r};
    for (int i = 0; i < 3; i ++ ) tr[i].a[i] = 1;
    if (l == r) return;
    int mid = l + r >> 1;
    build(u << 1, l, mid), build(u << 1 | 1, mid + 1, r);
}

void update(int u, int l, int r, int k, int a[], int b[])
{
    if (tr[u].l >= l && tr[u].r <= r) eval(u, k), eval(u, a, b);
    else
    {
        pushdown(u);
        int mid = tr[u].l + tr[u].r >> 1;
        if (l <= mid) update(u << 1, l, r, k, a, b);
        if (r > mid) update(u << 1 | 1, l, r, k, a, b);
        pushup(u);
    }
}

vector<int> query(int u, int l, int r)
{
    if (tr[u].l >= l && tr[u].r <= r) return {tr[u].s[0], tr[u].s[1], tr[u].s[2]};
    pushdown(u);
    int mid = tr[u].l + tr[u].r >> 1;
    vector<int> res(3);
    if (l <= mid) res = query(u << 1, l, r);
    if (r > mid)
    {
        auto t = query(u << 1 | 1, l, r);
        for (int i = 0; i < 3; i ++ )
            res[i] = (res[i] + t[i]) % MOD;
    }
    return res;
}

int main()
{
    scanf("%d%d", &n, &m);
    for (int i = 0; i < m; i ++ )
    {
        q[i].k = 1;
        scanf("%d%d%d", &q[i].t, &q[i].l, &q[i].r);
        if (q[i].t == 1) scanf("%d%d%d", &q[i].a, &q[i].b, &q[i].c);
        else if (q[i].t == 2) scanf("%d", &q[i].k);
        xs.push_back(q[i].l - 1), xs.push_back(q[i].r);
    }
    sort(xs.begin(), xs.end());
    xs.erase(unique(xs.begin(), xs.end()), xs.end());

    build(1, 0, xs.size() - 1);
    for (int i = 0; i < m; i ++ )
    {
        int t = q[i].t, l = get(q[i].l - 1) + 1, r = get(q[i].r);
        int a[] = {q[i].k, q[i].k, q[i].k}, b[] = {q[i].a, q[i].b, q[i].c};
        if (t == 1 || t == 2)
            update(1, l, r, 0, a, b);
        else if (t == 3)
            update(1, l, r, 1, a, b);
        else
        {
            auto t = query(1, l, r);
            LL sum = 0;
            for (int j = 0; j < 3; j ++ )
                sum += (LL)t[j] * t[j];
            printf("%lld\n", sum % MOD);
        }
    }
    return 0;
}

作者:yxc
链接:https://www.acwing.com/activity/content/code/content/987439/
来源:AcWing
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。

247. 亚特兰蒂斯(扫描线法

输入包含多组测试用例。

对于每组测试用例,第一行包含整数 n,表示总的地图数量。

接下来 n 行,描绘了每张地图,每行包含四个数字 x1,y1,x2,y2(不一定是整数),(x1,y1) 和 (x2,y2) 分别是地图的左上角位置和右下角位置。

注意,坐标轴 x 轴从上向下延伸,y 轴从左向右延伸。

当输入用例 n=0 时,表示输入终止,该用例无需处理。

#include <cstdio>
#include <cstring>
#include <iostream>
#include <algorithm>
#include <vector>

using namespace std;

const int N = 100010;

int n;
struct Segment
{
    double x, y1, y2;
    int k;
    bool operator< (const Segment &t)const
    {
        return x < t.x;
    }
}seg[N * 2];
struct Node
{
    int l, r;
    int cnt;
    double len;
}tr[N * 8];

vector<double> ys;

int find(double y)
{
    return lower_bound(ys.begin(), ys.end(), y) - ys.begin();
}

void pushup(int u)
{
    if (tr[u].cnt) tr[u].len = ys[tr[u].r + 1] - ys[tr[u].l];
    else if (tr[u].l != tr[u].r)
    {
        tr[u].len = tr[u << 1].len + tr[u << 1 | 1].len;
    }
    else tr[u].len = 0;
}

void build(int u, int l, int r)
{
    tr[u] = {l, r, 0, 0};
    if (l != r)
    {
        int mid = l + r >> 1;
        build(u << 1, l, mid), build(u << 1 | 1, mid + 1, r);
    }
}

void modify(int u, int l, int r, int k)
{
    if (tr[u].l >= l && tr[u].r <= r)
    {
        tr[u].cnt += k;
        pushup(u);
    }
    else
    {
        int mid = tr[u].l + tr[u].r >> 1;
        if (l <= mid) modify(u << 1, l, r, k);
        if (r > mid) modify(u << 1 | 1, l, r, k);
        pushup(u);
    }
}

int main()
{
    int T = 1;
    while (scanf("%d", &n), n)
    {
        ys.clear();
        for (int i = 0, j = 0; i < n; i ++ )
        {
            double x1, y1, x2, y2;
            scanf("%lf%lf%lf%lf", &x1, &y1, &x2, &y2);
            seg[j ++ ] = {x1, y1, y2, 1};
            seg[j ++ ] = {x2, y1, y2, -1};
            ys.push_back(y1), ys.push_back(y2);
        }

        sort(ys.begin(), ys.end());
        ys.erase(unique(ys.begin(), ys.end()), ys.end());

        build(1, 0, ys.size() - 2);

        sort(seg, seg + n * 2);

        double res = 0;
        for (int i = 0; i < n * 2; i ++ )
        {
            if (i > 0) res += tr[1].len * (seg[i].x - seg[i - 1].x);
            modify(1, find(seg[i].y1), find(seg[i].y2) - 1, seg[i].k);
        }

        printf("Test case #%d\n", T ++ );
        printf("Total explored area: %.2lf\n\n", res);
    }

    return 0;
}

作者:yxc
链接:https://www.acwing.com/activity/content/code/content/167934/
来源:AcWing
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。

主席树

256. 最大异或和

给定一个非负整数序列 a,初始长度为 N。

有 M 个操作,有以下两种操作类型:

A x:添加操作,表示在序列末尾添加一个数 x,序列的长度 N 增大 1。
Q l r x:询问操作,你需要找到一个位置 p,满足 l≤p≤r,使得:a[p] xor a[p+1] xor … xor a[N] xor x 最大,输出这个最大值。

#include <cstdio>
#include <cstring>
#include <iostream>
#include <algorithm>

using namespace std;

const int N = 600010, M = N * 25;

int n, m;
int s[N];
int tr[M][2], max_id[M];
int root[N], idx;

void insert(int i, int k, int p, int q)
{
    if (k < 0)
    {
        max_id[q] = i;
        return;
    }
    int v = s[i] >> k & 1;
    if (p) tr[q][v ^ 1] = tr[p][v ^ 1];
    tr[q][v] = ++ idx;
    insert(i, k - 1, tr[p][v], tr[q][v]);
    max_id[q] = max(max_id[tr[q][0]], max_id[tr[q][1]]);
}

int query(int root, int C, int L)
{
    int p = root;
    for (int i = 23; i >= 0; i -- )
    {
        int v = C >> i & 1;
        if (max_id[tr[p][v ^ 1]] >= L) p = tr[p][v ^ 1];
        else p = tr[p][v];
    }

    return C ^ s[max_id[p]];
}

int main()
{
    scanf("%d%d", &n, &m);

    max_id[0] = -1;
    root[0] = ++ idx;
    insert(0, 23, 0, root[0]);

    for (int i = 1; i <= n; i ++ )
    {
        int x;
        scanf("%d", &x);
        s[i] = s[i - 1] ^ x;
        root[i] = ++ idx;
        insert(i, 23, root[i - 1], root[i]);
    }

    char op[2];
    int l, r, x;
    while (m -- )
    {
        scanf("%s", op);
        if (*op == 'A')
        {
            scanf("%d", &x);
            n ++ ;
            s[n] = s[n - 1] ^ x;
            root[n] = ++ idx;
            insert(n, 23, root[n - 1], root[n]);
        }
        else
        {
            scanf("%d%d%d", &l, &r, &x);
            printf("%d\n", query(root[r - 1], s[n] ^ x, l - 1));
        }
    }

    return 0;
}

作者:yxc
链接:https://www.acwing.com/activity/content/code/content/168518/
来源:AcWing
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。

255. 第K小数

给定长度为 N 的整数序列 A,下标为 1∼N。

现在要执行 M 次操作,其中第 i 次操作为给出三个整数 li,ri,ki,求 A[li],A[li+1],…,A[ri] (即 A 的下标区间 [li,ri])中第 ki 小的数是多少。

#include <cstdio>
#include <cstring>
#include <iostream>
#include <algorithm>
#include <vector>

using namespace std;

const int N = 100010, M = 10010;

int n, m;
int a[N];
vector<int> nums;

struct Node
{
    int l, r;
    int cnt;
}tr[N * 4 + N * 17];

int root[N], idx;

int find(int x)
{
    return lower_bound(nums.begin(), nums.end(), x) - nums.begin();
}

int build(int l, int r)
{
    int p = ++ idx;
    if (l == r) return p;
    int mid = l + r >> 1;
    tr[p].l = build(l, mid), tr[p].r = build(mid + 1, r);
    return p;
}

int insert(int p, int l, int r, int x)
{
    int q = ++ idx;
    tr[q] = tr[p];
    if (l == r)
    {
        tr[q].cnt ++ ;
        return q;
    }
    int mid = l + r >> 1;
    if (x <= mid) tr[q].l = insert(tr[p].l, l, mid, x);
    else tr[q].r = insert(tr[p].r, mid + 1, r, x);
    tr[q].cnt = tr[tr[q].l].cnt + tr[tr[q].r].cnt;
    return q;
}

int query(int q, int p, int l, int r, int k)
{
    if (l == r) return r;
    int cnt = tr[tr[q].l].cnt - tr[tr[p].l].cnt;
    int mid = l + r >> 1;
    if (k <= cnt) return query(tr[q].l, tr[p].l, l, mid, k);
    else return query(tr[q].r, tr[p].r, mid + 1, r, k - cnt);
}

int main()
{
    scanf("%d%d", &n, &m);
    for (int i = 1; i <= n; i ++ )
    {
        scanf("%d", &a[i]);
        nums.push_back(a[i]);
    }

    sort(nums.begin(), nums.end());
    nums.erase(unique(nums.begin(), nums.end()), nums.end());

    root[0] = build(0, nums.size() - 1);

    for (int i = 1; i <= n; i ++ )
        root[i] = insert(root[i - 1], 0, nums.size() - 1, find(a[i]));

    while (m -- )
    {
        int l, r, k;
        scanf("%d%d%d", &l, &r, &k);
        printf("%d\n", nums[query(root[r], root[l - 1], 0, nums.size() - 1, k)]);
    }

    return 0;
}

作者:yxc
链接:https://www.acwing.com/activity/content/code/content/168534/
来源:AcWing
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。

ac自动机

1282. 搜索关键词

给定 n 个长度不超过 50 的由小写英文字母组成的单词,以及一篇长为 m 的文章。

请问,有多少个单词在文章中出现了。

#include <cstdio>
#include <cstring>
#include <iostream>
#include <algorithm>

using namespace std;

const int N = 10010, S = 55, M = 1000010;

int n;
int tr[N * S][26], cnt[N * S], idx;
char str[M];
int q[N * S], ne[N * S];

void insert()
{
    int p = 0;
    for (int i = 0; str[i]; i ++ )
    {
        int t = str[i] - 'a';
        if (!tr[p][t]) tr[p][t] = ++ idx;
        p = tr[p][t];
    }
    cnt[p] ++ ;
}

void build()
{
    int hh = 0, tt = -1;
    for (int i = 0; i < 26; i ++ )
        if (tr[0][i])
            q[ ++ tt] = tr[0][i];

    while (hh <= tt)
    {
        int t = q[hh ++ ];
        for (int i = 0; i < 26; i ++ )
        {
            int p = tr[t][i];
            if (!p) tr[t][i] = tr[ne[t]][i];
            else
            {
                ne[p] = tr[ne[t]][i];
                q[ ++ tt] = p;
            }
        }
    }
}

int main()
{
    int T;
    scanf("%d", &T);
    while (T -- )
    {
        memset(tr, 0, sizeof tr);
        memset(cnt, 0, sizeof cnt);
        memset(ne, 0, sizeof ne);
        idx = 0;

        scanf("%d", &n);
        for (int i = 0; i < n; i ++ )
        {
            scanf("%s", str);
            insert();
        }

        build();

        scanf("%s", str);

        int res = 0;
        for (int i = 0, j = 0; str[i]; i ++ )
        {
            int t = str[i] - 'a';
            j = tr[j][t];

            int p = j;
            while (p)
            {
                res += cnt[p];
                cnt[p] = 0;
                p = ne[p];
            }
        }

        printf("%d\n", res);
    }

    return 0;
}

作者:yxc
链接:https://www.acwing.com/activity/content/code/content/169821/
来源:AcWing
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。

1285. 单词

某人读论文,一篇论文是由许多单词组成的。

但他发现一个单词会在论文中出现很多次,现在他想知道每个单词分别在论文中出现多少次。

#include <cstdio>
#include <cstring>
#include <iostream>
#include <algorithm>

using namespace std;

const int N = 1000010;

int n;
int tr[N][26], f[N], idx;
int q[N], ne[N];
char str[N];
int id[210];

void insert(int x)
{
    int p = 0;
    for (int i = 0; str[i]; i ++ )
    {
        int t = str[i] - 'a';
        if (!tr[p][t]) tr[p][t] = ++ idx;
        p = tr[p][t];
        f[p] ++ ;
    }
    id[x] = p;
}

void build()
{
    int hh = 0, tt = -1;
    for (int i = 0; i < 26; i ++ )
        if (tr[0][i])
            q[ ++ tt] = tr[0][i];

    while (hh <= tt)
    {
        int t = q[hh ++ ];
        for (int i = 0; i < 26; i ++ )
        {
            int &p = tr[t][i];
            if (!p) p = tr[ne[t]][i];
            else
            {
                ne[p] = tr[ne[t]][i];
                q[ ++ tt] = p;
            }
        }
    }
}

int main()
{
    scanf("%d", &n);

    for (int i = 0; i < n; i ++ )
    {
        scanf("%s", str);
        insert(i);
    }

    build();

    for (int i = idx - 1; i >= 0; i -- ) f[ne[q[i]]] += f[q[i]];

    for (int i = 0; i < n; i ++ ) printf("%d\n", f[id[i]]);

    return 0;
}

作者:yxc
链接:https://www.acwing.com/activity/content/code/content/169866/
来源:AcWing
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。

splay

955. 维护数列

请写一个程序,要求维护一个数列,支持以6 种操作:

#include <iostream>
#include <cstdio>
#include <cstring>
#include <algorithm>

using namespace std;

const int N = 500010, INF = 1e9;

int n, m;
struct Node
{
    int s[2], p, v;
    int rev, same;
    int size, sum, ms, ls, rs;

    void init(int _v, int _p)
    {
        s[0] = s[1] = 0, p = _p, v = _v;
        rev = same = 0;
        size = 1, sum = ms = v;
        ls = rs = max(v, 0);
    }
}tr[N];
int root, nodes[N], tt;
int w[N];

void pushup(int x)
{
    auto &u = tr[x], &l = tr[u.s[0]], &r = tr[u.s[1]];
    u.size = l.size + r.size + 1;
    u.sum = l.sum + r.sum + u.v;
    u.ls = max(l.ls, l.sum + u.v + r.ls);
    u.rs = max(r.rs, r.sum + u.v + l.rs);
    u.ms = max(max(l.ms, r.ms), l.rs + u.v + r.ls);
}

void pushdown(int x)
{
    auto &u = tr[x], &l = tr[u.s[0]], &r = tr[u.s[1]];
    if (u.same)
    {
        u.same = u.rev = 0;
        if (u.s[0]) l.same = 1, l.v = u.v, l.sum = l.v * l.size;
        if (u.s[1]) r.same = 1, r.v = u.v, r.sum = r.v * r.size;
        if (u.v > 0)
        {
            if (u.s[0]) l.ms = l.ls = l.rs = l.sum;
            if (u.s[1]) r.ms = r.ls = r.rs = r.sum;
        }
        else
        {
            if (u.s[0]) l.ms = l.v, l.ls = l.rs = 0;
            if (u.s[1]) r.ms = r.v, r.ls = r.rs = 0;
        }
    }
    else if (u.rev)
    {
        u.rev = 0, l.rev ^= 1, r.rev ^= 1;
        swap(l.ls, l.rs), swap(r.ls, r.rs);
        swap(l.s[0], l.s[1]), swap(r.s[0], r.s[1]);
    }
}

void rotate(int x)
{
    int y = tr[x].p, z = tr[y].p;
    int k = tr[y].s[1] == x;
    tr[z].s[tr[z].s[1] == y] = x, tr[x].p = z;
    tr[y].s[k] = tr[x].s[k ^ 1], tr[tr[x].s[k ^ 1]].p = y;
    tr[x].s[k ^ 1] = y, tr[y].p = x;
    pushup(y), pushup(x);
}

void splay(int x, int k)
{
    while (tr[x].p != k)
    {
        int y = tr[x].p, z = tr[y].p;
        if (z != k)
            if ((tr[y].s[1] == x) ^ (tr[z].s[1] == y)) rotate(x);
            else rotate(y);
        rotate(x);
    }
    if (!k) root = x;
}

int get_k(int k)
{
    int u = root;
    while (u)
    {
        pushdown(u);
        if (tr[tr[u].s[0]].size >= k) u = tr[u].s[0];
        else if (tr[tr[u].s[0]].size + 1 == k) return u;
        else k -= tr[tr[u].s[0]].size + 1, u = tr[u].s[1];
    }
}

int build(int l, int r, int p)
{
    int mid = l + r >> 1;
    int u = nodes[tt -- ];
    tr[u].init(w[mid], p);
    if (l < mid) tr[u].s[0] = build(l, mid - 1, u);
    if (mid < r) tr[u].s[1] = build(mid + 1, r, u);
    pushup(u);
    return u;
}

void dfs(int u)
{
    if (tr[u].s[0]) dfs(tr[u].s[0]);
    if (tr[u].s[1]) dfs(tr[u].s[1]);
    nodes[ ++ tt] = u;
}

int main()
{
    for (int i = 1; i < N; i ++ ) nodes[ ++ tt] = i;
    scanf("%d%d", &n, &m);
    tr[0].ms = w[0] = w[n + 1] = -INF;
    for (int i = 1; i <= n; i ++ ) scanf("%d", &w[i]);
    root = build(0, n + 1, 0);

    char op[20];
    while (m -- )
    {
        scanf("%s", op);
        if (!strcmp(op, "INSERT"))
        {
            int posi, tot;
            scanf("%d%d", &posi, &tot);
            for (int i = 0; i < tot; i ++ ) scanf("%d", &w[i]);
            int l = get_k(posi + 1), r = get_k(posi + 2);
            splay(l, 0), splay(r, l);
            int u = build(0, tot - 1, r);
            tr[r].s[0] = u;
            pushup(r), pushup(l);
        }
        else if (!strcmp(op, "DELETE"))
        {
            int posi, tot;
            scanf("%d%d", &posi, &tot);
            int l = get_k(posi), r = get_k(posi + tot + 1);
            splay(l, 0), splay(r, l);
            dfs(tr[r].s[0]);
            tr[r].s[0] = 0;
            pushup(r), pushup(l);
        }
        else if (!strcmp(op, "MAKE-SAME"))
        {
            int posi, tot, c;
            scanf("%d%d%d", &posi, &tot, &c);
            int l = get_k(posi), r = get_k(posi + tot + 1);
            splay(l, 0), splay(r, l);
            auto& son = tr[tr[r].s[0]];
            son.same = 1, son.v = c, son.sum = c * son.size;
            if (c > 0) son.ms = son.ls = son.rs = son.sum;
            else son.ms = c, son.ls = son.rs = 0;
            pushup(r), pushup(l);
        }
        else if (!strcmp(op, "REVERSE"))
        {
            int posi, tot;
            scanf("%d%d", &posi, &tot);
            int l = get_k(posi), r = get_k(posi + tot + 1);
            splay(l, 0), splay(r, l);
            auto& son = tr[tr[r].s[0]];
            son.rev ^= 1;
            swap(son.ls, son.rs);
            swap(son.s[0], son.s[1]);
            pushup(r), pushup(l);
        }
        else if (!strcmp(op, "GET-SUM"))
        {
            int posi, tot;
            scanf("%d%d", &posi, &tot);
            int l = get_k(posi), r = get_k(posi + tot + 1);
            splay(l, 0), splay(r, l);
            printf("%d\n", tr[tr[r].s[0]].sum);
        }
        else printf("%d\n", tr[root].ms);
    }

    return 0;
}

作者:yxc
链接:https://www.acwing.com/activity/content/code/content/479402/
来源:AcWing
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。
  • 0
    点赞
  • 6
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值