0%

如题,已知一个数列,你需要进行下面三种操作:

1.将某区间每一个数乘上x

2.将某区间每一个数加上x

3.求出某区间每一个数的和

是一道卡了很久的题目.与更朴素的模板的差别在于乘法操作

显然需要两个懒标记tag1和tag2.tag1标记加法,tag2标记乘法

接下来考虑两个标记的嵌套关系

注意到如果加法在前:(x + a) * b + c = (x + a + c/b) * b

出现了分数.

所以应当由乘法在前

更新关系为:

void add1(llint pval){
          tag1 = (tag1 + pval) % p;
          val = (val + length() * pval) % p;
          return ;
}
void add2(llint pval){
          tag2 = (tag2 * pval) % p;
          tag1 = (tag1 * pval) % p;
          val = (val * pval) % p;
          return ;
}

整体代码:

#include <iostream>
#include <cstdio>
#include <cstdlib>
#include <cstring>
#include <string>
#include <algorithm>
#include <cmath>
#define llint long long
using namespace std;
llint p;
struct node{
        node *lson,*rson;
        llint l,r;
        llint val,tag1,tag2;
        void eq (node *a,node *b,llint c,llint d,llint e,llint f,llint g){
                lson = a;rson = b;l = c;r = d;val = e;tag1 = f;tag2 = g;
                return ;
        }
        node (llint* arr,llint ll,llint rr){
                llint mid = ((rr - ll) >> 1) + ll;
                if (ll == rr){
                        this->eq(NULL,NULL,ll,rr,arr[ll],0,1);
                        return ;
                }
                lson = new node(arr,ll,mid),rson = new node(arr,mid + 1,rr);
                this->eq(lson,rson,ll,rr,lson->val + rson->val,0,1);
        }
        llint length(){
                return r - l + 1;
        }
        void maintain(){
                if(l == r) return ;
                val = (lson->val + rson->val) % p;
                return ;
        }
        //"add" means add value to a node
        void add1(llint pval){
                tag1 = (tag1 + pval) % p;
                val = (val + length() * pval) % p;
                return ;
        }
        void add2(llint pval){
                tag2 = (tag2 * pval) % p;
                tag1 = (tag1 * pval) % p;
                val = (val * pval) % p;
                return ;
        }
        //"down" means push tags to the sons
        void down(){
                if(lson){
                        lson->add2(tag2);
                        lson->add1(tag1);
                }
                if(rson){
                        rson->add2(tag2);
                        rson->add1(tag1);
                }
                tag1 = 0;tag2 = 1;
                return ;
        }
        void oper1(llint pl,llint pr,llint pval){
                if(pl > r || pr < l) return ;
                if(pl <= l && pr >= r){
                        add1(pval);
                        return ;
                }
                down();
                lson->oper1(pl,pr,pval);
                rson->oper1(pl,pr,pval);
                maintain();
                return ;
        }
        void oper2(llint pl,llint pr,llint pval){
                if(pl > r || pr < l) return ;
                if(pl <= l && pr >= r){
                        add2(pval);
                        return ;
                }
                down();
                lson->oper2(pl,pr,pval);
                rson->oper2(pl,pr,pval);
                maintain();
                return ;
        }
        llint query(llint pl,llint pr){
                if(pl > r || pr < l) return 0;
                if(pl <= l && pr >= r)
                        return val;
                down();
                return (lson->query(pl,pr) + rson->query(pl,pr)) % p;
        }
}*root;
int main (){
        llint n,m,x,y,k,al,arr[300000];
        cin >> n >> m >> p;
        for (int i = 1;i <= n;++ i)
                scanf("%lld",&arr[i]);
        root = new node(arr,1,n);
        for (int i = 1;i <= m;++ i){
                scanf("%lld",&al);
                if(al == 1){
                        scanf("%lld%lld%lld",&x,&y,&k);
                        root->oper2(x,y,k);
                }
                if(al == 2){
                        scanf("%lld%lld%lld",&x,&y,&k);
                        root->oper1(x,y,k);
                }
                if(al == 3){
                        scanf("%lld%lld",&x,&y);
                        llint ans = root->query(x,y);
                        printf("%lld\n",ans);
                }
        }
        return 0;
}

以上

给一个01矩阵,求面积最大的只含有1的子矩形和正方形

定义

对于一个包含障碍的矩阵,从一个点出发向四周能扩展到的最大的矩形称为极大子矩形,显然极大子矩形的四条边上都至少有一个障碍物.所有极大子矩形中面积最大的成为最大子矩形.

最大子正方形

只是求最大子正方形可以设计一个DP解法

设dp(x,y)为以(x,y)为右下角的最大正方形

dp(x,y) = min(dp(x - 1,y) , dp(x,y - 1) , dp(x - 1,y - 1)) + 1

但是DP解法无法处理最大子矩形,因此需要悬线法

悬线法

悬线法是一个求极大子矩形的算法

操作方式

对于一个点(x,y) ,从此处向上划一条线直到碰到障碍,记长度为d(x,y),接着将该线向左,向右扫描,直到碰到障碍

注意: 这个扫描得到的矩形未必是极大子矩形,因为其下边界未必触及障碍.但它随后一定会成为一个极大子矩形的子矩形,不会出现遗漏

以上操作进一步抽象如下:

对第x行的数列d(x),求每一个元素d(x,y)作为前缀最小值和后缀最小值时最长的子列长度right(x,y),left(x,y).该矩形面积为d(x,y) * (right(x,y) + left(x,y) - 1)

d(x,y)的维护可以通过动态规划实现,维护复杂度为O(1),同时可以使用滚动数组优化.

那么如何实现平均O(1)维护后缀最小值数组?

后缀最小值

对于点(x,y),朴素维护其后缀最小值数组即询问它之前的每一个元素即d(x,y - k),0 < k <= y是否小于d(x,y) . 复杂度高达O(n)

注意:若一个元素d(x,j) <= d(x,y),则显而易见(x,j)的后缀最小值数组一定能成为(x,y)的后缀最小值数组的一部分,可以直接跳过这一段的询问.若d(x,j) > d(x,y),则询问到此为止,无需向前询问.

由此可以得到两个结论:

  1. 所有询问中每个元素只需肯定回答一次
  2. 每个元素发出的询问只会得到一个否定回答

询问次数最多为2*n,时间复杂度为O(1)

相关题目

USACO5.3巨大的牛棚

农夫约翰想要在他的正方形农场上建造一座正方形大牛棚。他讨厌在他的农场中砍树,想找一个能够让他在空旷无树的地方修建牛棚的地方。我们假定,他的农场划分成 N x N 的方格。输入数据中包括有树的方格的列表。你的任务是计算并输出,在他的农场中,不需要砍树却能够修建的最大正方形牛棚。牛棚的边必须和水平轴或者垂直轴平行。

EXAMPLE

考虑下面的方格,它表示农夫约翰的农场,‘.’表示没有树的方格,‘#’表示有树的方格

1 2 3 4 5 6 7 8

1 . . . . . . . .

2 . # . . . # . .

3 . . . . . . . .

4 . . . . . . . .

5 . . . . . . . .

6 . . # . . . . .

7 . . . . . . . .

8 . . . . . . . .

最大的牛棚是 5 x 5 的,可以建造在方格右下角的两个位置其中一个。

ZJOI2007棋盘制作

国际象棋是世界上最古老的博弈游戏之一,和中国的围棋、象棋以及日本的将棋同享盛名。据说国际象棋起源于易经的思想,棋盘是一个 8×8 大小的黑白相间的方阵,对应八八六十四卦,黑白对应阴阳。

而我们的主人公小Q,正是国际象棋的狂热爱好者。作为一个顶尖高手,他已不满足于普通的棋盘与规则,于是他跟他的好朋友小W决定将棋盘扩大以适应他们的新规则。

小Q找到了一张由 N×M 个正方形的格子组成的矩形纸片,每个格子被涂有黑白两种颜色之一。小Q想在这种纸中裁减一部分作为新棋盘,当然,他希望这个棋盘尽可能的大。

不过小Q还没有决定是找一个正方形的棋盘还是一个矩形的棋盘(当然,不管哪种,棋盘必须都黑白相间,即相邻的格子不同色),所以他希望可以找到最大的正方形棋盘面积和最大的矩形棋盘面积,从而决定哪个更好一些。

于是小Q找到了即将参加全国信息学竞赛的你,你能帮助他么?

for (int i = 1;i <= n;++ i){
        for (int j = 1;j <= m;++ j){
                dp[j] = (dp[j] + 1 ) *  map_[i][j];//map_表示输入地图
                if(!dp[j]){
                        l[j] = 0;
                        continue;
                }
                int k = j - 1;
                while (dp[k] >= dp[j])
                        k = k - l[k];
                l[j] = j - k;
        }
        for (int j = m;j >= 1;-- j){
                if(!dp[j]){
                        r[j] = 0;
                        continue;
                }
                int k = j + 1;
                while (dp[k] >= dp[j])
                        k = k + r[k];
                r[j] = k - j;
                ans1 = max(ans1,min(r[j] + l[j] - 1,dp[j]));//正方形
                ans2 = max(ans2,(r[j] + l[j] - 1) * dp[j]);//矩形
        }

}

其实是个背包问题

开始时没有分清费用和价值的实际意义以至于没写出来.后来思考良久后明白:

  • 费用: 一颗子树能满足的用户数量
  • 价值: 一颗子树满足的用户支付额与运费的差额
  • 最后答案应为最大的价值不小于0的费用
  • 注意是费用”恰为”的树上背包,需要设置一个”非法”状态的表示值.常常我们使用-1表示,但由于本题的价值本就存在负数,所以”非法”状态的表示不能使用-1

d(k,u,n)为节点u已有前k个儿子且需要满足n个用户时的最大价值

转移为:d(k,u,n)=max{d(k-1,u,n),d(MAX,v,n-j)+d(k-1,u,j)},0<j<MAXn_of_u

且:d(MAX,v,n-j)d(k-1,u,j)都合法

边界为d(k,u,0) = 0

最后处理到达u点的花费

d(MAX,u,n) = d(MAX,u,n) - cost_of_way_to_u

ps:以前写树上背包都是两个函数相互递归实现,空间较之递推大了一倍,于是本题就被卡空间了.所以第一次使用了递推实现树上背包

代码如下:

//by:beautyyu
#include <iostream>
#include <cstdio>
#include <string>
#include <cstring>
#include <algorithm>
#include <cmath>
#define ull unsigned long long
using namespace std;
struct edge{
    int v,nxt;
}e[3010];
const int PWP = -0x3fff;
int head[3010],num[3010],wei[3010],val[3010],dp[3010][3010];
bool deal[3010];
void count_(int rt){
    // calculate how many users a subtree have
    for (int i = head[rt];i;i = e[i].nxt){
        count_(e[i].v);
        num[rt] += num[e[i].v];
    }
    return ;
}
void d(int rt){
    dp[rt][0] = 0;
    for (int i = head[rt];i;i = e[i].nxt){
        int &v = e[i].v;
        d(v);
        for (int j = num[rt];j >= 1;-- j)
            for (int k = 1;k <= num[v] && k <= j;++ k)
                if(dp[rt][j - k] != PWP && dp[v][k] != PWP)
                    dp[rt][j] = max(dp[rt][j],dp[rt][j - k] + dp[v][k]);
    }
    for (int i = 1;i <= num[rt];++ i)
        if (dp[rt][i] != PWP)
            dp[rt][i] -= wei[rt];
    return ;
}
int main (){
    int n,m,al,be,cnt = 0;
    cin >> n >> m;
    for (int i = 0;i <= n;++ i)
        for (int j = 0;j <= m;++ j)
            dp[i][j] = PWP;
    for (int i = 1;i <= n - m;++ i){
        int k;
        scanf("%d",&k);
        for (int j = 1;j <= k;++ j){
            scanf("%d%d",&al,&be);
            wei[al] = be;
            e[++ cnt] = edge{al,head[i]};
            head[i] = cnt;
        }
    }
    for (int i = n - m + 1;i <= n;++ i){
        scanf("%d",&val[i]);
        num[i] = 1;
        dp[i][1] = val[i];
    }
    count_(1);
    //read in and first set over
    d(1); 
    for (int i = num[1];i >= 0;-- i)
        if (dp[1][i] >= 0){
            cout << i;
            return 0;
        }
    return 0;
}

五月二十日,夜.

夜幕上的星光逐渐地变得明亮,而夜幕下的灯光已然稀薄.一如既往地,绫步行在寂静的归途上.

"那个傻瓜,大概早就睡着了吧"绫看着门上漆黑的猫眼,这样想着.她悄悄地掏出钥匙,小心翼翼地放进锁孔,仔细不让老旧的门板发出刺耳的吱呀声."吵醒了她可不好呢"

"果然是睡着了呢"绫走进客厅,依然一片黑暗.然而卧室里却隐隐透出些许光亮."咦?没睡吗?很安静呢"绫稍稍有些惊讶

"唔~"卧室床上的少女躺在一堆杂乱的纸片和绸带中间,似乎是感受到了什么,缓缓地睁开了朦胧的睡眼."绫?!"看到熟悉地身影从门口走进,依困倦的睡意突然地因惊吓一扫而空."啊..我..怎么睡着了..呜~"

"果然还是睡着了嘛.."绫没有表现出太大的意外.毕竟这家伙的生物钟她也不是不清楚.绫反手关掉了刺眼的苍白的大灯,坐到依的床头,轻轻地旋开床头的夜灯.微弱地暖光照在依的脸上,似乎略显倦意."怎么不关灯呢,对精神不好的.唔..这些纸片是啥?"

"呜~只是忘了关灯啦!",依一时说不出话,支支唔唔地搪塞道."那这些纸片呢"绫笑着盯上依悄悄挪开的视线,看着她脸上的绯红,不依不饶地继续问道."那个.."依见躲不开绫的追问,脸上的羞涩又显三分"那个..是..是..是给你的礼物啦!!"

"咦?!"此时却是绫吃了一惊."今天..是520啦!520快乐啦!"依又悄悄地挪开了视线,尽管话说的理直气壮,音量却越来越小."虽然知道自己不会做礼物..但是礼物的包装盒应该还是可以的..吧..于是..就....就睡着了".依的声音越来越小,耳根渐渐地变红.尽数映在绫眼里

"诶..真是..小傻瓜啊"绫无奈地摇摇头,揉了揉依红烔烔的耳朵

时钟悄悄地越过0时,没有发出一点声音

"我喜欢你呦,依"

是521了呢

如果不考虑依赖关系环的话本题可以得到40分

具体做法就是在树上做一次背包dp.引用<背包九讲>泛化物品概念,物品本身可以视作一个weight对应value的函数.只要枚举为每件物品提供的weight后取最优即可.本题中’物品’指的就是树上的一颗子树

接着考虑依赖关系环的问题.对于一个环要么全部取走,获得全部价值和,消耗全部费用和 ; 要么一个都不取.显然这里应该tarjan缩点.因为保证每个点的入度为1,所以一个环缩点之后一定是树上的根.最后将森林上的每个根都挂向0节点,形成一颗树即可

代码如下.蒻的英语不好请见谅

//by:beautyyu
#include <iostream>
#include <cstdio>
#include <string>
#include <cstring>
#include <algorithm>
#include <cmath>
#define ull unsigned long long
using namespace std;
struct edge
{
    int v, nxt;
}e[5000];
int head[5000],memf[5000][1000],memd[5000][1000];
//'memd' -- memory for function 'd','memf' -- memory for function 'f'
int w[5000],v[5000];
int d(int al,int be);
int f(int al,int be);
//tarjan begin
int sta[5000],top = 0;bool insta[5000];//stack
int dfn[5000],low[5000],bl[5000],bcnt = 0,c = 0;
void tarjan(int k){
    dfn[k] = low[k] = ++ c;
    insta[sta[++ top] = k] = 1;
    for (int i = head[k];i;i = e[i].nxt){
        int &ev = e[i].v;
        if (!dfn[ev]){
            tarjan(ev);
            low[k] = min(low[k],low[ev]);
        }
        else if (insta[ev])
            low[k] = min(low[k],dfn[ev]);
    }
    if (low[k] == dfn[k]){
        ++ bcnt;
        do insta[sta[top]] = 0,bl[sta[top --]] = bcnt;
        while(sta[top + 1] != k);
    }
    return ;
}
//tarjan end

int main (){
    int n,m;
    //read gragh
    cin >> n >> m;
    int de[5000],be[5000],ge[5000];
    for (int i = 1;i <= n;++ i)
        scanf("%d",&de[i]);
    for (int i = 1;i <= n;++ i)
        scanf("%d",&be[i]);
    int al,cnt = 0;
    for (int i = 1;i <= n;++ i){
        scanf("%d",&al);
        ge[i] = al;
        e[++ cnt] = edge{i,head[al]};
        head[al] = cnt;
    }
    //read gragh end
    for (int i = 1;i <= n;++ i)
        if(!dfn[i]) tarjan(i);
    //build new tree
    for (int i = 1;i <= n;++ i)
        w[bl[i]] += de[i],
        v[bl[i]] += be[i];
    memset(head,0,sizeof(head));
    memset(e,0,sizeof(e));
    cnt = 0;
    bool havefather[5000];
    for (int i = 1;i <= n;++ i)
        if (bl[i] != bl[ge[i]])
            e[++ cnt] = edge{bl[i],head[bl[ge[i]]]},
            head[bl[ge[i]]] = cnt,
            havefather[bl[i]] = 1;
    n = bcnt;
    for (int i = 1;i <= n;++ i)
        if(!havefather[i])
            e[++ cnt] = edge{i,head[0]},
            head[0] = cnt;
    //build new tree end
    memset(memd,-1,sizeof(memd));
    memset(memf,-1,sizeof(memf));
    memset(memf[0],0,sizeof(memf[0]));
    cout << d(0,m);
    return 0;
}
int d(int rt,int wei){
//most value porduced by the tree 'rt' with weight'wei'
    if (memd[rt][wei] != -1) return memd[rt][wei];
    if (wei < w[rt]) return memd[rt][wei] = 0;
    return memd[rt][wei] = f(head[rt],wei - w[rt]) + v[rt];
}
int f(int i,int wei){
//most value porduced by the list begins from 'i' with weight'wei'
    if (memf[i][wei] != -1) return memf[i][wei];
    int ev = e[i].v;
    int ans = 0;
    for (int j = 0;j <= wei;++ j)
        ans = max(ans,d(ev,j) + f(e[i].nxt,wei - j));
    return memf[i][wei] = ans;
}