UESTC 数据结构

老头马桶枪!

Time Limit: 1000 MS     Memory Limit: 64 MB

众所周知,集训队的题目是非常困难的。因此,队员们在挂机AK之后,常常会玩一些游戏。

这次,率先AK的周大爷想出了一个叫老头马桶枪的游戏。

在一个小岛上,有三个物种,一共N个生物生活在一起,他们分别是老头、马桶和枪。他们之间的关系是相互克制的,就像包剪锤一样。老头克制马桶,马桶克制枪,枪又克制老头。

现在,集训队的三名成员,小周,小钱和小胡按次序(周、钱、胡)轮流给出信息,信息有两种形式:

第一种记录方式是1 X Y

,表示 X Y

是同类。

第二种记录方式是2 X Y

,表示 X Y有攻击性行为( X克制 Y

)。

当其中一人给出的信息和之前的人给出的信息矛盾时,他便输了,要请吃晚饭。那么,聪明的你能帮助他们看出谁会输呢?(最多只会有一个人输,当多条信息矛盾时,最先给出矛盾信息的人输)

Input

第一行包含两个整数N

M N是生物总个数, M是三个队员给出的信息总个数。以下 M行,每行包含一句队员的话。(依次是小周、小钱、小胡所说的话,三人轮流给出信息)。形式有 1 x y 2 x y 1N100000 1M100000 1X,YN

Output

输出一行,表示最先给出矛盾信息的人是谁。若是小周,输出1;若是小钱,输出2;若是小胡,输出3;若没有人给出矛盾信息,输出-1

Sample input and output

Sample InputSample Output
100 7
1 100 1 
2 1 2
2 2 3 
2 3 3 
1 1 3 
2 3 1 
1 5 5
1

Source

2018 UESTC Training for Data Structures

UESTC Online Judge

Copyright (C) 2012 - 2018 Ruins He(@ruinshe), Jianjin Fan(@pfctgeorge) and Yun Li(@mzry1992). Project home

Any Problem, Please Report On Issues Page.

这一题仿照poj1182的食物链https://blog.youkuaiyun.com/keepcoral/article/details/79946060

食物链循环,a->b,b->c,c->a,这里学到一种新的方法,上面写的实在有点难懂,特别是推到关系那里,真的很难想,这里从别人博客上学到了新方法。

首先,有n个动物,那么我们开辟3n个集合,x,x+n,x+2n,分别表示与x同类,被x吃的,吃x的集合,所以两个动物x和y下面有两种情况:

1 x和y是同类,那么和y同类的即等价于和x同类,吃y的等价于吃x的,被y吃的等价于被x吃的,所以这三个集合要同时合并,

             Union(x,y);
             Union(x+n,y+n);
             Union(x+2*n,y+2*n)

判断真假条件:x被y吃||x吃y,那么直接表示就是x在y+n的集合||x在y+2n的集合,这里y+n代表被y吃的集合,y+2n代表吃y的集合,所以判断条件为judge(x,y+n)&&judge(x,y+2n),

2 x吃y,那么被y吃的等价于吃x的,和y同类的等价于被x吃的,吃y的等价于x的同类,所以集合为

                Union(x,y+n);
                Union(x+n,y+2*n);
                Union(x+2*n,y);

判断真假条件:x和y同类||y吃x,所以条件为 judge(x,y)&&judge(x,y+n)(我的代码可能会有点出入,因为n和2*n我写的恰好相反的)

这里的合并是最关键的操作,下面的几道题也是一样,也就是当不知道情况的时候才可以去合并,这是题目的要求

#include<iostream>
#include<string>
#include<vector>
#include<cstring>
#include<cstdio>
#include<cmath>
#include<algorithm>
#include<set>
#include<stack>
#include<queue>
#include<string>
#include<map>
#define INF 0x3f3f3f3f
using namespace std;
typedef long long ll;
int father[300009];
int Find(int x)
{
    if(x!=father[x]) father[x]=Find(father[x]);
    return father[x];
}
int Union(int x,int y)
{
    int i=Find(x);
    int j=Find(y);
    if(i!=j) father[i]=j;
}
int judge(int x,int y)
{
    int i=Find(x);
    int j=Find(y);
    if(i!=j)//不同类
    {
        return 1;
    }
    else return 0;
}
int main()
{
    int n,m,i,j,k;
    scanf("%d%d",&n,&m);
    for(i=1;i<=3*n;i++) father[i]=i;
    for(i=1; i<=m; i++)
    {
        int op,x,y;
        scanf("%d%d%d",&op,&x,&y);
        if(op==1)//x和y是同类,所以判断不同类
        {
            if(judge(x,y+n)&&judge(x,y+2*n))
            {
                Union(x,y);
                Union(x+n,y+n);
                Union(x+2*n,y+2*n);
            }
            else
            {
                if(i%3!=0)printf("%d\n",i%3);
                else printf("3\n");
                break;
            }
        }
        else//x吃y,要判断同类或者y吃x
        {
            if(judge(x,y)&&judge(x,y+2*n))
            {
                Union(x,y+n);
                Union(x+n,y+2*n);
                Union(x+2*n,y);
            }
            else
            {
                if(i%3!=0)printf("%d\n",i%3);
                else printf("3\n");
                break;
            }
        }
    }
    if(i==m+1) printf("-1\n");
    return 0;
}

I - 不如把并查集加上个计数功能吧

Time Limit: 1000 MS     Memory Limit: 64 MB

在另一个宇宙,一个月有 N

天。多变的天气条件使得人们很恼火,终于,天气统计局产生了。它会对外发布 M 条信息,格式如下: X Y 表示第 X 天的天气和第 Y

天一样。

但民众并不满足于此,他们想知道有多少天的天气和第 X

天一样。现在,作为一个聪明的程序员,你能帮他们解决这个问题吗?

Input

第一行包含两个整数N

M N是总天数, M是信息总个数。接下来的 M行,每行一条信息。格式为 x y。然后接下来的一行是一个整数 Q,表示询问总个数接下来的 Q行,每行包含一个数字 z,表示询问有多少天的天气和第 z天一样。 1N100000 1x,y,zN

Output

对于每一个询问,输出一行,包含一个整数,表示和z

天气一样的天数。

Sample input and output

Sample InputSample Output
10 5
1 2
2 3
3 4
1 5
6 7
2
1
6
5
2

UESTC Online Judge

Copyright (C) 2012 - 2018 Ruins He(@ruinshe), Jianjin Fan(@pfctgeorge) and Yun Li(@mzry1992). Project home

Any Problem, Please Report On Issues Page.

简单并查集,加一个sum数组合并计数就可以了
#include<cstdio>
#include<cstdlib>
#include<cstring>
#include<algorithm>
#include<iostream>
using namespace std;
typedef unsigned long long ll;
#define N 100009
#define M 400090
#define C 18446744073709551611
int father[N];
int sum[N];
int n,m;
int Find(int x)
{
    if(father[x]!=x) father[x]=Find(father[x]);
    return father[x];
}
void Union(int x,int y)
{
    int i=Find(x);
    int j=Find(y);
    if(i!=j)
    {
        father[i]=j;
        sum[j]+=sum[i];
    }
}
int main()
{
    scanf("%d%d",&n,&m);
    for(int i=1;i<=n;i++)
    {
        father[i]=i;
        sum[i]=1;
    }
    while(m--)
    {
        int x,y;
        scanf("%d%d",&x,&y);
        Union(x,y);
    }

    for(int i=1;i<=n;i++) Find(i);
    scanf("%d",&m);
    while(m--)
    {
        int x;
        scanf("%d",&x);
        printf("%d\n",sum[father[x]]);
    }
}

K - 爱吃瓜的伊卡洛斯(1)

Time Limit: 1000 MS     Memory Limit: 64 MB

伊卡洛斯很爱吃西瓜。一次,他来到一个西瓜摊旁,发现水果摊有N个西瓜,但只有红色和黄色两种颜色。

伊卡洛斯很想知道知道一些信息,便于老板交谈了起来。

当老板的话的第一个字符为”A”时,老板会告诉伊卡洛斯一些信息,格式如下:

A x y 1 这句话表示第x个西瓜和第y个西瓜是同一种颜色的。

A x y 2 这句话表示第x个西瓜和第y个西瓜是不同种颜色的。

当然,为了考验伊卡洛斯有没有认真听, 老板也会时不时问伊卡洛斯一些问题,格式如下:

Q x y 这句话表示询问第x个西瓜和第y个西瓜是不是同一种颜色,如果确定为同一种颜色,伊卡洛斯需要回答1;确定为不同种颜色,伊卡洛斯需要回答2;无法确定时伊卡洛斯回答3

注意,伊卡洛斯是根据已获得的信息来回答的。也就是只有这个问题之前的信息才为已知信息。

老板说,只有回答对他全部的问题,伊卡洛斯才能吃到瓜,他聪明的想到了让你来帮助他。

Input

第一行包含两个整数NMN是西瓜总数,M是以AQ开头的老板的话总和。
以下M行,每行包含一条老板的话。形式有A x y 1A x y 2Q x y1N100000 1M200000 1X,YN数据保证没有矛盾

Output

对于每一条$Q$指令,输出1/2/3代表两个西瓜颜色的关系。

Sample input and output

Sample InputSample Output
6 9  
A 1 2 1  
A 1 3 1  
A 1 4 2  
Q 2 4  
Q 1 6  
A 3 6 1  
A 4 5 2  
Q 1 5  
Q 1 6
2
3
1 
1

Hint

西瓜的颜色有且仅有两种!



对立面的并查集,x表示同色的,x+n表示和x不同颜色的,所以如果说了不同颜色,那么Union(x,y),Union(x+n,y+n) 如果不同颜色,那么Union(x,y+n),Union(x+n,y),这里简单来说就是x和y不同颜色,那么x属于和y不同颜色的集合(y+n)这里。查询的时候,如果Find(x)==Find(y),同色;如果Find(x)=Find(y+n)||Find(y)=Find(x+n),不同色;其它不清楚
#include<cstdio>
#include<cstdlib>
#include<cstring>
#include<algorithm>
#include<iostream>
using namespace std;
typedef unsigned long long ll;
#define N 100009
#define M 400090
#define C 18446744073709551611
int father[5*N];
int sum[N];
int n,m;
int Find(int x)
{
    if(father[x]!=x) father[x]=Find(father[x]);
    return father[x];
}
void Union(int x,int y)
{
    int i=Find(x);
    int j=Find(y);
    if(i!=j)
    {
        father[i]=j;
    }
}
int main()
{
    char s[10];
    scanf("%d%d",&n,&m);
    for(int i=1; i<=2*n; i++)
    {
        father[i]=i;
    }
    while(m--)
    {
        int x,y,flag;
        scanf("%s%d%d",&s,&x,&y);
        if(s[0]=='A')
        {
            scanf("%d",&flag);
            if(flag==1)
            {
                Union(x,y);
                Union(x+n,y+n);
            }
            else if(flag==2)
            {
                Union(x,y+n);
                Union(y,x+n);
            }
        }
        else
        {
            if(Find(x)==Find(y)) printf("1\n");
            else if(Find(x)==Find(y+n)||Find(y)==Find(x+n)) printf("2\n");
            else printf("3\n");
        }
    }

}

L - 爱吃瓜的伊卡洛斯(2)

Time Limit: 1000 MS     Memory Limit: 64 MB

伊卡洛斯很爱吃西瓜。一次,他来到一个西瓜摊旁,发现水果摊有$N$个西瓜,西瓜有红色、黄色、绿色、蓝色……等等数不清的颜色。伊卡洛斯很想知道知道一些信息,便于老板交谈了起来。当老板的话的第一个字符为”A”时,老板会告诉伊卡洛斯一些信息,格式如下:$A\ x\ y\ 1$ 这句话表示第$x$个西瓜和第$y$个西瓜是同一种颜色的。$A\ x\ y\ 2$ 这句话表示第$x$个西瓜和第$y$个西瓜是不同种颜色的。

当然,为了考验伊卡洛斯有没有认真听, 老板也会时不时问伊卡洛斯一些问题,格式如下:$Q\ x\ y$ 这句话表示询问第$x$个西瓜和第$y$个西瓜是不是同一种颜色,如果确定为同一种颜色,伊卡洛斯需要回答1;确定为不同种颜色,伊卡洛斯需要回答2;无法确定时伊卡洛斯回答3。注意,伊卡洛斯是根据已获得的信息来回答的。也就是只有这个问题之前的信息才为已知信息。

老板说,只有回答对他全部的问题,伊卡洛斯才能吃到瓜,他聪明的想到了让你来帮助他。

Input

第一行包含两个整数N

M N是西瓜总数, M是以 A Q开头的老板的话总和。
以下 M行,每行包含一条老板的话。形式有 A x y 1 A x y 2 Q x y 1N100000 1M200000 1X,YN

数据保证没有矛盾

Output

对于每一条Q

指令,输出1/2/3代表两个西瓜颜色的关系。

Sample input and output

Sample InputSample Output
6 9  
A 1 2 1  
A 1 3 1  
A 1 4 2  
Q 2 4  
Q 1 6  
A 3 6 1  
A 4 5 2  
Q 1 5  
Q 1 6
2
3
3  
1

Hint

西瓜的颜色可以有无数多种!

上面一题的加强版,不能单独去对立了,因为这里不是单单只有两种颜色,如果说x和y,y和z不同颜色,那么我们只能说明他们不同颜色,不能直接证明x和z的关系。这里用到启发式合并(小的合并到大的),用stl的set表示和x不同的颜色的元素的集合,如果x和y是相同颜色,那么和x不同的颜色的,与和y不同的颜色的可以合并到一起,小的合并到大的,并且合并father并查集Union(x,y);如果x和y不同,那么set[x].insert(Find(y)),set[y].insert( Find(x)),直接找他们相同的根节点放到相对不同颜色的集合,这里就表示他们不同颜色了。查询的时候,如果Find(x)==Find(y)那么就是相同的颜色,如果不等,接着判断x的集合里面是否含有y||y的集合里面是否含有x,如果有就可以判断他们不同了,如果没有,那么就是不明情况
#include <iostream>
#include <cstdio>
#include <string>
#include <set>

using namespace std;
int father[100009];
set<int>s[100009];//代表s[x]代表和x不同的元素的集合
int Find(int x)
{
    if(x!=father[x]) father[x]=Find(father[x]);
    return father[x];
}
void Union(int i,int j)
{
    int x=Find(i);
    int y=Find(j);
    if(x!=y)
    {
        if(s[x].size()>s[y].size()) swap(s[x],s[y]);//小的合并到大的
        father[x]=y;
        for(set<int>::iterator it=s[x].begin(); it!=s[x].end(); it++)
        {
            s[y].insert(Find(*it));//找到根节点
        }
    }
}
int main()
{
    int n,m;
    scanf("%d%d",&n,&m);
    for(int i=1; i<=n; i++)
    {
        father[i]=i;
    }
    while(m--)
    {
        char c[10];
        scanf("%s",c);
        if(c[0]=='A')
        {
            int x,y,z;
            scanf("%d%d%d",&x,&y,&z);
            if(z==1)
            {
                Union(x,y);
            }
            else
            {
                x=Find(x);
                y=Find(y);
                s[x].insert(y);
                s[y].insert(x);
            }
        }
        else
        {
            int x,y;
            scanf("%d%d",&x,&y);
            x=Find(x);
            y=Find(y);
            if(x==y) printf("1\n");
            else if(s[x].count(y)==1||s[y].count(x)==1) printf("2\n");//在对应的set里面的找到了相应元素
            //证明这两个元素是不同的
            else printf("3\n");
        }
    }
}

E - 小埋的steam愿望单

Time Limit: 2000 MS     Memory Limit: 64 MB

小埋有一个steam愿望单,上面记载着她想买的游戏!现在小埋有以下 $n$ 个操作:

$1\ x\ y$ 添加一个价格为 $y$ 名字为 $x$ 的游戏加入愿望单

$2\ x$ 删除名字为 $x$ 的游戏

$3\ x\ y$ 名字为 $x$ 的游戏价格调整为 $y$

$4\ x$ $x$ 为 $1$ 输出最便宜的游戏的名字(如果有多个同价格游戏输出字典序最小的),$x$ 为 $2$ 输出最贵的游戏(如果有多个同价格游戏输出字典序最大的)

不合法的情况请忽略该操作

不合法的情况请忽略该操作

不合法的情况请忽略该操作

Input

第一行一个$n(1\le n \le 1e5)$接下来 $n$ 行 每行一个 $p$ 表示操作类型 接下来根据操作类型跟一个或两个数字( $x$ 只包括大小写字母和下划线并不超过25个字符,$1\le y\le 1e9$)

Output

对于每个查询,输出一行对应游戏的名字

Sample input and output

Sample InputSample Output
8
1 slay_the_spire 60
1 dark_soul_III 118
1 The_Binding_of_Isaac 58
1 Age_of_Empires_II 88
4 1
3 dark_soul_III 57
4 1
4 2
The_Binding_of_Isaac
dark_soul_III
Age_of_Empires_II
7
4 1
1 I 100
1 II 150
1 I 200
4 1
2 III
3 IV 600
set和map的应用,map中erase(对应值)就可以直接删除了,遍历的时候要m.erase(it++)(题外话),map<string,int>string代表名字,数字代表节点所在的位置。操作1,先找map是否已经存在这个元素,如果没有,把这个节点加入数组, 并且加入map,set并在map中记录在当前数组的位置,操作2,删除,map,set中删除这个节点,操作3,修改直接从map中读到这个节点的位置,然后在set中删除,然后再数组中修改,然后再加入set,操作4,重载,价格从小到大,相同时按字典序排行,那么set中就是价格从小到大,同时相同时字典序也是从小到大,所以第一个一定时最便宜字典序最小的,最后一个一定是最贵字典序最大的。注意判断set空的情况
#include<iostream>
#include<string>
#include<vector>
#include<cstring>
#include<cstdio>
#include<cmath>
#include<algorithm>
#include<set>
#include<stack>
#include<queue>
#include<string>
#include<map>
#define INF 0x3f3f3f3f
using namespace std;
typedef long long ll;
struct node
{
    int v;
    string name;
    friend bool operator < (node a,node b)
    {
        if(a.v==b.v) return a.name<b.name;
        else return a.v<b.v;
    }
} c[100009];
set<node>s;
map<string,int>m;
int main()
{
    int n,ans=1;
    scanf("%d",&n);
    while(n--)
    {
        int flag,v;
        string a;
        node temp;
        scanf("%d",&flag);
        if(flag==1)
        {
            cin>>a>>v;
            if(m.count(a)==0)
            {
                temp.name=a;
                temp.v=v;
                s.insert(temp);
                m[a]=ans;
                c[ans++]=temp;
            }
        }
        else if(flag==2)
        {
            cin>>a;
            if(m.count(a)==1)
            {
                s.erase(c[m[a]]);
                m.erase(a);
            }
        }
        else if(flag==3)
        {
            cin>>a>>v;
            if(m.count(a)==1)
            {
                s.erase(c[m[a]]);
                c[m[a]].v=v;
                s.insert(c[m[a]]);
            }
        }
        else
        {
            set<node>::iterator it;
            cin>>v;
            if(s.size())
            {
                if(v==1)
                {
                    it=s.begin();
                    cout<<(*it).name<<endl;
                }
                else
                {
                    it=s.end();
                    it--;
                    cout<<(*it).name<<endl;
                }
            }
        }
    }
  
}

三澄美琴是一个热爱学习的法医,今天晚上她准备下载很多很多法医论文资料。下面有三个操作

1 ti ai bi

i ti时刻加入了编号为 ai耗时为 bi

的论文进入下载队列

2 ti

ti

时刻取消队列首位的任务(如果下载队列为空就忽略该操作)

3 ti

查询在 ti

时刻队列首位的任务编号,无下载任务输出 -1

Input

第一行一个n

(1n1e5)
接下来 n行,每行 j(1j3),ti(1ti1e9,),当 j=1 时, ti 后会跟 ai(1ain), bi(1bi1e4)

Output

对于每个3号操作输出一行,输出目前队列首位的论文编号

Sample input and output

Sample InputSample Output
6
1 1 1 5
3 2
1 3 2 3
2 4
3 5
3 6
1
2
-1

Hint

样例解释

操作一:在第1秒加入了编号为1的论文,耗时5秒,将于第6秒完成

操作二:在第1秒查询,队列首位为编号1

操作三:在第3秒加入编号为2的论文下载,耗时3秒,将于第6秒完成,队列里有两个下载任务,队列首位为编号1

操作四:在第4秒移除了编号为1的论文,目前队列里有一个下载任务,队列首位为编号2

操作五:在第5秒查询,队列首位为编号2

操作六:在第6秒查询,此时2号任务刚好完成,队列为空,输出-1

单纯队列,注意一下就好了

#include<iostream>
#include<string>
#include<vector>
#include<cstring>
#include<cstdio>
#include<cmath>
#include<algorithm>
#include<set>
#include<stack>
#include<queue>
#include<string>
#include<map>
#define INF 0x3f3f3f3f
using namespace std;
typedef long long ll;
struct node
{
    int id,t;
};
queue<node>q;
int main()
{
    int m;
    scanf("%d",&m);
    while(m--)
    {
        int op,begin,t;
        node temp;
        scanf("%d",&op);
        if(op==1)
        {

           cin>>begin>>temp.id>>t;
           temp.t=begin+t;
           q.push(temp);
        }
        else if(op==2)
        {
            cin>>t;
            while(!q.empty())
            {
                if(t>=q.front().t) q.pop();
                else
                {
                    q.pop();
                    break;
                }
            }
        }
        else
        {
            cin>>t;
            while(!q.empty())
            {
                if(t>=q.front().t) q.pop();
                else
                {
                    printf("%d\n",q.front().id);
                    break;
                }
            }
            if(q.empty()) printf("-1\n");
        }
    }
}
A.单点修改+区间求和+区间最大最小值
单点修改直接把点当作区间去处理,点x为[x,x],然后修改询问最值的时候用一个全局变量去带回。
#include <iostream>
#include <cstdio>
#include <cstring>
#include <string>
#include <queue>
#include <algorithm>
#include <vector>
#include <cmath>
#include <set>
#include <map>
#define INF 0x3f3f3f3f
#define mod 10007
#define maxn 4000009
#define L rt<<1
#define R rt<<1|1
typedef long long ll;
using namespace std;
int n;
ll sum[maxn];
ll Min[maxn];
ll Max[maxn];
ll ans1,ans2;
void pushup(int rt)
{
    sum[rt]=sum[L]+sum[R];
    Max[rt]=max(Max[L],Max[R]);
    Min[rt]=min(Min[L],Min[R]);
}
ll query(int a,int b,int l,int r,int rt)
{
    ll ans=0;
    if(a<=l&&r<=b)
    {
       ans1=max(ans1,Max[rt]);
       ans2=min(ans2,Min[rt]);
       return sum[rt];
    }
    int mid=(l+r)>>1;
    if(a<=mid) ans+=query(a,b,l,mid,L);
    if(mid+1<=b) ans+=query(a,b,mid+1,r,R);
    return ans;
}
void update(int a,int b,int l,int r,int rt,int c)
{
    if(a<=l&&r<=b)
    {
        sum[rt]=c;
        Max[rt]=c;
        Min[rt]=c;
        return ;
    }
    int mid=(l+r)/2;
    if(a<=mid) update(a,b,l,mid,L,c);
    if(mid+1<=b) update(a,b,mid+1,r,R,c);
    pushup(rt);
}
int main()
{
    int m;
    scanf("%d%d",&n,&m);
    while(m--)
    {
        int a,b,op;
        ll c;
        scanf("%d%d%d",&op,&a,&b);
        if(op==0) update(a,a,1,n,1,b);
        else
        {
            ans1=-9999999999;
            ans2=INF;
            printf("%lld\n",query(a,b,1,n,1)-ans1-ans2);
        }
    }
}



B.区间修改+区间求和线段树
这里线段树的区间修改直接写就可以了,不管加数是正数还是负数,atag[]数组标记的就就是它的加数,所以下推的时候根节点atag[rt]!=0的时候才需要下推

#include <iostream>
#include <cstdio>
#include <cstring>
#include <string>
#include <queue>
#include <algorithm>
#include <vector>
#include <cmath>
#include <set>
#include <map>
#define INF 0x3f3f3f3f
#define mod 10007
#define maxn 4000009
#define L rt<<1
#define R rt<<1|1
typedef long long ll;
using namespace std;
int n;
ll sum[maxn];
ll atag[maxn];
int g[maxn];
void pushup(int rt)
{
    sum[rt]=sum[L]+sum[R];
}
void pushdown(int rt,int ln,int rn)
{
    if(atag[rt])
    {
        //下推标记
        sum[L]+=ln*atag[rt];
        sum[R]+=rn*atag[rt];
        //让左右子区间加上父节点的标记
        atag[L]+=atag[rt];
        atag[R]+=atag[rt];
        //这里应该是累加,因为标记是累加标记
        atag[rt]=0;
    }
}
void update(int a,int b,int l,int r,int rt,ll c)//[a,b]代表操作区间,就是要修改的区间,l,r表示当前节点rt表示的区间
{
    if(a<=l&&r<=b)
    {
        sum[rt]+=(r-l+1)*c;
        atag[rt]+=c;
        return ;
    }
    int mid=(l+r)>>1;
    pushdown(rt,mid-l+1,r-mid);
    if(a<=mid) update(a,b,l,mid,L,c);//如果左子区间和操作区间有重叠,那么就要递归左区间
    if(mid+1<=b) update(a,b,mid+1,r,R,c);
    pushup(rt);//左右区间更新了,同时也要更新该节点
}
ll query(int a,int b,int l,int r,int rt)
{
    ll ans=0;
    if(a<=l&&r<=b)
    {
        return sum[rt];
    }
    int mid=(l+r)>>1;
    pushdown(rt,mid-l+1,r-mid);
    if(a<=mid) ans+=query(a,b,l,mid,L);
    if(mid+1<=b) ans+=query(a,b,mid+1,r,R);
    return ans;
}
int main()
{
    int m;
    scanf("%d%d",&n,&m);
    while(m--)
    {
        int a,b,op;
        ll c;
        scanf("%d%d%d%lld",&op,&a,&b,&c);
        if(op==0) update(a,b,1,n,1,c);
        else printf("%lld\n",query(a,b,1,n,1));
    }
}
C.设 xem 表示集合中最小的未出现的正整数,如 xem{}=1xem{1,3,4}=2

定义

bi=xem{bici,bici+1,...,bi1},i=1,2,...,n

特别的,b0=1

给定 n

c1,c2,...,cn,请你计算出 b1,b2,...,bn.
https://blog.youkuaiyun.com/weixin_39302444/article/details/80470471这题解讲的很清晰
题意要理解清楚,首先解决边界条件,到b[n]的时候最大值应该是n+1,因为0-n的最小没出现过的整数,所以最大值为n+1,那么接着我们要设last[x]=,表示x出现的最后一个位置为i,所以我们要找到最小的last[j]<i-c[i]就是j最小,那么我们怎么找到最小值呢,这题目就转换为线段树求最小值,单点更新+最小单点查找。这里强调一下,单点更新最后应该是Min[rt]=x,这里应该是rt才对,因为rt所在的节点代表了当前区间[l,l],所以这就是单点更新的边界条件,然后如何查询,那么直接用Min[L]<x来判断即可,这里是优先找左边,所以最终一定找到的是最小符合的值
#include <iostream>
#include <cstdio>
#include <cstring>
#include <string>
#include <queue>
#include <algorithm>
#include <vector>
#include <cmath>
#include <set>
#include <map>
#define INF 0x3f3f3f3f
#define mod 10007
#define maxn 4000009
#define L rt<<1
#define R rt<<1|1
typedef long long ll;
using namespace std;
int n,m;
int val[maxn];
int v[maxn];
int blo[maxn];
int cnt[maxn];
vector<int>ve[1009];
map<int,int>mp;
int f[1005][1005];
int ans;
int Min[maxn];
int c[maxn];
void pushup(int rt)
{
    Min[rt]=min(Min[L],Min[R]);
}
void update(int l,int r,int rt,int pos,int x)//单点更新
{
    if(l==r)
    {
        Min[rt]=x;
        return ;
    }
    int mid=(l+r)>>1;
    if(pos<=mid) update(l,mid,L,pos,x);
    else update(mid+1,r,R,pos,x);
    pushup(rt);
}
void query(int l,int r,int rt,int x)
{
    if(l==r)
    {
        ans=l;
        return ;
    }
    int mid=(l+r)>>1;
    if(Min[L]<x) query(l,mid,L,x);
    else query(mid+1,r,R,x);
}
int main()
{
    int i;
    scanf("%d",&n);
    for(i=1;i<=n;i++)
    {
        scanf("%d",&c[i]);
    }
    memset(Min,-1,sizeof(Min));
    update(1,n+1,1,1,0);
    for(i=1;i<=n;i++)
    {
        query(1,n+1,1,i-c[i]);
        update(1,n+1,1,ans,i);
        printf("%d ",ans);
    }
}

D.一棵复杂的线段树,给n个元素a[i],对区间进行排序,有两种操作,从小到大或者从大到小,求最后第k个元素a[k].那么这道题目要去遍历一遍原来的a[i],看是否最后的结果第k个元素等于a[i],那么我们可以排序一下,然后对a[i]进行二分,对于一个a[i],用一个线段树去维护区间内大于等于a[i]的个数,也就是我们将大于等于当前二分的a[i]的原数组内所有的元素都标记为1,小于它的都标记为0,所以维护的sum[rt]就是区间内1的个数,也就是大于等于a[i]的个数,排序操作就是,如果从小到大排序,就是将小的放前面,大的放到后面,所以我们先查询[a,b]区间内有多少个1,然后对[a,b]进行排序,[a,b-num]为0,[b-num+1,b]为1这就是从小到大的排序,从大到小也是大的放前面,小的放后面就好了,这里要特判一下,如果num=0的话,那么就继续循环就好了,因为排序没有任何意义,这就是线段树更新的函数,然后最后查询,如果在第k位的值为1,那么也就是意味第k位的值应该大于等于a[i]的,所以向右查询,如果为0,那么就是第k位的值应该小于a[i]的,所以向左找,最后就可以找到答案了。

#include <iostream>
#include <cstdio>
#include <cstring>
#include <string>
#include <queue>
#include <algorithm>
#include <vector>
#include <cmath>
#include <set>
#include <map>
#define INF 0x3f3f3f3f
#define mod 10007
#define maxn 4000009
#define L rt<<1
#define R rt<<1|1
typedef long long ll;
using namespace std;
int n,m;
int v[maxn];
int atag[maxn];
int ans;
struct node
{
    int op,a,b;
}c[maxn];
int p[maxn];
int sum[maxn];
void pushup(int rt)
{
    sum[rt]=sum[L]+sum[R];
}
void pushdown(int rt,int ln,int rn)
{
    if(atag[rt]!=-1)
    {
        sum[L]=ln*atag[rt];
        sum[R]=rn*atag[rt];
        atag[L]=atag[rt];
        atag[R]=atag[rt];
        atag[rt]=-1;
    }
}
void build(int l,int r,int rt,int val)
{
    atag[rt]=-1;
    if(l==r)
    {
        if(v[l]>=val) sum[rt]=1;
        else sum[rt]=0;
        return ;
    }
    int mid=(l+r)/2;
    build(l,mid,L,val);
    build(mid+1,r,R,val);
    pushup(rt);
}
void update(int a,int b,int l,int r,int rt,int val)
{
    if(a<=l&&r<=b)
    {
        sum[rt]=val*(r-l+1);
        atag[rt]=val;
        return ;
    }
    int mid=(l+r)/2;
    pushdown(rt,mid-l+1,r-mid);
    if(a<=mid) update(a,b,l,mid,L,val);
    if(mid+1<=b) update(a,b,mid+1,r,R,val);
    pushup(rt);
}
int Q(int a,int b,int l,int r,int rt)
{
    int cnt=0;
    if(a<=l&&r<=b)
    {
        return sum[rt];
    }
    int mid=(l+r)>>1;
    pushdown(rt,mid-l+1,r-mid);
    if(a<=mid) cnt+=Q(a,b,l,mid,L);
    if(mid+1<=b) cnt+=Q(a,b,mid+1,r,R);
    return cnt;
}
void query(int l,int r,int rt,int pos)
{
    if(l==r)
    {
        ans=sum[rt];
        return;
    }
    int mid=(l+r)/2;
    pushdown(rt,mid-l+1,r-mid);
    if(pos<=mid) query(l,mid,L,pos);
    else query(mid+1,r,R,pos);
}
int main()
{
    int k,i;
    scanf("%d%d",&n,&k);
    for(i=1;i<=n;i++)
    {
        scanf("%d",&v[i]);
        p[i]=v[i];
    }
    scanf("%d",&m);
    for(i=1;i<=m;i++)
    {
        scanf("%d%d%d",&c[i].op,&c[i].a,&c[i].b);
    }
    sort(p+1,p+n+1);
    int l=1,r=n,mid;
    while(l<=r)
    {
        mid=(l+r)/2;
        build(1,n,1,p[mid]);
        for(i=1;i<=m;i++)
        {
            int num=Q(c[i].a,c[i].b,1,n,1);
            if(num==0) continue;
            if(!c[i].op)
            {
                update(c[i].a,c[i].b,1,n,1,0);
                update(c[i].b-num+1,c[i].b,1,n,1,1);
            }
            else
            {
                update(c[i].a,c[i].b,1,n,1,0);
                update(c[i].a,c[i].a+num-1,1,n,1,1);
            }
        }
        query(1,n,1,k);
        if(ans) l=mid+1;
        else r=mid-1;
    }
    printf("%d\n",p[l-1]);
}

H中堂系的困难任务,哈夫曼树dp递推式,看不出来...不过用优先队列来用最基本的贪心思想去码还是比较简单的

哈夫曼树
#include <iostream>
#include <cstdio>
#include <cstring>
#include <string>
#include <queue>
#include <algorithm>
#include <vector>
#include <cmath>
#include <set>
#include <map>
#define INF 0x3f3f3f3f
#define mod 10007
#define maxn 4000009
#define L rt<<1
#define R rt<<1|1
typedef long long ll;
using namespace std;
int n,m;
int v[maxn];
int atag[maxn];
int ans;
ll a;
priority_queue<ll,vector<ll>,greater<ll> >q;
int main()
{
    int t;
    scanf("%d",&t);
    while(t--)
    {
        scanf("%d",&n);
        for(int i=1; i<=n; i++)
        {
            scanf("%lld",&a);
            q.push(a);
        }
        ll sum=0;
        for(int i=1; i<=n-1; i++)
        {
            int temp=0;
            temp+=q.top();
            q.pop();
            temp+=q.top();
            q.pop();
            q.push(temp);
            sum+=temp;
        }
        printf("%lld\n",sum);
        while(!q.empty()) q.pop();
    }
}

O.帆宝RMQ 区间加法+找到x的左右端点,就和hwzer的分块入门一样,直接对块排序,然后对每个块都找一遍lower_bound(),脑残码歪了分块,疯狂wa test1,原因是lo[a]没有*m...可能multiset的复杂度太高了...TLE,有时间再去研究研究,最后以这道题为uestc数据结构专题的结尾吧,还有几题以后再补了,现在最主要是考试了....

#include <iostream>
#include <cstdio>
#include <cstring>
#include <string>
#include <queue>
#include <algorithm>
#include <vector>
#include <cmath>
#include <set>
#include <map>
#define INF 0x3f3f3f3f
#define mod 10007
#define maxn 100099
#define L rt<<1
#define R rt<<1|1
typedef long long ll;
using namespace std;
int n,m;
struct node
{
    int id;
    int x;
} v[maxn];
int blo[maxn];
ll atag[maxn];
vector<node>ve[maxn];
bool cmp(const node &a,const node &b)
{
    if(a.x==b.x) return a.id<b.id;
    else return a.x<b.x;
}
void reset(int x)
{
    ve[x].clear();
    for(int i=(x-1)*m+1; i<=min(n,x*m); i++)
    {
        ve[x].push_back(v[i]);
    }
    sort(ve[x].begin(),ve[x].end(),cmp);
}
int query(int c)
{
    int Min=INF;
    int Max=-1;
    for(int i=1; i<=blo[n]; i++)
    {
        node temp;
        temp.id=0;
        temp.x=c-atag[i];
        vector<node>::iterator it;
        it=lower_bound(ve[i].begin(),ve[i].end(),temp,cmp);
        if(it==ve[i].end()) continue;
        else
        {
            while(it!=ve[i].end())
            {
                if((*it).x==temp.x)
                {
                    Min=min(Min,(*it).id);
                    Max=max(Max,(*it).id);
                }
                else break;
                it++;
            }
        }
    }
    if(Min==INF||Max==-1) return -1;
    else
    {
        return Max-Min;
    }
}
void add(int a,int b,int c)
{
    int i;
    for(i=a; i<=min(b,blo[a]*m); i++)
    {
        v[i].x+=c;
    }
    reset(blo[a]);
    if(blo[a]!=blo[b])
    {
        for(i=(blo[b]-1)*m+1; i<=b; i++)
        {
            v[i].x+=c;
        }
        reset(blo[b]);
    }
    for(i=blo[a]+1; i<=blo[b]-1; i++)
    {
        atag[i]+=c;
    }
}
int main()
{
    int i,k;
    scanf("%d%d",&n,&k);
    m=sqrt(n);
    for(i=1; i<=n; i++)
    {
        scanf("%lld",&v[i].x);
        v[i].id=i;
        blo[i]=(i-1)/m+1;
        ve[blo[i]].push_back(v[i]);
    }
    for(i=1; i<=blo[n]; i++) reset(i);
    for(i=1; i<=k; i++)
    {
        int op,a,b;
        ll c;
        scanf("%d",&op);
        if(op==1)
        {
            scanf("%d%d%d",&a,&b,&c);
            add(a,b,c);
        }
        else
        {
            scanf("%d",&c);
            printf("%d\n",query(c));
        }
    }
}





评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值