清北学堂学习笔记 第一期

Day 1

1、贪心的奇怪方法:调整法

   调整法,顾名思义,就是用别的方式进行题目的分析以及证明,例如说luogu的最大乘积。这种题目的主要分析思路为:先考虑一些简单的情况,通过简单的情况来推出一些有用的结论以及性质

2、解决棋盘类问题的有效方法:奇偶调换法

   这里举一个大水题:luogu 01迷宫,这道题显然可以记忆化直接水过去,我们通过观察可以发现如果把棋盘上行号和列号之和为奇数的点所对应的数字调换(0—>1),这时只需要求每个点所能到达的同色的点的数目就可以了

Day 1 模拟题解题报告:

T1:考前:这道题目看起来很nice~考后:垃圾dumpling,送我爆零!!!最后考试成绩130,rank29,gg。

T1是一道非常鬼畜的最小生成树问题,首先我们需要求出任意两点之间的编辑距离,然后建图(完全图),任意两点之间的边权就是编辑距离。由于这道题目中有“并”的操作,所以我们能想到并查集,同时他要求同一个集合内的任意两点距离严格小于到集合外任一点的距离,而且我们发现,所有点在同一集合中是恒成立的,这十分像树一样。于是————最小生成树就浮现了。我们跑krusker算法,跑的过程中用n^2的复杂度判断合并是否合法,如果不合法,令F[ i ]表示 i 集合合法的方案数(只有i集合内的数时)
那么F [ i并j ]=F[i]*F[ j ] (乘法原理),如果合法,在这基础上+1(多了一种方案数)。然后就结束了

T 2 由于太水了就不写了(脑残写T了3个测试点)

T3 主要核心为预处理1-m中所有数的因子(用链表存储),从而优化时间,我考试时无脑暴力TLE得60分

Day 2主要学习了一些做题的思路:

1、bitset

bitset是一种在压位优化中运用广泛的stl,他的用法有很多,代码啥的就不放了,主要讲一讲应用:

T1 JSOI2010 连通数

这道题可以先进行tarjan缩点,然后进行转移,朴素的方法就是N^3的转移(类似传递闭包的原理),用bitset可以进行快速的转移:详情请看代码

    bitset a[MXN];
    for (int k = 0; k < n; k++)
        for (int i = 0; i < n; i++)
            if (a[i][k]) a[i] |= a[k]; // Floyd transitive closure

bitset可以当作一个很大的二进制,我试过,5000000都行!(可能会爆空间),你可以把他当作一个int来用,你可以对她进行整体的与、或等运算,速度快,常数小

我们再来一道例题 :luogu CF97D

这道题显然是个模拟,但是朴素的模拟势必会浪费大量的时间,我们需要优化转移:我们用bitset来表示地图上的点,具体的表示就像状态压缩dp中的运用那样。

Day 3 数据结构刷题(劝退)班

今天上午主要刷了一波大佬眼中的数据结构水题,emmmmm(我觉得并不简单),就写写解题报告吧

1,luogu P3419,

这道题可以大胆的贪心,显然我们每次将玩具移上柜子时要选择距离下一次玩时间最长的那个,我们只需要处理出nxt数组,用堆维护地板上的玩具,重载运算符即可。

T2,题面:对一个长度<=100000的序列求前缀和得到序列s,对其求前缀和得到序列ss,单点修改原序列,单点查询SS。

解法:
清北学堂学习笔记 第一期_第1张图片

T3:题面:

清北学堂学习笔记 第一期_第2张图片`

这道题目咋一看好像只能暴力,但细细观察发现,如果我们把左括号设为1,右括号设为-1,设序列的前缀和为S[i],那么一段区间L,R合法的条件为S[R]-S[L-1]==0,并且每一项保证S[i]-S[L-1]>=0。那么我们就可以用线段树维护了。

T4 Luogu P2894 [USACO08FEB]酒店Hotel

题解:考虑用线段树维护区间内最长连续的空房间数目,由于是连续的,我们可以维护三个信息:1、以左界向右延伸最长的一段连续空房间。2 以右界向左延伸的最长的一段连续空房间。3 区间内一段最长的连续空房间。有这三个信息,我们就可以大胆的pushup了,注意pushdown的细节就可以了。

T5 Luogu P1712 [NOI2016]区间

题解:题目要求我们要选出m个区间使得存在一个点使得其被所有选择的区间覆盖,我们很自然的想到用线段树维护,每次选择一个区间就区间+1.。。。。。但怎么求最小?我们可以对所有的区间排序(从小到大),然后枚举选择的起点i,固定i,不断使终点右移,直到出现第一个合法情况(用线段树求区间最大值)。然后我们继续移动i,再次找出合法情况,最后取min。由于右移i后每个点被覆盖的次数不会增多,所以整个过程中j一直向右移,所以时间复杂度为nlogn。

这里插入一个知识点——线段树的标记永久化

什么是标记永久化?简而言之就是不需要pushup和push’down,在往下更新的过程中更新sum,到达目标区间后打个标记,可见,在目标区间以下的都没有修改,所以查询时需要携带者标记一路向下,到达目标区间后一波加上!(极其暴力!!)

废话不多,上代码

#include
#include
#include
#include
#include
#include 
#include
#define half (l+r)>>1;
using namespace std;
int n,m;
const int maxn=400000;
struct hzw
{
    int sum;
    int add;
    int lc;
    int rc;  
}t[maxn];
int a[maxn],tot;
inline void build(int s,int l,int r)
{
    if (l==r)
    {
        t[s].sum+=a[l];
        return;
    }
    int mid=half;
    t[s].lc=++tot;
    build(tot,l,mid);
    t[s].rc=++tot;
    build(tot,mid+1,r);
    t[s].sum=t[t[s].lc].sum+t[t[s].rc].sum;
}
inline void update(int s,int l,int r,int cl,int cr,int x)
{
    t[s].sum+=(cr-cl+1)*x;
    if (l==cl&&r==cr)
    {
        t[s].add+=x;
        return ;
    }
    int mid=half;
    if (cr<=mid)     update(t[s].lc,l,mid,cl,cr,x);
    else if (cl>mid) update(t[s].rc,mid+1,r,cl,cr,x);
    else {
        update(t[s].lc,l,mid,cl,mid,x);
        update(t[s].rc,mid+1,r,mid+1,cr,x);
    }
}
inline int query(int s,int l,int r,int cl,int cr,int cnt)
{
    if (l==cl&&r==cr)
    {
        return cnt*(cr-cl+1)+t[s].sum;
    }
    int mid=half;
    if (cr<=mid)     return query(t[s].lc,l,mid,cl,cr,cnt+t[s].add);
    else if (cl>mid) return query(t[s].rc,mid+1,r,cl,cr,cnt+t[s].add);
    else {
        return query(t[s].lc,l,mid,cl,mid,t[s].add+cnt)+query(t[s].rc,mid+1,r,mid+1,cr,cnt+t[s].add);
    }
}
int main() {
    int ans=0,cur;
    tot++;
    cin>>n>>m;
    for(int i=1;i<=n;i++)scanf("%d",&a[i]);
    build(1,1,n);
    for(int i=1,q,x,y;i<=m;i++)
    {   
        scanf("%d%d%d",&q,&x,&y);
        if(q==1)
        {   scanf("%d",&cur);
            update(1,1,n,x,y,cur);
        }
        else
        {   
            cout<1,1,n,x,y,0)<return 0;
}

主席树,暂时不作讲解,只发一下代码,(一定会更新)

// luogu-judger-enable-o2
#include
#include
#include
#include
#include
#include
#include
#include
#define half (l+r)>>1;
using namespace std;

const int maxn=200001;

int n,m,root[maxn],ra[maxn],a[maxn],tot;

struct hzw
{
    int lc,rc,val;
}t[maxn*21];

inline void build(int &u,int l,int r,int x)
{
    t[++tot]=t[u];
    u=tot;
    if (l==r)
    {
        t[u].val++;
        return;
    } 
    int mid=half;
    if (x<=mid) build(t[u].lc,l,mid,x);
    else build(t[u].rc,mid+1,r,x);
    t[u].val=t[t[u].lc].val+t[t[u].rc].val;
}

inline int query(int rt,int lt,int l,int r,int k)
{
    if (l==r) return l;
    int mid=half;
    int pan=t[t[rt].lc].val-t[t[lt].lc].val;
    if (pan>=k) return query(t[rt].lc,t[lt].lc,l,mid,k);
    else return query(t[rt].rc,t[lt].rc,mid+1,r,k-pan);
}

int main()
{
    cin>>n>>m;
    for (int i = 1; i <= n; i++) scanf("%d", &a[i]);
    for (int i = 1; i <= n; i++) ra[i] = a[i];
    sort(ra + 1, ra + n + 1);
    ra[0] = unique(ra + 1, ra + n + 1) - ra - 1;
    for (int i = 1; i <= n; i++) a[i] = lower_bound(ra + 1, ra + ra[0] + 1, a[i]) - ra;
    root[0]=0;
    for (int i=1;i<=n;++i)
    {
        root[i]=root[i-1];
        build(root[i],1,ra[0],a[i]);
    }
    for (int i=1,a,b,c;i<=m;++i)
    {
        scanf("%d%d%d",&a,&b,&c);
        printf("%d\n",ra[query(root[b],root[a-1],1,ra[0],c)]);
    }
    return 0;
}

Day 4 图♂论 (表白周驿东老师,超喜欢她小时候)

解题报告

T1 luogu P3623 [APIO2008]免费道路

显然,我们需要满足两个条件,第一个就是需要有K条鹅卵石路,第二个就是要保证连通性。我们不能无脑地将鹅卵石路加入到kruskal中,因为这并不能保证她的连通性。我们需要先跑一遍kruskal,优先选水泥路,剩下的就是为了维持连通性必须要连的鹅卵石,将这些连上后再次跑一遍kru,优先加鹅卵石,当达到K条时停止,加水泥边。

T2 bzoj 3714: [PA2014]Kuglarz

本题运用了差分的思想,我们令B[ i ]表示前 i 个杯子里球总数的奇偶性,利用差分的原理,如果B[ i ]与
B[ i-1]的奇偶性不同,那么球就出现在B[ i ]中,我们每次花费代价知道两点之间球总数的奇偶性,相当于两点连了一条边,只要所有点都与“0”点相连,就可以知道球在哪里,用kruskal求最小代价即可

T3 bzoj 2654: tree

这道题看起来是一道二分题,但好像没法二分,因为我们找不到一个单调的量,这时我们就要用到wqs二分了,wqs二分的作用就是如果题目给了一个选物品的限制条件,要求刚好选m个,让你最大化(最小化)权值,并且所选的物品具有附加权值越大,选的数量越少的性质(例如最小生成树,白边权值越大出现在mst中的数量就越少)于是我们可以二分一个附加权值,使得mst中恰好有need条边,最后减去附加值。

T4 Luogu P2047 [NOI2007]社交网络

这道题说实话是个现代文阅读题,不难。我们首先跑一遍floyad求出任意两点i j 的最短路和最短路数目,然后枚举k,如果dis[ i ][ j ]==dis[ i ][ k ]+dis[ k ][ j ],那么存在经过k的最短路,条数为i,k之间的最短路条数*k,j之间的最短路条数。然后》》》本题终结。

T5 luogu P4049 [JSOI2007]合金

这个题看似有三个比例,其实只有两个(因为第三个可以由前两个推出来),我们将其中一个比例设为x,另一个设为y,将她们画在坐标系上,我们发现若干种材料可以加工出她们围成的凸包中的所有点。很显然,我们选择的原料围成的一定是凸多边形(凹多边形的话至少有一个点是多余的),我们枚举两个点i,j,枚举所有的产品,如果所有的产品都在i,j的同侧,则在i,j间连接一条权值为1的边。最后跑floyad求最小环就ojbk了。

T6 P3199 [HNOI2009]最小圈

这道题题面及其繁琐,其实分析一下也就那么回事。看题面好像是二分,但是我们不知道如何check,也没有找到一个可以二分的参数。但我们可以利用01分数规划(不会)的思想,将每个边的边权减掉x,二分x,用spfa判正环来check。

T7 bzoj 2438: [中山市选2011]杀人游戏

JC只有两种结局,要么上来就被干掉,要么顺着安全查下去直到遍历完整个连通块,这道题就转化为了选择最少的点,遍历n-1条边。我们先进行tarjan缩点 ,找入度为0的点。同时还有一种情况,如果那个人虽然入度为0,但是他的出边连接的所有的人的入度都>1,这个人也不用选。

T8 NOI 2018 归程

本题要用到kruskal重构树,这是个很神奇的算法
转载个blog https://blog.csdn.net/wu_tongtong/article/details/77601523
这是这道题目就比较好写了,我们先用dij求出1到所有点的最短路(我的spfa!!),这时对于每一个询问,我们需要求出一个能到达的点中dis最小的点,我们考虑重构树,用最大生成树,根据重构树的性质,我们在重构树上倍增求出第一个海拔小于要求的祖先,他的整棵子树的所有点都满足海拔大于要求,所以用st表维护节点(按dfs序)直接o(1)答案
话说有一道毒瘤题叫 BZOJ 3545,大体思路相仿,只不过用主席树维护

还有几道小题,我就直接说思路了。

1、一个图,含有n个点,m个边,含有有向边和无向边,将所有的无向边定向,使得图没有环。
只需要拓扑一波,使无向边从拓扑序小的指向大的。
2、一张 n 个点 m 条边的有向图,求最少加几条边能使它变成一个强连通图。n; m ≤ 100000
只需要用tarjan缩点,只需要考虑入度为0的点和出度为0的点,两者数量取max(自行脑补)

你可能感兴趣的:(一个蒟蒻的OI之路)