In mathematics you don't understand things, you just get used to them.

标签 data structures 下的文章

11.P3203 [HNOI2010]弹飞绵羊

某天,$Lostmonkey$ 发明了一种超级弹力装置,为了在他的绵羊朋友面前显摆,他邀请小绵羊一起玩个游戏。

游戏一开始,$Lostmonkey$ 在地上沿着一条直线摆上 $n$ 个装置,每个装置设定初始弹力系数 $k_i$,当绵羊达到第 $i$ 个装置时,它会往后弹 $k_i$ 步,达到第 $i+k_i$ 个装置,若不存在第 $i+k_i$ 个装置,则绵羊被弹飞。

绵羊想知道当它从第 $i$ 个装置起步时,被弹几次后会被弹飞。为了使得游戏更有趣,$Lostmonkey$ 可以修改某个弹力装置的弹力系数,任何时候弹力系数均为正整数。


每个弹力装置只会对应一个位置可以弹到,也就是说我们可以把它看作一条边,而且不会有环这种**玩意。

对于修改弹力系数,我们可以用断边连边来维护

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

using namespace std;

const int SIZE = 2e5 + 5;
class LinkCutTree {
public:
    struct SPLAY {
        int val;
        int fa;
        int ch[2];
    } T[SIZE];
    inline bool isroot(int x) { return !(T[T[x].fa].ch[0] ^ x && T[T[x].fa].ch[1] ^ x); }
    inline void push(int x) { T[x].val = T[T[x].ch[0]].val + T[T[x].ch[1]].val + 1; }
    inline void rotate(int x) { int y = T[x].fa, z = T[y].fa, k = T[y].ch[1] == x, w = T[x].ch[!k]; (isroot(y)) && (T[z].ch[T[z].ch[1] == y] = x); T[x].ch[!k] = y, T[y].ch[k] = w; (w) && (T[w].fa = y); T[y].fa = x; T[x].fa = z; push(y); }
    inline void splay(int x) { for(; isroot(x); rotate(x)) { int y = T[x].fa, z = T[y].fa; (isroot(y)) && (rotate(T[y].ch[1] ^ x ^ T[z].ch[1] ^ y ?  x : y), 1); } push(x); }
    inline void access(int x) { for(int y = 0; x; x = T[y = x].fa) splay(x), T[x].ch[1] = y, push(x); }
} lct_mast;
int n, m;

signed main() {
    scanf("%d", &n);
    for(int i = 1, s; i <= n; ++i) {
        lct_mast.T[i].val = 1;
        scanf("%d", &s);
        (i + s <= n) && (lct_mast.T[i].fa = i + s);
    }
    for(scanf("%d", &m); m; --m) {
        int opt, x, y;
        scanf("%d", &opt), scanf("%d", &x);
        if (opt ^ 2) {
            lct_mast.access(x + 1);
            lct_mast.splay(x + 1);
            printf("%d\n", lct_mast.T[x + 1].val);
        } else {
            scanf("%d", &y);
            lct_mast.access(x + 1);
            lct_mast.splay(x + 1);
            lct_mast.T[x + 1].ch[0] = lct_mast.T[lct_mast.T[x + 1].ch[0]].fa = 0;
            (x + y + 1 <= n) && (lct_mast.T[x + 1].fa = x + y + 1);
            lct_mast.push(x + 1);
        }
    }
    return 0;
}

12.SP4487 GSS6 - Can you answer these queries VI

The first line of the input contains an integer N.
The following line contains N integers, representing the starting
sequence A1..AN, _(|Ai| <= 10000)_.

The third line contains an integer Q. The next Q lines contains the operations in following form:

I x y: insert element y at position x _(between x - 1 and x)_.
D x : delete the element at position x.
R x y: replace element at position x with y.
Q x y: print max{Ai + Ai+1 + .. + Aj | x <= i <= j <= y}.

All given positions are valid, and given values are between -10000 and +10000.

The sequence will never be empty.


这道题显然是一道平衡树的裸题,唯一的难度就是求最大子段和。

可以类比线段树维护最大子段和,维护$lmax$以x为根的前缀最大和、$rmax$以x为根的后缀最大和、$maxsum$最大子段和以及$sum$总和

对于$Update$我们可以对是否有左右孩子做讨论。

#include <cstdio>
#include <iostream>
#include <algorithm>
#include <queue>
#include <utility>

using namespace std;

const int SIZE = 2e5 + 5;
int n, m, tot, root, a[SIZE];
struct SPLAY {
    int fa;
    int ch[2];
    int siz;
    int val;
    int sum;
    int lmax;
    int rmax;
    int maxsum;
} T[SIZE];
vector < int > st;

inline int newnode(int v = 0) {
    int x;
    if (st.empty()) x = ++tot;
    else x = st.back(), st.pop_back();
    T[x].fa = T[x].ch[0] = T[x].ch[1] = 0;
    T[x].siz = 1;
    T[x].val = T[x].sum = T[x].maxsum = v;
    T[x].lmax = T[x].rmax = max(v, 0);
    return x;
}

inline void delnode(int x) {
    T[x].fa = T[x].ch[0] = T[x].ch[1] = 0;
    T[x].sum = T[x].lmax = T[x].rmax = T[x].maxsum = 0;
    T[x].siz = 1;
    st.push_back(x);
}

inline bool which(int x) {
    if (T[T[x].fa].ch[0] == x) return 0;
    if (T[T[x].fa].ch[1] == x) return 1;
    return -1;
}

inline void update(int x) {
    T[x].sum = T[x].val; T[x].siz = 1;
    if (T[x].ch[0]) T[x].sum += T[T[x].ch[0]].sum, T[x].siz += T[T[x].ch[0]].siz;
    if (T[x].ch[1]) T[x].sum += T[T[x].ch[1]].sum, T[x].siz += T[T[x].ch[1]].siz;
    if (T[x].ch[0] && T[x].ch[1]) {
        T[x].lmax = max(T[T[x].ch[0]].lmax, T[T[x].ch[0]].sum + T[x].val + T[T[x].ch[1]].lmax);
        T[x].rmax = max(T[T[x].ch[1]].rmax, T[T[x].ch[1]].sum + T[x].val + T[T[x].ch[0]].rmax);
        T[x].maxsum = max({T[T[x].ch[0]].maxsum, T[T[x].ch[1]].maxsum, T[T[x].ch[0]].rmax + T[x].val + T[T[x].ch[1]].lmax});
    } else if (T[x].ch[0]) {
        T[x].lmax = max({T[T[x].ch[0]].lmax, T[T[x].ch[0]].sum + T[x].val, 0});
        T[x].rmax = max(T[x].val + T[T[x].ch[0]].rmax, 0);
        T[x].maxsum = max(T[T[x].ch[0]].maxsum, T[T[x].ch[0]].rmax + T[x].val);
    } else if (T[x].ch[1]) {
        T[x].lmax = max(T[x].val + T[T[x].ch[1]].lmax, 0);
        T[x].rmax = max({T[T[x].ch[1]].rmax, T[T[x].ch[1]].sum + T[x].val, 0});
        T[x].maxsum = max(T[T[x].ch[1]].maxsum, T[T[x].ch[1]].lmax + T[x].val);
    } else {
        T[x].maxsum = T[x].val;
        T[x].lmax = T[x].rmax = max(T[x].val, 0);
    }
}

inline void rotate(int x) {
    if (!x) return;
    int w = which(x), y = T[x].fa;
    if (~which(y)) T[T[y].fa].ch[which(y)] = x;
    T[x].fa = T[y].fa;
    T[y].ch[w] = T[x].ch[w ^ 1];
    if (T[x].ch[w ^ 1]) T[T[x].ch[w ^ 1]].fa = y;
    T[x].ch[w ^ 1] = y;
    T[y].fa = x;
    update(y), update(x);
}
inline void splay(int x, int &goal) {
    if (x == goal) return;
    int p = T[goal].fa;
    for (int y; T[x].fa ^ p; rotate(x)) y = T[x].fa, (T[y].fa ^ p) && (rotate(which(y) ^ which(x) ? x : y), 1);
    goal = x;
}

inline int kth_element(int x, int k) {
    while (233) {
        if (T[x].ch[0] && k <= T[T[x].ch[0]].siz) x = T[x].ch[0];
        else {
            if (T[x].ch[0]) k -= T[T[x].ch[0]].siz;
            if (!--k) return x;
            x = T[x].ch[1];
        }
    }
}

inline void insert(int &rt, int p, int val) {
    int x = kth_element(rt, p);
    splay(x, rt);
    int y = kth_element(rt, p + 1);
    splay(y, T[rt].ch[1]);
    T[y].ch[0] = newnode(val);
    T[T[y].ch[0]].fa = y;
    update(T[y].ch[0]);
    update(y), update(x);
}

inline void erase(int &rt, int p) {
    int y = kth_element(rt, p);
    splay(y, rt);
    int x = kth_element(rt, p + 1);
    splay(x, T[rt].ch[1]);
    int z = T[x].ch[1];
    T[z].fa = y;
    T[y].ch[1] = z;
    delnode(x);
    update(y);
}

inline void modify(int &rt, int p, int val) {
    int x = kth_element(rt, p + 1);
    splay(x, rt);
    T[x].val = val;
    update(x);
}

inline int find(int &rt, int l, int r) {
    int x = kth_element(rt, l);
    splay(x, rt);
    int y = kth_element(rt, r + 2);
    splay(y, T[rt].ch[1]);
    return T[T[y].ch[0]].maxsum;
}

inline void make(int p, int l, int r) {
    int mid = (l + r) >> 1;
    T[p].val = a[mid];
    if (mid - 1 >= l) T[p].ch[0] = newnode(), T[T[p].ch[0]].fa = p, make(T[p].ch[0], l, mid - 1);
    if (mid + 1 <= r) T[p].ch[1] = newnode(), T[T[p].ch[1]].fa = p, make(T[p].ch[1], mid + 1, r);
    update(p);
}

signed main() {
    scanf("%d", &n);
    for (int i = 1; i <= n; ++i) scanf("%d", &a[i]);
    int root = newnode();
    make(root, 0, n + 1);
    scanf("%d", &m);
    for (int i = 1; i <= m; ++i) {
        char opt[2];
        int x, y;
        scanf("%s %d", opt, &x);
        if (*opt ^ 'D') scanf("%d", &y);
        if (*opt == 'I') insert(root, x, y);
        if (*opt == 'D') erase(root, x);
        if (*opt == 'R') modify(root, x, y);
        if (*opt == 'Q') printf("%d\n", find(root, x, y));
    }
    return 0;
}

13.Count on a tree

给定一棵 $n$ 个节点的树,每个点有一个权值。有 $m$ 个询问,每次给你 $u$,$v$,$k$ 你需要回答 $u \text{ xor last}$ 和 $v$ 这两个节点间第 $k$ 小的点权。


LYC

树上的路径问题,我们一般都要用树链剖分。

静态第$k$小的问题,我们一般用主席树。

经过严谨分析,我们得出,这道题是树剖加主席树。

把树剖成重链之后像主席树模板那样按$dfs$序插入每个数(让同一条重链在主席树的$root$数组中成为一个连续的区间,方便统计),然后我们要查询$u$,$v$两个节点间的第$k$小。

和模板不一样的地方来了:这里的区间第$k$小的区间不连续。

我们回想模板的思想过程:

通过差分得出区间内每个数值出现的个数。

既然这次区间是不连续的,我们就每次都把这些不连续的子区间统计一遍,再加起来就好了,就相当于是把这些子区间合起来。

具体来说,就是树剖$LCA$时记录每个区间的左右端点。使用主席树查询时再把原来的对于一个区间的统计改为对很多区间的统计就好了。

剩下的就和模板一样了。

代码:

#include<cstdio>
#include<vector>
#include<algorithm>
using namespace std;
int n,m,p,a[100010],last,u,v,w,fa[100010],dep[100010],son[100010],siz[100010],dfn[100010],ton[100010],hb[100010],tot,be[100010],en[100010],cnt,root[100010],cntot;
struct node
{
    int l,r,sum;
}nodes[4000010];
vector<int> e[100010],pri;
int getID(int val)
{
    return lower_bound(pri.begin(),pri.end(),val)-pri.begin()+1;
}//离散化
void dfs1(int x,int las)
{
    dep[x]=dep[las]+1;
    fa[x]=las;
    siz[x]=1;
    int b=-1e9,s=0;
    for(int i=0;i<e[x].size();++i)
    {
        int y=e[x][i];
        if(y^las)
        {
            dfs1(y,x);
            siz[x]+=siz[y];
            if(siz[y]>b)
            {
                b=siz[y];
                s=y;
            }
        }
    }
    son[x]=s;
}
void dfs2(int x,int las,int heavy)
{
    if(heavy)    hb[x]=hb[las];
    else    hb[x]=x;
    dfn[x]=++tot;
    ton[tot]=a[x];
    if(son[x])    dfs2(son[x],x,1);
    for(int i=0;i<e[x].size();++i)
    {
        int y=e[x][i];
        if(y^las&&y^son[x])    dfs2(y,x,0);
    }
}
void LCA(int x,int y)
{
    int fx=hb[x],fy=hb[y];
    while(fx^fy)
    {
        if(dep[fx]<dep[fy])
        {
            swap(fx,fy);
            swap(x,y);
        }
        be[++cnt]=root[dfn[fx]-1];
        en[cnt]=root[dfn[x]];//记录每一个区间的左端点-1和右端点,相当于原来find(int l,int r,int p1,int p2,int k)中的p1,p2。
        x=fa[fx];
        fx=hb[x];
    }
    be[++cnt]=root[min(dfn[x],dfn[y])-1];
    en[cnt]=root[max(dfn[x],dfn[y])];
    //所有的子区间中的数包含且仅包含了u到v的最短路径上的所有点。因为树剖LCA会用一个一个区间覆盖所有点,而我们记录了每一个区间。
}//树剖
void ins(int l,int r,int pre,int &now,int pos)
{
    nodes[++cntot]=nodes[pre];
    now=cntot;
    ++nodes[now].sum;
    if(l==r)    return;
    int mid=(l+r)>>1;
    if(pos<=mid)    ins(l,mid,nodes[pre].l,nodes[now].l,pos);
    else    ins(mid+1,r,nodes[pre].r,nodes[now].r,pos);
}
int find(int l,int r,int k)
{
    if(l==r)    return pri[l-1];//一直到确定第k小的位置;返回原值。
    int X=0;
    for(int i=1;i<=cnt;++i)    X+=(nodes[nodes[en[i]].l].sum-nodes[nodes[be[i]].l].sum);
    //临时统计u到v的最短路径上的所有点权中在[l,mid]中的数的个数。
    int mid=(l+r)>>1;
    if(k<=X)//k<=X,说明第k小在左边
    {
        for(int i=1;i<=cnt;++i)//把每个端点都往左下跳。使它代表的值为这个区间的元素值在[l,mid]的个数。
        {
            en[i]=nodes[en[i]].l;
            be[i]=nodes[be[i]].l;
        }
        //然后我们就可以去缩小范围,去查询[l,mid]这个区间了。
        return find(l,mid,k);
    }
    else//否则,第k小是右边的第k-X小
    {
        for(int i=1;i<=cnt;++i)//把每个端点都往右下跳。道理同上。
        {
            en[i]=nodes[en[i]].r;
            be[i]=nodes[be[i]].r;
        }
        return find(mid+1,r,k-X);//继续查找
    }
}//主席树
int main()
{
    scanf("%d %d",&n,&m);
    for(int i=1;i<=n;++i)
    {
        scanf("%d",&a[i]);
        pri.push_back(a[i]);
    }
    for(int i=1;i<n;++i)
    {
        scanf("%d %d",&u,&v);
        e[u].push_back(v);
        e[v].push_back(u);
    }
    sort(pri.begin(),pri.end());
    pri.erase(unique(pri.begin(),pri.end()),pri.end());
    dfs1(1,1); 
    dfs2(1,1,0);
    p=pri.size();
    for(int i=1;i<=n;++i)    ins(1,p,root[i-1],root[i],getID(ton[i]));//按dfs序插入
    for(int i=1;i<=m;++i)
    {
        scanf("%d %d %d",&u,&v,&w);
        u^=last;
        cnt=0;
        LCA(u,v);
        last=find(1,p,w);
        printf("%d\n",last);
    }
    return 0;
}

WGY

对于每一个节点$x$,先令$rt_x=rt_{x-1}$,然后在$rt_x$中插入$a_i$,这样其实每个节点维护的都是节点到根的信息

对于查询操作中的每一个$(x,y)$,我们可以用$rt_x+rt_y-rt_{lca_{x,y}}-rt_{fa_{lca_{x,y}}}$来得到答案

#include <cstdio>
#include <iostream>
#include <algorithm>
#include <cstring>
#include <queue>
#define mid ((l + r) >> 1)

const int SIZE = 1e5 + 5;
struct TreeNode {
    int l, r;
    int size;
} fhq[SIZE << 5];
int n, m, tot, fa[SIZE], rt[SIZE];
int a[SIZE], rnk[SIZE], dp[SIZE], inq[30][SIZE], lastans, Q[SIZE];
std::vector < std::vector < int > > G(SIZE);

inline bool cmp(int x, int y) { return a[x] < a[y]; }

inline void newnode(int t, int p) {
    fhq[++tot] = fhq[rt[t]];
    rt[t] = tot;
    int u = rt[t], l = 1, r = n;
    while (l ^ r) {
        fhq[u].size++;
        if (p <= mid) fhq[++tot] = fhq[fhq[u].l], fhq[u].l = tot, u = fhq[u].l, r = mid;
        else fhq[++tot] = fhq[fhq[u].r], fhq[u].r = tot, u = fhq[u].r, l = mid + 1;
    }
    fhq[u].size++;
}

inline int find(int a, int b, int c, int d, int l, int r, int u) {
    if (l ^ r)
        if (fhq[fhq[a].l].size + fhq[fhq[b].l].size - fhq[fhq[c].l].size - fhq[fhq[d].l].size >= u) return find(fhq[a].l, fhq[b].l, fhq[c].l, fhq[d].l, l, mid, u);
        else return find(fhq[a].r, fhq[b].r, fhq[c].r, fhq[d].r, mid + 1, r, u - (fhq[fhq[a].l].size + fhq[fhq[b].l].size - fhq[fhq[c].l].size - fhq[fhq[d].l].size));
    else return l;
}

inline void dfs(int x, int fa) {
    dp[x] = dp[fa] + 1;
    rt[x] = rt[fa];
    ::fa[x] = fa;
    newnode(x, a[x]);
    for (int i = 0; i < (int)G[x].size(); ++i) if (G[x][i] ^ fa) dfs(G[x][i], x);
}
inline int lca_mast(int x, int y) {
    if (dp[x] < dp[y]) std::swap(x, y);
    for (int i = 0; dp[x] - dp[y]; ++i) if ((1 << i) & (dp[x] - dp[y])) x = inq[i][x];
    if (x ^ y) { for (int i = 25; i >= 0; --i) if (inq[i][x] ^ inq[i][y]) x = inq[i][x], y = inq[i][y]; return fa[x]; }
    else return x;
}

signed main() {
    scanf("%d %d", &n, &m);
    for (int i = 1; i <= n; ++i) scanf("%d", &a[i]), Q[i] = i;
    std::sort(Q + 1, Q + 1 + n, cmp);
    for (int i = 1; i <= n; ++i) rnk[i] = a[Q[i]], a[Q[i]] = i;
    for (int i = 1, x, y; i < n; ++i) scanf("%d %d", &x, &y), G[x].push_back(y), G[y].push_back(x);
    dfs(1, 0);
    for (int i = 1; i <= n; ++i) inq[0][i] = fa[i];
    for (int i = 1; i < 26; ++i) for (int j = 1; j <= n; ++j) inq[i][j] = inq[i - 1][inq[i - 1][j]];
    for (int i = 1; i <= m; ++i) {
        int x, y, z;
        scanf("%d %d %d", &x, &y, &z);
        x ^= lastans;
        int lca = lca_mast(x, y);
        printf("%d\n", lastans = rnk[find(rt[x], rt[y], rt[lca], rt[fa[lca]], 1, n, z)]);
    }
    return 0;
}

14.P2486 [SDOI2011]染色

spfa together


WGY

可以把首先把连接不同色点的边权设置为1,同色的设为9,这样整个问题就变成了查询路径上的权值和。

显然,我们可以用暴力$LinkCutTree$或$TreeChainSplitting$来搞,这里给出$LCT$的做法。

维护每一个$Splay$节点的最左端点的值和最右端点的颜色,对于它的老汉节点,我们可以找出它的前驱和后继的颜色。这样就可以累计它和前驱和后继连边的权值和辣

#include <cstdio>
#include <iostream>
#include <algorithm>
#include <cstring>
#include <queue>
#define lson (fhq[x].ch[0])
#define rson (fhq[x].ch[1])

using namespace std;


#define DEBUG 1 // debug toggle
struct IO {
#define MAXSIZE (1 << 20)
#define isdigit(x) (x >= '0' && x <= '9')
  char buf[MAXSIZE], *p1, *p2;
  char pbuf[MAXSIZE], *pp;
#if DEBUG
#else
  IO() : p1(buf), p2(buf), pp(pbuf) {}
  ~IO() { fwrite(pbuf, 1, pp - pbuf, stdout); }
#endif
  inline char gc() {
#if DEBUG
    return getchar();
#endif
    if (p1 == p2) p2 = (p1 = buf) + fread(buf, 1, MAXSIZE, stdin);
    return p1 == p2 ? ' ' : *p1++;
  }
  inline bool blank(char ch) {
    return ch == ' ' || ch == '\n' || ch == '\r' || ch == '\t';
  }
  template <class T>
  inline void read(T &x) {
    register double tmp = 1;
    register bool sign = 0;
    x = 0;
    register char ch = gc();
    for (; !isdigit(ch); ch = gc())
      if (ch == '-') sign = 1;
    for (; isdigit(ch); ch = gc()) x = x * 10 + (ch - '0');
    if (ch == '.')
      for (ch = gc(); isdigit(ch); ch = gc())
        tmp /= 10.0, x += tmp * (ch - '0');
    if (sign) x = -x;
  }
  inline void read(char *s) {
    register char ch = gc();
    for (; blank(ch); ch = gc())
      ;
    for (; !blank(ch); ch = gc()) *s++ = ch;
    *s = 0;
  }
  inline void read(char &c) {
    for (c = gc(); blank(c); c = gc())
      ;
  }
  inline void push(const char &c) {
#if DEBUG
    putchar(c);
#else
    if (pp - pbuf == MAXSIZE) fwrite(pbuf, 1, MAXSIZE, stdout), pp = pbuf;
    *pp++ = c;
#endif
  }
  template <class T>
  inline void write(T x) {
    if (x < 0) x = -x, push('-');
    static T sta[35];
    T top = 0;
    do {
      sta[top++] = x % 10, x /= 10;
    } while (x);
    while (top) push(sta[--top] + '0');
  }
  template <class T>
  inline void write(T x, char lastChar) {
    write(x), push(lastChar);
  }
} io;

const int SIZE = 1e6 + 5;
int n, m;
class LinkCutTree {
public:
    struct SPLAY {
        int w, c;
        int l, r;
        int tag, rev;
        int fa, ch[2];
    } fhq[SIZE]; // The fhq-treap replaces the splay
    // Struct SPLAY
    int stack[SIZE], tp;

    inline bool isroot(int x) { return fhq[fhq[x].fa].ch[0] ^ x && fhq[fhq[x].fa].ch[1] ^ x; }
    inline void tr_dn1(int x) { if (fhq[x].rev) swap(lson, rson), swap(fhq[lson].l, fhq[lson].r), swap(fhq[rson].l, fhq[rson].r), fhq[lson].rev ^= 1, fhq[rson].rev ^= 1, fhq[x].rev = 0; }
    inline void tr_dn2(int x) { if (fhq[x].tag) fhq[x].l = fhq[x].r = fhq[x].c = fhq[x].tag, fhq[lson].tag = fhq[rson].tag = fhq[x].tag, fhq[x].w = fhq[x].tag = 0; }
    inline void tr_dn(int x) { tr_dn1(x), tr_dn2(x); }
    inline void tr_up_(int x) { tr_dn(lson), tr_dn(rson); fhq[x].w = fhq[lson].w + fhq[rson].w; }
    inline void tr_up1(int x) { if (lson) fhq[x].l = fhq[lson].l, ((fhq[x].c ^ fhq[lson].r) && (++fhq[x].w, 1)); else fhq[x].l = fhq[x].c; }
    inline void tr_up2(int x) { if (rson) fhq[x].r = fhq[rson].r, ((fhq[x].c ^ fhq[rson].l) && (++fhq[x].w, 1)); else fhq[x].r = fhq[x].c; }
    inline void tr_up(int x) { tr_up_(x); tr_up1(x); tr_up2(x); }
    inline void rotate(int x) { int y = fhq[x].fa, z = fhq[y].fa, k = fhq[y].ch[1] == x; if (!isroot(y)) fhq[z].ch[fhq[z].ch[1] == y] = x; fhq[x].fa = z; fhq[y].ch[k] = fhq[x].ch[k ^ 1], fhq[fhq[x].ch[k ^ 1]].fa = y; fhq[x].ch[k ^ 1] = y; fhq[y].fa = x; tr_up(y); }
    inline void splay1(int x) { stack[tp = 1] = x; for (int i = x; !isroot(i); i = fhq[i].fa) stack[++tp] = fhq[i].fa; while (tp) tr_dn(stack[tp--]); }
    inline void splay2(int x) { for (; !isroot(x); rotate(x)) { int y = fhq[x].fa, z = fhq[y].fa; if (!isroot(y)) (fhq[y].ch[1] ^ x ^ fhq[z].ch[1] ^ y) ? rotate(x) : rotate(y); } }
    inline void splay(int x) { splay1(x), splay2(x); tr_up(x); }
    inline void access(int x) { for (int y = 0; x; y = x, x = fhq[x].fa) splay(x), rson = y, tr_up(x); }
    inline void makeroot(int x) { access(x), splay(x), fhq[x].rev ^= 1; }
    inline int findroot(int x) { access(x), splay(x); while (lson) x = lson; return x; }
    inline void split(int x, int y) { makeroot(x), access(y), splay(y); }
    inline void connect(int x, int y) { makeroot(x), fhq[x].fa = y; }
} lct_mast; // Class LinkCutTree

signed main() {
    io.read(n), io.read(m);
    for (int i = 1, x; i <= n; ++i) io.read(x), lct_mast.fhq[i].c = lct_mast.fhq[i].l = lct_mast.fhq[i].r = x;
    for (int i = 1, x, y; i < n; ++i) io.read(x), io.read(y), lct_mast.connect(x, y);
    for (int i = 1, a, b, c; i <= m; ++i) {
        char ch = getchar();
        while (ch ^ 'C' && ch ^ 'Q') ch = getchar();
        if (ch ^ 'Q') io.read(a), io.read(b), io.read(c), lct_mast.split(a, b), lct_mast.fhq[b].tag = c;
        else io.read(a), io.read(b), lct_mast.split(a, b), io.write(lct_mast.fhq[b].w + 1, '\n');
    }
    return 0;
}

LYC

15.「ZJOI2017」树状数组

漆黑的晚上,九条可怜躺在床上辗转反侧。难以入眠的她想起了若干年前她的一次悲惨的OI比赛经历。那是一道基础的树状数组题。

给出一个长度为$n$的数组$A$,初始值都为0,接下来进行$m$次操作,操作有两种:

* 1 x,表示将 $A_{x}$ 变成 $\left ( A_{x}+ 1 \right )$ mod 2。

* 2 l r,表示询问 $ \left ( \sum_{i=l}^{r} A_{i} \right )$ mod 2。

尽管那个时候的可怜非常的 simple,但是她还是发现这题可以用树状数组做。当时非常young 的她写了如下的算法:

其中 lowbit($x$) 表示数字 $x$ 最低的非 0 二进制位,例如 lowbit(5) = 1, lowbit(12) = 4。进行第一类操作的时候就调用 Add($x$),第二类操作的时候答案就是 Query($l$,$r$)。

如果你对树状数组比较熟悉,不难发现可怜把树状数组写错了: Add 和 Find 中 $x$ 变化的方向反了。因此这个程序在最终测试时华丽的爆 0 了。

然而奇怪的是,在当时,这个程序通过了出题人给出的大样例——这也是可怜没有进行对

拍的原因。

现在,可怜想要算一下,这个程序回答对每一个询问的概率是多少,这样她就可以再次的

感受到自己是一个多么非的人了。然而时间已经过去了很多年,即使是可怜也没有办法完全回忆起当时的大样例。幸运的是,她回忆起了大部分内容,唯一遗忘的是每一次第一类操作的 $x$

的值,因此她假定这次操作的 $x$ 是在 $\left [ l_{i},r_{i} \right ]$ 范围内 等概率随机 的。

具体来说,可怜给出了一个长度为 $n$ 的数组 $A$,初始为 0,接下来进行了 $m$ 次操作:

* 1 $l$ $r$,表示在区间 $\left [ l, r \right ]$ 中等概率选取一个 $x$ 并执行 Add($x$)。

* 2 $l$ $r$,表示询问执行 Query$\left ( l, r \right )$得到的结果是正确的概率是多少。


这道题$\cdots$,没什么可讲的吧?二维线段树蛮干就好了

#include <cstdio>
#include <iostream>
#include <algorithm>
#include <cstring>
#include <queue>
#define mid ((l + r) >> 1)

using namespace std;
typedef long long int ull;

const int SIZE = 1e5 + 5;
const int MOD = 998244353;
struct TreeNode {
    int l, r;
    int val;
} tr[SIZE * 400];
int rt[SIZE * 20], n, q, tot;

inline ull result(ull p, ull q) {
    ull res = p * q; res %= MOD;
    return res = (res + (1 - p + MOD) * (1 - q + MOD) % MOD) % MOD;
}

inline ull fast_pow(ull x, ull y) {
    ull res = 1;
    for (; y; y >>= 1, (x *= x) %= MOD) if (y & 1) (res *= x) %= MOD;
    return res % MOD;
}

inline void modifies(int l, int r, int &rt, int x, int y, ull p) {
    if (!rt) rt = ++tot, tr[rt].val = 1;
    if (l >= x && r <= y) return (void)(tr[rt].val = result(p, tr[rt].val));
    if (mid >= x) modifies(l, mid, tr[rt].l, x, y, p);
    if (mid < y) modifies(mid + 1, r, tr[rt].r, x, y, p);
}

inline void modify(int l, int r, int rt, int lx, int rx, int ly, int ry, ull p) {
    if (l >= lx && r <= rx) return (void)(modifies(1, n, ::rt[rt], ly, ry, p));
    if (mid >= lx) modify(l, mid, rt << 1, lx, rx, ly, ry, p);
    if (mid < rx) modify(mid + 1, r, rt << 1 | 1, lx, rx, ly, ry, p);
}

inline ull finds(int l, int r, int rt, int x) {
    if (!rt) return 1;
    if (l ^ r)
        if (mid >= x) return result(tr[rt].val, finds(l, mid, tr[rt].l, x));
        else return result(tr[rt].val, finds(mid + 1, r, tr[rt].r, x));
    else return tr[rt].val;
}

inline ull find(int l, int r, int rt, int x, int y) {
    if (l ^ r)
        if (mid >= x) return result(finds(1, n, ::rt[rt], y), find(l, mid, rt << 1, x, y));
        else return result(finds(1, n, ::rt[rt], y), find(mid + 1, r, rt << 1 | 1, x, y));
    else return finds(1, n, ::rt[rt], y);
}

signed main() {
    scanf("%d %d", &n, &q);
    for (int i = 0; i < q; ++i) {
        int opt, l, r;
        scanf("%d %d %d", &opt, &l, &r);
        if (opt ^ 1) printf("%lld\n", find(0, n, 1, l - 1, r));
        else {
            ull p = fast_pow(r - l + 1, MOD - 2);
            if (l > 1) modify(0, n, 1, 1, l - 1, l, r, (1 - p + MOD) % MOD), modify(0, n, 1, 0, 0, 1, l - 1, 0);
            if (r < n) modify(0, n, 1, l, r, r + 1, n, (1 - p + MOD) % MOD), modify(0, n, 1, 0, 0, r + 1, n, 0);
            modify(0, n, 1, l, r, l, r, (1 - p * 2 % MOD + MOD) % MOD), modify(0, n, 1, 0, 0, l, r, p);
        }
    }
    return 0;
}

16.P2161 [SHOI2009]会场预约

// 未免LJS吐槽不贴题面了

这道题正解应该是平衡树或者BIT,这里提供一种简便的STL做法。

对于第一个操作,我们可以令有冲突的预约相等,避免set特性坑死一票人

对于第二个操作直接输出set的大小即可

// 省略头文件和快读

struct LaLaLand {
    int l, r;
    bool operator < (const LaLaLand& rhs) const { return r < rhs.l; }
};
set < LaLaLand > st;
int T;

signed main() {
    for (read(T); T; --T) {
        char opt[5];
        read(opt);
        int l, r, cnt = 0;
        if (*opt == 'A') {
            read(l, r);
            LaLaLand tmp = {l, r};
            IT it = st.find(tmp);
            while (it != st.end()) ++cnt, st.erase(it), it = st.find(tmp);
            st.insert(tmp);
            write(io_l, cnt);
        }
        else write(io_l, st.size());
    }
    return 0;
}

17.SP11470 TTM - To the moon

一个长度为n的数组,4种操作 :

  • C l r d:区间 $[l,r]$ 中的数都加 $d$ ,同时当前的时间戳加 $1$。
  • Q l r:查询当前时间戳区间 $[l,r]$ 中所有数的和 。
  • H l r t:查询时间戳 $t$ 区间 $[l,r]$ 的和 。
  • B t:将当前时间戳置为 $t$ 。

这道题正解应该是主席树+标记永久化,我来提供一种代码极短(压行后不到1K)的做法(怎么感觉我就没什么正经解法)

维护一个差分数组,将每次询问看作是两次前缀和相减

然后...就没有然后了,其它都是模板的主席树,只是修改用差分就好了

18.P4168 [Violet]蒲公英

在乡下的小路旁种着许多蒲公英,而我们的问题正是与这些蒲公英有关。

为了简化起见,我们把所有的蒲公英看成一个长度为n的序列 $(a_1,a_2..a_n)$,其中 $a_i$ 为一个正整数,表示第i棵蒲公英的种类编号。

而每次询问一个区间 [l,r],你需要回答区间里出现次数最多的是哪种蒲公英,如果有若干种蒲公英出现次数相同,则输出种类编号最小的那个。

注意,你的算法必须是在线的


LYC:

这就是传说中的分块打表了

由于强制在线,莫队是不可能的了

求区间众数,我们可以按之前讲的分块打表技术,处理出每两个特征点的每个元素的出现个数和当前众数。

对于每个询问,由于众数问题不容易往回收(要重新找众数),我们选取左右端点往内的最近的特征点

继承(注意不是直接把这个区间的答案拿来接着用,因为不能回退,如果直接用又不回退,下次调用时会调到错误的结果)这个区间的答案,利用这个区间之前统计的元素出现个数

向外扩张,统计出要询问的答案

最后要把刚刚统计众数加上的元素个数清掉(回溯最初处理好的状态)

代码:(这里块大小我开的$n^{2/3}$,开$\sqrt n$好像开不下)

#include<cmath>
#include<cstdio>
#include<vector>
#include<algorithm>
using namespace std;
vector<int> pri;
int n,m,a[40010],each,cnt[40][40][40010],MAX[40][40],lans,l,r,ans,tmp[40010];
int main()
{
    scanf("%d %d",&n,&m);
    each=pow(n,2.0/3);
    for(int i=1;i<=n;++i)
    {
        scanf("%d",&a[i]);
        pri.push_back(a[i]);
    }
    sort(pri.begin(),pri.end());
    pri.erase(unique(pri.begin(),pri.end()),pri.end());
    for(int i=1;i<=n;++i)    a[i]=lower_bound(pri.begin(),pri.end(),a[i])-pri.begin()+1;//离散化 
    for(int i=1,p=1;i<=n;i+=each,++p)
    {
        for(int j=i+each-1,q=p;j<=n;j+=each,++q)
        {
            for(int k=i;k<=j;++k)
            {
                ++cnt[p][q][a[k]];
                if(cnt[p][q][a[k]]>cnt[p][q][MAX[p][q]]||(cnt[p][q][a[k]]==cnt[p][q][MAX[p][q]]&&a[k]<MAX[p][q]))    MAX[p][q]=a[k];
            }
        }
    }//初始化(打表) 
    for(int i=1;i<=m;++i)
    {
        scanf("%d %d",&l,&r);
        l=(l+lans-1)%n+1;
        r=(r+lans-1)%n+1;
        if(l>r)    swap(l,r);//强制在线 
        if(r-l>2*each)
        {
            int p=ceil(1.0*(l-1)/each)+1;
            int q=ceil(1.0*(r+1)/each)-1;//得到向内收缩的端点
            ans=MAX[p][q];//继承答案 
            for(int i=(p-1)*each;i>=l;--i)
            {
                ++cnt[p][q][a[i]];
                if(cnt[p][q][a[i]]>cnt[p][q][ans]||(cnt[p][q][a[i]]==cnt[p][q][ans]&&a[i]<ans))    ans=a[i];
            }
            for(int i=q*each+1;i<=r;++i)
            {
                ++cnt[p][q][a[i]];
                if(cnt[p][q][a[i]]>cnt[p][q][ans]||(cnt[p][q][a[i]]==cnt[p][q][ans]&&a[i]<ans))    ans=a[i];
            }
            lans=pri[ans-1];//获得原值 
            printf("%d\n",lans);
            for(int i=(p-1)*each;i>=l;--i)    --cnt[p][q][a[i]];
            for(int i=q*each+1;i<=r;++i)    --cnt[p][q][a[i]];//回溯 
        }
        else//区间太小,如果两个端点在一个块内,向内收缩就会重复统计整个块,所以暴力统计 
        {
            ans=0;
            for(int i=l;i<=r;++i)
            {
                ++tmp[a[i]];
                if(tmp[a[i]]>tmp[ans]||(tmp[a[i]]==tmp[ans]&&a[i]<ans))    ans=a[i];
            }
            lans=pri[ans-1];
            printf("%d\n",lans);
            for(int i=l;i<=r;++i)    --tmp[a[i]];//回溯清零 
        }
    }
    return 0;
} 

WGY:

这道题luogu的数据很水,于是...于是...我们直接离散化+暴力就能过!

然后....然后就没了(这次真的不是我不想写,是真的没什么写的...)

#include <cstdio>
#include <iostream>
#include <algorithm>
#include <cstring>
#include <queue>
#define mid ((l + r) >> 1)
#define mp make_pair
#define fir first
#define sec second
#define pub push_back
#define pob pop_back

using namespace std;
typedef long long LL;

// #define DEBUG 1
struct IO {
#define MAXSIZE (1 << 20)
#define isdigit(x) (x >= '0' && x <= '9')
  char buf[MAXSIZE], *p1, *p2;
  char pbuf[MAXSIZE], *pp;
#if DEBUG
#else
  IO() : p1(buf), p2(buf), pp(pbuf) {}
  ~IO() { fwrite(pbuf, 1, pp - pbuf, stdout); }
#endif
  inline char gc() {
#if DEBUG
    return getchar();
#endif
    if (p1 == p2) p2 = (p1 = buf) + fread(buf, 1, MAXSIZE, stdin);
    return p1 == p2 ? ' ' : *p1++;
  }
  inline bool blank(char ch) {
    return ch == ' ' || ch == '\n' || ch == '\r' || ch == '\t';
  }
  template <class T>
  inline void read(T &x) {
    register double tmp = 1;
    register bool sign = 0;
    x = 0;
    register char ch = gc();
    for (; !isdigit(ch); ch = gc())
      if (ch == '-') sign = 1;
    for (; isdigit(ch); ch = gc()) x = x * 10 + (ch - '0');
    if (ch == '.')
      for (ch = gc(); isdigit(ch); ch = gc())
        tmp /= 10.0, x += tmp * (ch - '0');
    if (sign) x = -x;
  }
  inline void read(char *s) {
    register char ch = gc();
    for (; blank(ch); ch = gc())
      ;
    for (; !blank(ch); ch = gc()) *s++ = ch;
    *s = 0;
  }
  inline void read(char &c) {
    for (c = gc(); blank(c); c = gc())
      ;
  }
  inline void push(const char &c) {
#if DEBUG
    putchar(c);
#else
    if (pp - pbuf == MAXSIZE) fwrite(pbuf, 1, MAXSIZE, stdout), pp = pbuf;
    *pp++ = c;
#endif
  }
  template <class T>
  inline void write(T x) {
    if (x < 0) x = -x, push('-');
    static T sta[35];
    T top = 0;
    do {
      sta[top++] = x % 10, x /= 10;
    } while (x);
    while (top) push(sta[--top] + '0');
  }
  template <class T>
  inline void write(T x, char lastChar) {
    write(x), push(lastChar);
  }
} io;

const int SIZE = 50000 + 5;
int a[SIZE], b[SIZE];
int cnt[SIZE], x;
int l, r, n, m, l0, r0;

signed main() {
    io.read(n), io.read(m);
    for (int i = 1; i <= n; ++i) io.read(a[i]), b[i] = a[i];
    sort(b + 1, b + 1 + n);
    int len = unique(b + 1, b + 1 + n) - b - 1;
    for (int i = 1; i <= n; ++i) a[i] = lower_bound(b + 1, b + 1 + len, a[i]) - b;
    while (m--) {
        io.read(l0), io.read(r0);
        l = (l0 + x - 1) % n + 1;
        r = (r0 + x - 1) % n + 1;
        if (l > r) swap(l, r);
        for (int i = l; i <= r; ++i) cnt[a[i]]++;
        int MAX = 0, pos = 0;
        for (int i = 1; i <= len; ++i) if (MAX < cnt[i]) MAX = cnt[i], pos = i;
        printf("%d\n", b[pos]);
        x = b[pos];
        memset(cnt, 0, sizeof cnt);
    }
}

19.[CQOI2014]排序机械臂

为了把工厂中高低不等的物品按从低到高排好序,工程师发明了一种排序机械臂。它遵循一个简单的排序规则,第一次操作找到高度最低的物品的位置 $P_1$ ,并把左起第一个物品至 $P_1$ 间的物品 (即区间 $[1,P_1]$ 间的物品) 反序;第二次找到第二低的物品的位置 $P_2$ ,并把左起第二个至 $P_2$ 间的物品 (即区间 $[2,P_2]$ 间的物品) 反序……最终所有的物品都会被排好序。

样例说明

上图给出有六个物品的示例,第一次操作前,高度最低的物品在位置 $4$ ,于是把第一至第四的物品反序;第二次操作前,第二低的物品在位罝六,于是把第二至六的物品反序……

你的任务便是编写一个程序,确定一个操作序列,即每次操作前第 $i$ 低的物品所在位置 $P_i$ ,以便机械臂工作。需要注意的是,如果有高度相同的物品,必须保证排序后它们的相对位置关系与初始时相同。


LYC:

我们乍一看这道题

“哟!区间翻转平衡树裸题,还6倍经验?!”

再一看,每次翻转区间第一个到元素值最小的一个,再删除最小的一个

???

元素值最小的一个

应该怎么维护呢

我们可以在update里顺便维护这个子树中最小的值是第几个和它的大小(我脑残维护了前面有几个数)。

针对这个进行分类讨论:

1).当前结点的值比左右子树的最小值都小

那么最小值是当前结点的值。

它前面的元素个数是左子树的大小(中序遍历为原序列)

2).左子树的最小值最小

完美继承左子树的两个值。

3).右子树的最小值最小

最小值不用说了吧。

它前面的个数就是左子树的大小加一(当前结点)再加上右子树中在它前面的元素个数

因为子树不变时,那我们维护的这个值也不变。子树变了,那就肯定要更新。

然后再每次都查询最小的元素在哪一个位置,删掉它再翻转前面的区间就好了

代码:

#include<cstdio>
#include<algorithm>
using namespace std;
int n,tot,root,root1,root2,root3,c[100010];

struct node
{
    int l,r,num,key,sum;
    int s,smum;//最小值大小,前面的元素个数 
    bool rev;//翻转标记 
}nodes[100010];

struct laji
{
    int v,ID;
}a[100010];
bool cmp(laji one,laji two)
{
    if(one.v^two.v)    return one.v<two.v;
    return one.ID<two.ID;
}//离散化,顺便解决如果值相同,保持原有相对位置的问题 

int newnode(int val)
{
    nodes[++tot].s=val;//最小值设为自己,前面没有数 
    nodes[tot].num=val;
    nodes[tot].key=rand();
    nodes[tot].sum=1;
    return tot;
}
void pushdown(int x)
{
    swap(nodes[x].l,nodes[x].r);//首先交换左右儿子 
    nodes[x].smum=nodes[x].sum-(nodes[x].smum+1);//整个子树翻转过来,则最小值的位置就要翻过来 
    //原来前面的元素个数:nodes[x].smum
    //原位置: nodes[x].smum+1
    //翻转位置:nodes[x].sum-(nodes[x].smum+1)+1
    //翻转后前面的元素个数:nodes[x].sum-(nodes[x].smum+1)
    nodes[nodes[x].l].rev^=1;
    nodes[nodes[x].r].rev^=1;//下传标记 
    nodes[x].rev=0;//记得清空 
}
void update(int x)
{
    nodes[x].sum=nodes[nodes[x].l].sum+nodes[nodes[x].r].sum+1;
    if(nodes[nodes[x].l].rev)    pushdown(nodes[x].l);
    if(nodes[nodes[x].r].rev)    pushdown(nodes[x].r);//如果左右子树有标记,也要下传,否则更新出的值不对
    if(nodes[x].num<nodes[nodes[x].l].s&&nodes[x].num<nodes[nodes[x].r].s)//分类讨论情况1 
    {
        nodes[x].s=nodes[x].num;
        nodes[x].smum=nodes[nodes[x].l].sum;
    }
    else if(nodes[nodes[x].l].s<nodes[nodes[x].r].s)//情况2 
    {
        nodes[x].s=nodes[nodes[x].l].s;
        nodes[x].smum=nodes[nodes[x].l].smum;
    }
    else//情况3 
    {
        nodes[x].s=nodes[nodes[x].r].s;
        nodes[x].smum=nodes[nodes[x].r].smum+nodes[nodes[x].l].sum+1;
    }
}
void split(int now,int siz,int &x,int &y)
{
    if(!now)    x=y=0;
    else
    {
        if(nodes[now].rev)    pushdown(now);//下面要用到它的儿子,所以我们要下传标记 
        if(nodes[nodes[now].l].sum<siz)
        {
            x=now;
            split(nodes[now].r,siz-nodes[nodes[now].l].sum-1,nodes[x].r,y);
        }
        else
        {
            y=now;
            split(nodes[now].l,siz,x,nodes[y].l);
        }
        update(now);
    }
}
int merge(int x,int y)
{
    if(!x||!y)    return x+y;
    if(nodes[x].key<nodes[y].key)
    {
        if(nodes[x].rev)    pushdown(x);
        nodes[x].r=merge(nodes[x].r,y);
        update(x);
        return x;
    }
    else
    {
        if(nodes[y].rev)    pushdown(y);
        nodes[y].l=merge(x,nodes[y].l);
        update(y);
        return y;
    }
}
int main()
{
    srand(20060515);
    nodes[0].num=nodes[0].s=1e9;//处理节点为空的特殊情况为极大值,使更新叶子节点时不会把0更新进去 
    scanf("%d",&n);
    for(int i=1;i<=n;++i)
    {
        scanf("%d",&a[i].v);
        a[i].ID=i;
    }
    sort(a+1,a+1+n,cmp);
    for(int i=1;i<=n;++i)    c[a[i].ID]=i;//离散化 
    for(int i=1;i<=n;++i)    root=merge(root,newnode(c[i]));//处理原序列,直接把当前元素插入到它前面元素的右边 
    for(int i=1;i<=n;++i)
    {
        if(nodes[root].rev)    pushdown(root);//如果根节点有翻转标记要翻转,否则最小值的位置是反的。 
        printf("%d ",nodes[root].smum+i);//还有之前的i-1个已经被删除的数 
        split(root,nodes[root].smum,root1,root2);
        split(root2,1,root2,root3);//删除这个数 
        nodes[root1].rev^=1;//翻转前面的区间 
        root=merge(root1,root3);
    }
    return 0;
}

WGY:

SplayNB!!!区间操作直接秒过!!(具体题解参考LYC,我就放个代码)

#include <cstdio>
#include <iostream>
#include <algorithm>
#include <cstring>
#include <queue>
#define mid ((l + r) >> 1)

using namespace std;

// #define DEBUG 1
struct IO {
#define MAXSIZE (1 << 20)
#define isdigit(x) (x >= '0' && x <= '9')
  char buf[MAXSIZE], *p1, *p2;
  char pbuf[MAXSIZE], *pp;
#if DEBUG
#else
  IO() : p1(buf), p2(buf), pp(pbuf) {}
  ~IO() { fwrite(pbuf, 1, pp - pbuf, stdout); }
#endif
  inline char gc() {
#if DEBUG
    return getchar();
#endif
    if (p1 == p2) p2 = (p1 = buf) + fread(buf, 1, MAXSIZE, stdin);
    return p1 == p2 ? ' ' : *p1++;
  }
  inline bool blank(char ch) {
    return ch == ' ' || ch == '\n' || ch == '\r' || ch == '\t';
  }
  template <class T>
  inline void read(T &x) {
    register double tmp = 1;
    register bool sign = 0;
    x = 0;
    register char ch = gc();
    for (; !isdigit(ch); ch = gc())
      if (ch == '-') sign = 1;
    for (; isdigit(ch); ch = gc()) x = x * 10 + (ch - '0');
    if (ch == '.')
      for (ch = gc(); isdigit(ch); ch = gc())
        tmp /= 10.0, x += tmp * (ch - '0');
    if (sign) x = -x;
  }
  inline void read(char *s) {
    register char ch = gc();
    for (; blank(ch); ch = gc())
      ;
    for (; !blank(ch); ch = gc()) *s++ = ch;
    *s = 0;
  }
  inline void read(char &c) {
    for (c = gc(); blank(c); c = gc())
      ;
  }
  inline void push(const char &c) {
#if DEBUG
    putchar(c);
#else
    if (pp - pbuf == MAXSIZE) fwrite(pbuf, 1, MAXSIZE, stdout), pp = pbuf;
    *pp++ = c;
#endif
  }
  template <class T>
  inline void write(T x) {
    if (x < 0) x = -x, push('-');
    static T sta[35];
    T top = 0;
    do {
      sta[top++] = x % 10, x /= 10;
    } while (x);
    while (top) push(sta[--top] + '0');
  }
  template <class T>
  inline void write(T x, char lastChar) {
    write(x), push(lastChar);
  }
} io;

const int SIZE = 100000 + 5;
struct SPLAY {
    int fa;
    int size;
    int val;
    int rev;
    int ch[2];
} t[SIZE];
int n, root, tot, pos[SIZE];
struct InputNode {
    int id;
    int val;
} a[SIZE];

bool cmp1(const InputNode& rhs, const InputNode& rsp) { return rhs.val ^ rsp.val ? rhs.val < rsp.val : rhs.id < rsp.id; }
bool cmp2(const InputNode& rhs, const InputNode& rsp) { return rhs.id < rsp.id; }

void update(int x) {
    t[x].size = t[t[x].ch[0]].size + t[t[x].ch[1]].size + 1;
}

void transf(int x) {
    if (t[x].rev) {
        swap(t[x].ch[0], t[x].ch[1]);
        t[t[x].ch[0]].rev ^= 1;
        t[t[x].ch[1]].rev ^= 1;
        t[x].rev = 0;
    }
}

int make(int fa, int l, int r) {
    if (l > r) return 0;
    int p = ++tot;
    return(t[p].val = a[mid].val, t[p].fa = fa, pos[a[mid].val] = p, t[p].ch[0] = make(p, l, mid - 1), t[p].ch[1] = make(p, mid + 1, r), update(p), p);
}

void rotate(int x) {
    int y = t[x].fa, z = t[y].fa;
    transf(y), transf(x);
    int k = t[t[x].fa].ch[1] == x;
    t[y].ch[k] = t[x].ch[k ^ 1];
    t[t[y].ch[k]].fa = y;
    t[y].fa = x;
    t[x].ch[k ^ 1] = y;
    t[x].fa = z;
    if (z) t[z].ch[y == t[z].ch[1]] = x;
    update(y), update(x);
}

void splay(int x, int goal) {
    for (int y; (y = t[x].fa) ^ goal; rotate(x))
        if (t[y].fa ^ goal)
            rotate(t[t[x].fa].ch[1] ^ x ^ y ^ t[t[y].fa].ch[1] ? x : y);
    if (!goal) root = x;
}

int behavior() {
    transf(root);
    int x = t[root].ch[1];
    while (transf(x), t[x].ch[0]) x = t[x].ch[0];
    return x;
}

signed main() {
    scanf("%d", &n);
    for (int i = 1; i <= n; ++i) scanf("%d", &a[i + 1].val), a[i + 1].id = i + 1;
    a[1].val = 0, a[n + 2].val = n + 1;
    sort(a + 2, a + 2 + n, cmp1);
    for (int i = 2; i <= n + 1; ++i) a[i].val = i - 1;
    sort(a + 2, a + 2 + n, cmp2);
    root = make(0, 1, n + 2);
    for (int i = 1; i <= n; ++i) {
        int x = pos[i];
        splay(x, 0);
        printf("%d ", t[t[x].ch[0]].size);
        x = behavior();
        int y = pos[i - 1];
        splay(y, 0);
        splay(x, y);
        t[t[x].ch[0]].rev ^= 1;
    }
    return 0;
}

20.[SHOI2013]扇形面积并(权值线段树&扫描线)

给定 n 个同心的扇形,求有多少面积,被至少 $k$ 个扇形所覆盖。


LYC:

这道题要用到扫描线的思想,那扫描线是什么呢?

扫描线

经典应用:给出一堆矩阵,求它们覆盖的总面积

数据范围恶心死了,$10^5$个矩阵,矩阵的坐标的绝对值小于$10^9$。

看到这个数据范围我们就知道:要离散化。

之后呢,我们把每个矩阵的左右边界都转换成添加和删除。

我们用一条扫描线从左到右扫过去,线段树来维护现在整条扫描线的被覆盖情况。

矩阵的左边就是添加以这个矩阵的上下边界点为端点的线段。

右边再右边一个就转换成删除。

每走过一个点,我们就用线段树统计当前扫描线被这些矩阵覆盖了多少,再把答案加上去就好了。

模板我就不写啦其实是我不会

$\ $

好了那我们来看这道题吧。

一堆圆心相同的扇形,求被至少$k$个扇形覆盖的面积。

我们先单看一条从圆心射出的射线。

由于每个扇形的圆心是相同的。

所以覆盖每个地方的扇形数量是向外递减的。

那么被至少$k$个扇形覆盖的地方就在从外到内第$k$个扇形,它被刚好或者多于$k$个扇形所覆盖,更向内的地方的数量则更多,也是大于等于$k$。

所以我们可以用权值线段树来维护每个地方有多少个扇形边界。从而查找从外到内第$k$个扇形边界。

运用扫描线思想,把给出的扇形的两个角度值转换成添加和删除一个扇形边界。

实现有很多坑,自己看注释吧。

代码:

#include<cstdio>
#include<vector>
#include<algorithm>
using namespace std;
long long n,m,k,nodes[400010],a1,a2,ans,s,now;
struct cir
{
    long long r;
    bool insorex;//1为添加,2为删除
}cur;
vector<cir> op[2000010];//用来储存在每个角度的修改
void ins(long long l,long long r,long long x,long long pos)
{
    ++nodes[x];
    if(l^r)
    {
        long long mid=(l+r)>>1;
        if(pos<=mid)    ins(l,mid,x<<1,pos);
        else    ins(mid+1,r,x<<1|1,pos);
    }
}
void exins(long long l,long long r,long long x,long long pos)//权值线段树删除
{
    --nodes[x];
    if(l^r)
    {
        long long mid=(l+r)>>1;
        if(pos<=mid)    exins(l,mid,x<<1,pos);
        else    exins(mid+1,r,x<<1|1,pos);
    }
}
long long find(long long l,long long r,long long x,long long val)//权值线段树查找从外到内第k个
{
    if(l==r)    return l;
    long long mid=(l+r)>>1;
    if(val<=nodes[x<<1|1])    return find(mid+1,r,x<<1|1,val);
    else    return find(l,mid,x<<1,val-nodes[x<<1|1]);
}
int main()
{
    scanf("%lld %lld %lld",&n,&m,&k);
    for(long long i=1;i<=n;++i)
    {
        scanf("%lld %lld %lld",&cur.r,&a1,&a2);
        s=max(s,cur.r);
        a1+=m;
        a2+=m;//a1,a2可能是负数
               //转换成添加和修改
        if(a1>a2)//这个扇形跨越了分界线,需要拆成两半
        {
            cur.insorex=1;
            op[a1+1].push_back(cur);
            op[1].push_back(cur);
            cur.insorex=0;
            op[a2+1].push_back(cur);
        }
        else
        {
            cur.insorex=1;
            op[a1+1].push_back(cur);
            cur.insorex=0;
            op[a2+1].push_back(cur);
        }
    }
    for(long long i=1;i<=m*2;++i)//注意是半开区间(-Pi,Pi]
    {
        for(long long j=0;j<op[i].size();++j)//进行修改操作
        {
            cur=op[i][j];
            if(cur.insorex)
            {
                ins(1,s,1,cur.r);
                ++now;
            }
            else
            {
                exins(1,s,1,cur.r);
                --now;//统计现在总共有多少个
            }
        }
        if(now>=k)//有k个才统计,不然容易出锅
        {
            long long tmp=find(1,s,1,k);
            ans+=tmp*tmp;//圆面积公式:Pi*r*r
        }
    }
    printf("%lld\n",ans);
    return 0;
}

WGY

提供树状数组+二分做法,复杂度$O(n\log^2n)$

树状数组写起来短,常数小,动动脑子可以套在很多题目上,它不香嘛

题目让我们求目前覆盖的第 $k$ 大,我们可以把原来的覆盖位置差分一下,然后二分即可

#include <cstdio>
#include <iostream>
#include <algorithm>
#include <cstring>
#include <queue>
#define mid ((l + r) >> 1)
#define mp make_pair
#define fir first
#define sec second
#define pub push_back
#define pob pop_back

using namespace std;
typedef long long LL;

 #define DEBUG 1
struct IO {
#define MAXSIZE (1 << 20)
#define isdigit(x) (x >= '0' && x <= '9')
  char buf[MAXSIZE], *p1, *p2;
  char pbuf[MAXSIZE], *pp;
#if DEBUG
#else
  IO() : p1(buf), p2(buf), pp(pbuf) {}
  ~IO() { fwrite(pbuf, 1, pp - pbuf, stdout); }
#endif
  inline char gc() {
#if DEBUG
    return getchar();
#endif
    if (p1 == p2) p2 = (p1 = buf) + fread(buf, 1, MAXSIZE, stdin);
    return p1 == p2 ? ' ' : *p1++;
  }
  inline bool blank(char ch) {
    return ch == ' ' || ch == '\n' || ch == '\r' || ch == '\t';
  }
  template <class T>
  inline void read(T &x) {
    register double tmp = 1;
    register bool sign = 0;
    x = 0;
    register char ch = gc();
    for (; !isdigit(ch); ch = gc())
      if (ch == '-') sign = 1;
    for (; isdigit(ch); ch = gc()) x = x * 10 + (ch - '0');
    if (ch == '.')
      for (ch = gc(); isdigit(ch); ch = gc())
        tmp /= 10.0, x += tmp * (ch - '0');
    if (sign) x = -x;
  }
  inline void read(char *s) {
    register char ch = gc();
    for (; blank(ch); ch = gc())
      ;
    for (; !blank(ch); ch = gc()) *s++ = ch;
    *s = 0;
  }
  inline void read(char &c) {
    for (c = gc(); blank(c); c = gc())
      ;
  }
  inline void push(const char &c) {
#if DEBUG
    putchar(c);
#else
    if (pp - pbuf == MAXSIZE) fwrite(pbuf, 1, MAXSIZE, stdout), pp = pbuf;
    *pp++ = c;
#endif
  }
  template <class T>
  inline void write(T x) {
    if (x < 0) x = -x, push('-');
    static T sta[35];
    T top = 0;
    do {
      sta[top++] = x % 10, x /= 10;
    } while (x);
    while (top) push(sta[--top] + '0');
  }
  template <class T>
  inline void write(T x, char lastChar) {
    write(x), push(lastChar);
  }
} io;

const int SIZE = 1e6 + 5;
int n, m, k, a[SIZE];
LL tree[SIZE], ans;
vector < int > In[SIZE<<1], Out[SIZE<<1];

void Add(int x, int y) { for (; x < SIZE; x += x & -x) tree[x] += y; }

int Ask(int x, int res = 0) { for (; x; x -= x & -x) res += tree[x]; return res; }

int KthElement(int k) {
    int l = 1, r = SIZE - 5;
    while (l < r)
        if (Ask(mid) < k) l = mid + 1;
        else r = mid;
    return l;
}

signed main() {
    io.read(m), io.read(n), io.read(k);
    int L, R;
    for (int i = 1; i <= m; ++i) {
        io.read(a[i]);
        io.read(L);
        io.read(R);
        if (L < R) L += n + 1, R += n, In[L].pub(i), Out[R + 1].pub(i);
        else L ^= R ^= L ^= R, L += n, R += n + 1, In[1].pub(i), Out[L + 1].pub(i), In[R].pub(i);
    }
    int now = 0, x;
    for (int i = 1; i <= (n<<1); ++i) {
        for (int j = 0; j < In[i].size(); ++j) Add(a[In[i][j]], 1);
        for (int j = 0; j < Out[i].size(); ++j) Add(a[Out[i][j]], -1);
        now += In[i].size() - Out[i].size();
        if (now >= k) x = KthElement(now - k + 1), ans += (LL)x * x;
    }
    printf("%lld\n", ans);
    return 0;
}

1.「一本通 4.6 例 1」营业额统计

原题来自:HNOI 2002

Tiger 最近被公司升任为营业部经理,他上任后接受公司交给的第一项任务便是统计并分析公司成立以来的营业情况。

Tiger 拿出了公司的账本,账本上记录了公司成立以来每天的营业额。分析营业情况是一项相当复杂的工作。由于节假日,大减价或者是其他情况的时候,营业额会出现一定的波动,当然一定的波动是能够接受的,但是在某些时候营业额突变得很高或是很低,这就证明公司此时的经营状况出现了问题。

经济管理学上定义了一种最小波动值来衡量这种情况:记该天以前某一天的营业额为 $a_i$,该天营业额为 $b$,则该天的最小波动值 $\delta=\min |a_i-b|$,当最小波动值越大时,就说明营业情况越不稳定。而分析整个公司的从成立到现在营业情况是否稳定,只需要把每一天的最小波动值加起来就可以了。

你的任务就是编写一个程序帮助 Tiger 来计算这一个值,第一天的最小波动值为第一天的营业额。

一句话题意

给出一个 $n$ 个数的数列 $\{a_n\}$,对于第 $i$ 个元素 $a_i$,定义 $f_i=\min |a_i-a_j|$,其中 $1\le j\lt i,f_1=a_1$。求 $\sum f_i$。


没什么可说的,就是板子,可以用来调一下指针版splay。不过我发现指针版跑的飞慢是在搞嘛

还是正经说一下吧

这道题要查询最小波动值。我们只需要把x的前驱和后继求出来,看一下谁跟x的关系更好离的更近,再算出答案,再把这个数insert进去就好了

#include <cstdio>
#include <algorithm>
#define mid (l + r >> 1)
#define int long long

using namespace std;

const int SIZE = (1 << 15) + 5;
const int INF = 0x7fffffff;
struct SplayNode {
    SplayNode *son[2], *fa;
    int val, siz;
    SplayNode(SplayNode *fa = NULL, int val = 0) : fa(fa), val(val) {
        *son = *(son + 1) = NULL;
        siz = 1;
    }
    inline bool islyczbing() { return this == fa->son[1]; }
    inline int rnk() { return 1 + (*son ? (*son)->siz : 0); }
    inline void up() { siz = 1 + (*son ? (*son)->siz : 0) + ((*(son + 1)) ? (*(son + 1))->siz : 0); }
} * root;

inline void rotate(SplayNode *x) {
    bool k = x->islyczbing();
    SplayNode *y = x->fa, *z = y->fa, *w = x->son[!k];
    if (root == y)
        root = x;
    else
        z->son[y->islyczbing()] = x;
    x->fa = z;
    y->fa = x;
    x->son[!k] = y;
    y->son[k] = w;
    if (w)
        w->fa = y;
    y->up();
    x->up();
}

inline void cosplay(SplayNode *x) {
    while (x != root) {
        if (x->fa != root)
            rotate(x->islyczbing() ^ x->fa->islyczbing() ? x : x->fa);
        rotate(x);
    }
}

inline void insert(int val) {
    if (!root)
        return (void)(root = new SplayNode(NULL, val));
    SplayNode *p = root, *fa = NULL;
    while (p) {
        fa = p;
        p = p->son[val > p->val];
    }
    p = new SplayNode(fa, val);
    fa->son[val > fa->val] = p;
    cosplay(p);
}

inline int getpre(int val) {
    SplayNode *p = root, *lst = NULL;
    while (p) {
        if (val > p->val)
            lst = p, p = p->son[1];
        else
            p = p->son[0];
    }
    if (lst)
        return cosplay(lst), lst->val;
    return -INF;
}

inline int getnext(int val) {
    SplayNode *p = root, *lst = NULL;
    while (p) {
        if (val < p->val)
            lst = p, p = p->son[0];
        else
            p = p->son[1];
    }
    if (lst)
        return cosplay(lst), lst->val;
    return INF;
}

signed main() {
    root = NULL;
    int n;
    scanf("%lld", &n);
    int ans = 0;
    for (int i = 1, x; i <= n; ++i) {
        scanf("%lld", &x);
        if (i == 1)
            ans += x;
        else
            ans += min(x - getpre(x + 1), getnext(x - 1) - x);
        insert(x);
    }
    printf("%lld\n", ans);
    return 0;
}

2.[SHOI2013]发牌

在一些扑克游戏里,如德州扑克,发牌是有讲究的。一般称呼专业的发牌手为荷官。荷官在发牌前,先要销牌(burn card)。所谓销牌,就是把当前在牌库顶的那一张牌移动到牌库底,它用来防止玩家猜牌而影响游戏。

假设一开始,荷官拿出了一副新牌,这副牌有N 张不同的牌,编号依次为1到N。由于是新牌,所以牌是按照顺序排好的,从牌库顶开始,依次为1, 2,……直到N,N 号牌在牌库底。为了发完所有的牌,荷官会进行N 次发牌操作,在第i 次发牌之前,他会连续进行Ri次销牌操作, Ri由输入给定。请问最后玩家拿到这副牌的顺序是什么样的?

举个例子,假设N = 4,则一开始的时候,牌库中牌的构成顺序为{1, 2, 3, 4}。

假设R1=2,则荷官应该连销两次牌,将1 和2 放入牌库底,再将3 发给玩家。目前牌库中的牌顺序为{4, 1, 2}。

假设R2=0,荷官不需要销牌,直接将4 发给玩家,目前牌库中的牌顺序为{1,2}。

假设R3=3,则荷官依次销去了1, 2, 1,再将2 发给了玩家。目前牌库仅剩下一张牌1。

假设R4=2,荷官在重复销去两次1 之后,还是将1 发给了玩家,这是因为1 是牌库中唯一的一张牌。


splay题解 by wgy

这道题还蛮简单的,用splay找出1-x的区间,然后把它转到后面去,再删除第一个数就好了

需吸氧

#pragma GCC optimize(2)
#pragma GCC optimize(3)
#pragma GCC optimize("Ofast")
#pragma GCC optimize("inline")
#pragma GCC optimize("-fgcse")
#pragma GCC optimize("-fgcse-lm")
#pragma GCC optimize("-fipa-sra")
#pragma GCC optimize("-ftree-pre")
#pragma GCC optimize("-ftree-vrp")
#pragma GCC optimize("-fpeephole2")
#pragma GCC optimize("-ffast-math")
#pragma GCC optimize("-fsched-spec")
#pragma GCC optimize("unroll-loops")
#pragma GCC optimize("-falign-jumps")
#pragma GCC optimize("-falign-loops")
#pragma GCC optimize("-falign-labels")
#pragma GCC optimize("-fdevirtualize")
#pragma GCC optimize("-fcaller-saves")
#pragma GCC optimize("-fcrossjumping")
#pragma GCC optimize("-fthread-jumps")
#pragma GCC optimize("-funroll-loops")
#pragma GCC optimize("-fwhole-program")
#pragma GCC optimize("-freorder-blocks")
#pragma GCC optimize("-fschedule-insns")
#pragma GCC optimize("inline-functions")
#pragma GCC optimize("-ftree-tail-merge")
#pragma GCC optimize("-fschedule-insns2")
#pragma GCC optimize("-fstrict-aliasing")
#pragma GCC optimize("-fstrict-overflow")
#pragma GCC optimize("-falign-functions")
#pragma GCC optimize("-fcse-skip-blocks")
#pragma GCC optimize("-fcse-follow-jumps")
#pragma GCC optimize("-fsched-interblock")
#pragma GCC optimize("-fpartial-inlining")
#pragma GCC optimize("no-stack-protector")
#pragma GCC optimize("-freorder-functions")
#pragma GCC optimize("-findirect-inlining")
#pragma GCC optimize("-fhoist-adjacent-loads")
#pragma GCC optimize("-frerun-cse-after-loop")
#pragma GCC optimize("inline-small-functions")
#pragma GCC optimize("-finline-small-functions")
#pragma GCC optimize("-ftree-switch-conversion")
#pragma GCC optimize("-foptimize-sibling-calls")
#pragma GCC optimize("-fexpensive-optimizations")
#pragma GCC optimize("-funsafe-loop-optimizations")
#pragma GCC optimize("inline-functions-called-once")
#pragma GCC optimize("-fdelete-null-pointer-checks")
#pragma GCC optimize(2)
#pragma GCC optimize(3)
#include <cstdio>
#include <iostream>
#include <algorithm>
#include <cstring>
#include <queue>
#include <utility>
#include <cctype>
#define mid (l + r >> 1)

using namespace std;

const int SIZE = 7e5 + 5;
struct SPLAY {
    int siz;
    int val;
    int ch[2];
    int fa;
} T[SIZE];
int root, n, R[SIZE], tot;

template < typename T >
inline void read ( T &a ) {
    a = 0; T f = 1; char ch;
    while (!isdigit(ch = getchar())) if (ch == '-') f = -1;
    while (isdigit(ch)) a = (a << 3) + (a << 1) + (ch ^ '0'), ch = getchar();
    a *= f;
}

template < typename T >
inline T read ( T _checkType, bool _Typeflag ) {
    T f = 1, a = 0; char ch;
    while (!isdigit(ch = getchar())) if (ch == '-') f = -1;
    while (isdigit(ch)) a = (a << 3) + (a << 1) + (ch ^ '0'), ch = getchar();
    return a * f;
}
template < typename T >
inline void write ( T x, char end_, int st = 0 ) {
    if (x < 0) x = -x, putchar('-');
    if (x > 9) write(x / 10, end_, st + 1);
    putchar(x % 10 + '0');
    if (!st) putchar(end_);
}

inline void update(int u) {
    T[u].siz = T[T[u].ch[0]].siz + T[T[u].ch[1]].siz + 1;
}

inline int make(int l, int r, int fa) {
    int u = ++tot;
    T[u].siz = 1;
    T[u].val = mid;
    T[u].ch[0] = T[u].ch[1] = 0;
    T[u].fa = fa;
    if (mid > l) T[u].ch[0] = make(l, mid - 1, u);
    if (mid < r) T[u].ch[1] = make(mid + 1, r, u);
    update(u);
    return u;
}

inline void rotate(int x) {
    int y = T[x].fa;
    int z = T[y].fa;
    int w = T[y].ch[1] == x;
    T[z].ch[T[z].ch[1] == y] = x;
    T[x].fa = z;
    T[y].ch[w] = T[x].ch[w ^ 1];
    T[T[x].ch[w ^ 1]].fa = y;
    T[x].ch[w ^ 1] = y;
    T[y].fa = x;
    update(y), update(x);
}

inline void splay(int x, int goal) {
    for (; T[x].fa ^ goal; rotate(x)) {
        int y = T[x].fa;
        int z = T[y].fa;
        if (z ^ goal)
            T[y].ch[1] ^ x ^ T[z].ch[1] ^ y ? rotate(x) : rotate(y);
    }
    if (!goal) root = x;
}

inline int getRank(int x) {
    int u = root;
    while (233) {
        if (x <= T[T[u].ch[0]].siz) u = T[u].ch[0];
        else {
            x -= T[T[u].ch[0]].siz + 1;
            if (!x) return u;
            u = T[u].ch[1];
        }
    }
}

inline int getcard(int x) {
    if (x) {
        splay(getRank(x), 0);
        int u = root;
        root = T[u].ch[1];
        T[root].fa = 0;
        T[u].ch[1] = 0;
        update(u);
        splay(getRank(T[root].siz), 0);
        if (u) T[u].fa = root;
        if (root) T[root].ch[1] = u, update(root);
    }
    int ranker = getRank(1);
    int res = T[ranker].val;
    splay(ranker, 0);
    if (T[ranker].ch[1])
        T[root = T[ranker].ch[1]].fa = 0;
    return res;
}

signed main() {
    read(n);
    root = make(1, n, 0);
    for (int i = 1; i <= n; ++i) {
        read(R[i]);
        write(getcard(R[i] % T[root].siz), '\n');
    }
    return 0;
}

权值线段树题解 by lyc:

对于这道题,平衡树 常数太大 不开O2基本都会超时。

所以我们用常数更小一些的线段树来解决这道题。

首先,这道题有7e5张牌,我们采用权值线段树的做法,每个节点记录它对应的区间还剩多少张牌。那么建树操作就很显然了对吧

然后我们考虑销牌操作,由于销牌后整个序列依旧按从小到大有序(1,2,3,4 ->1,3,4),我们设定一个指针now,指向当前牌堆里的最后一张牌在目前牌堆中大小的排名,初始为0(n%n),每次销掉a张牌,我们就将now向右移a位,使它指向销完牌后牌堆里的最后一张,再将now加一,得到当前排队顶的牌在牌堆中的排名,根据定义,在权值线段树中查找第now个还存在的数就得到了这次删去的牌的编号。输出后还要记得把这张牌在权值线段树中删掉,再把now减一,使它重新指向牌堆里的最后一张牌。

代码:

#include<cstdio>
int n,nodes[80000010],now,a;
void writing(int x)
{
    if(!x)    return;
    writing(x/10);
    putchar((x%10)+'0');
}
void read(int &hhh)
{
    int x=0;
    char c=getchar();
    while((c<'0')|(c>'9'))    c=getchar();
    x=c^'0';
    c=getchar();
    while((c<='9')&(c>='0'))
    {
        x=(x<<1)+(x<<3)+(c^'0');
        c=getchar();
    }
    hhh=x;
}
void build(int l,int r,int x)
{
    if(l==r)    nodes[x]=1;
    else
    {
        int mid=(l+r)>>1;
        build(l,mid,x<<1);
        build(mid+1,r,(x<<1)+1);
        nodes[x]=r-l+1;
    }
}
int kth(int l,int r,int x,int val)
{
    if(l^r)
    {
        int mid=(l+r)>>1;
        if(val<=nodes[x<<1])    return kth(l,mid,x<<1,val);
        else    return kth(mid+1,r,(x<<1)+1,val-nodes[x<<1]);
    }
    else    return l;
}
void del(int l,int r,int x,int val)
{
    --nodes[x];
    if(l^r)
    {
        int mid=(l+r)>>1;
        if(val<=mid)    del(l,mid,x<<1,val);
        else    del(mid+1,r,(x<<1)+1,val);
    }
}
int main()
{
    read(n);
    build(1,n,1);
    for(int i=n;i;--i)
    {
        read(a);
        now=(now+a)%i+1;
        int cur=kth(1,n,1,now);
        writing(cur);
        putchar('\n');
        del(1,n,1,cur);
        --now;
    }
    return 0;
}

3.数列分块入门 8

给出一个长为 $n$ 的数列,以及 $n$ 个操作,操作涉及区间询问等于一个数 $c$ 的元素,并将这个区间的所有元素改为 $c$。


本题的重点在于并将这个区间的所有元素改为c

区间推平操作!

有了区间推平,我们第一时间会想到什么?线段树|分块

ODT!!!!

#include <cstdio>
#include <iostream>
#include <algorithm>
#include <cstring>
#include <queue>
#include <vector>
#include <string>
#include <cctype>
#include <utility>
#include <set>
#define IT set<TreeNode>::iterator

using namespace std;

inline int getInt() {
    int a = 0, f = 1; char ch;
    while (!isdigit(ch = getchar())) if (ch == '-') f = -1;
    while (isdigit(ch)) a = (a << 3) + (a << 1) + (ch ^ '0'), ch = getchar();
    return a * f;
}

inline void outInt(int x) {
    char buffer[20]; int length = 0;
    bool minus = x < 0; if (minus) x = -x;
    do { buffer[length++] = x % 10 + '0'; x /= 10; } while (x);
    if (minus) buffer[length++] = '-';
    do { putchar(buffer[--length]); } while (length);
}

struct TreeNode {
    int lef;
    int rig;
    mutable int val;
    
    TreeNode(int l, int r = -1, int v = 0) : lef(l), rig(r), val(v) {}
    friend bool operator < (const TreeNode &rhs) const {
        return lef < rhs.lef;
    }
} ;
set<TreeNode> st;
int n = getInt();

inline IT split(int pos) {
    IT it = st.lower_bound(TreeNode(pos));
    if (it != st.end() && it->lef == pos)
        return it;
    it--;
    int l = it->lef, r = it->rig;
    int v = it->val;
    st.erase(it);
    st.insert(TreeNode(l, pos - 1, v));
    return st.insert(TreeNode(pos, r, v)).first;
}

inline void assign(int l, int r, int v) {
    IT itr = split(r + 1), itl = split(l);
    st.erase(itl, itr);
    st.insert(TreeNode(l, r, v));
}

inline int query(int l, int r, int v) {
    IT itr = split(r + 1), itl = split(l);
    int res = 0;
    for (; itl != itr; ++itl) res += (itl->rig - itl->lef + 1) * (itl->val == v);
    return res;
}

signed main() {
    for (int i = 1; i <= n; ++i) {
        int x = getInt();
        st.insert(TreeNode(i, i, x));
    }
    for (int i = 1; i <= n; ++i) {
        int l = getInt();
        int r = getInt();
        int v = getInt();
        printf("%d\n", query(l, r, v));
        assign(l, r, v);
    }
    return 0;
}

4.SP3267 DQUERY - D-query

$Given$ $a$ $sequence$ $of$ $n$ $numbers$ $and$ $a$ $number$ $of$ $d-queries.$ $A$ $d-query$ $is$ $a$ $pair$ $(i,$ $j)$ $(1$ $≤$ $i$ $≤$ $j$ $≤$ $n).$ $For$ $each$ $d-query$ $(i,$ $j),$ $you$ $have$ $to$ $return$ $the$ $number$ $of$ $distinct$ $elements$ $in$ $the$ $subsequence$ $a_i$,$a_{i+1},\cdots,a_j$


这道题的树状数组的做法是离线的。

首先按询问的右端点排序,然后用树状数组把没有出现过的值加上去,把出现过的减去,最后统计答案即可

#include <cstdio>
#include <iostream>
#include <algorithm>
#include <cstring>
#include <queue>
#define mid ((l + r) >> 1)

using namespace std;

const int SIZE = 1e6 + 5;
int n, m, tree[SIZE], ans[SIZE];
int list[SIZE], tag[SIZE];
pair < pair < int , int > , int > st[SIZE];

inline bool _rule(pair < pair < int , int > , int > x, pair < pair < int , int > , int > y) {
    return x.first.second < y.first.second;
}

signed main() {
    scanf("%d", &n);
    for (int i = 1; i <= n; ++i) scanf("%d", &list[i]);
    scanf("%d", &m);
    for (int i = 1; i <= m; ++i) scanf("%d %d", &st[i].first.first, &st[i].first.second), st[i].second = i;
    sort(st + 1, st + 1 + m, _rule);
    int zblyc = 1;
    for (int i = 1; i <= m; ++i) {
        for (int j = zblyc; j <= st[i].first.second; ++j) {
            if (tag[list[j]]) for (int x = tag[list[j]]; x <= n; x += x & -x) tree[x]--;
            tag[list[j]] = j;
            for (int x = j; x <= n; x += x & -x) tree[x]++;
        }
        zblyc = st[i].first.second + 1;
        int lef = 0, rig = 0;
        for (int x = st[i].first.second; x; x -= x & -x) rig += tree[x];
        for (int x = st[i].first.first - 1; x; x -= x & -x) lef += tree[x];
        ans[st[i].second] = rig - lef;
    }
    for (int i = 1; i <= m; ++i) printf("%d\n", ans[i]);
    return 0;
}

5.P1231 教辅的组成

注:本题我已搬运到OJ,数据已经齐全,略卡时间和空间,但均在合理范围,我的代码已通过,请求公开 Link

蒟蒻HansBug在一本语文书里面发现了一本答案,然而他却明明记得这书应该还包含一份练习题。

然而出现在他眼前的书多得数不胜数,其中有书,有答案,有练习册。已知一个完整的书册均应该包含且仅包含一本书、一本练习册和一份答案,然而现在全都乱做了一团。

许多书上面的字迹都已经模糊了,然而HansBug还是可以大致判断这是一本书还是练习册或答案,并且能够大致知道一本书和答案以及一本书和练习册的对应关系(即仅仅知道某书和某答案、某书和某练习册有可能相对应,除此以外的均不可能对应)。

既然如此,HansBug想知道在这样的情况下,最多可能同时组合成多少个完整的书册。


虽然这并不是数据结构的题,但我觉得搬上来也不错,毕竟网络流是个好东西

这道题我自己码了半天RE结果看了Siyuan的题解后才发现我add_edge传参传错了。话说为什么传参错会RE

大概讲一下这道题的思路吧

看到题目很容易想到跑费用流二分图匹配,但这道题不行。

为什么呢?

因为他有三个部分

那么直接跑最大流吧!

但这里还有一个问题,就是一本书作为中间的部分有可能会被重复利用绿色产品

Siyuan曾经这样说道:

因此我们就要引入拆点的思想。我们的目的是:即使一本书与多个联系册有关系,它流出的流量也只能是 1。所以我们把每个代表书的点拆成左右两个点,左边的点和练习册连边,右边的点和答案连边;当然左右对应点也要连一条容量为 1 的边

有了拆点的思想我们就可以直接跑最大流啦!

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

using namespace std;

const int SIZE_N = 4e6 + 5;
const int SIZE_M = 1e6 + 5;
const int INF = 1 << 30;
int n1, n2, n3, m, tot = 1, s, t;
int path[SIZE_N], ter[SIZE_M];
int head[SIZE_N], edge[SIZE_M];
int _next[SIZE_N], ver[SIZE_M];
int depth[SIZE_N], cur[SIZE_N];

inline int getid(int p, int x) {
    switch (p) {
        case 1: return x;
        case 2: return n2 + x;
        case 3: return n2 + n1 + x;
        case 4: return n2 + (n1 << 1) + x;
    }
}

inline void add_edge(int from, int to, int dis) {
    ver[++tot] = to, edge[tot] = dis, _next[tot] = head[from], head[from] = tot;
}

inline int bfs() {
    memset(depth, 0, sizeof depth);
    memcpy(cur, head, sizeof head);
    queue < int > Q;
    Q.push(s), depth[s] = 1;
    while (!Q.empty()) {
        int x = Q.front(); Q.pop();
        for (int i = head[x]; i; i = _next[i]) {
            if (!depth[ver[i]] && edge[i]) depth[ver[i]] = depth[x] + 1, Q.push(ver[i]);
        }
    }
    return depth[t];
}

inline int dfs(int x, int flow) {
    if (x == t) return flow;
    int res = 0;
    for (int i = cur[x]; i && res < flow; i = _next[i]) {
        cur[x] = i;
        if (depth[ver[i]] == depth[x] + 1 && edge[i]) {
            int tmp = dfs(ver[i], min(edge[i], flow - res));
            if (tmp) edge[i] -= tmp, edge[i ^ 1] += tmp, res += tmp;
        }
    }
    if (res < flow) depth[x] = -1;
    return res;
}

inline int dinic() {
    int res = 0;
    for (int x; bfs(); ) while (x = dfs(s, INF)) res += x;
    return res;
}

signed main() {
    scanf("%d %d %d", &n1, &n2, &n3);
    scanf("%d", &m);
    for (int i = 1; i <= m; ++i) {
        int from, to;
        scanf("%d %d", &from, &to);
        add_edge(getid(1, to), getid(2, from), 1);
        add_edge(getid(2, from), getid(1, to), 0);
    }
    scanf("%d", &m);
    for (int i = 1; i <= m; ++i) {
        int from, to;
        scanf("%d %d", &from, &to);
        add_edge(getid(3, from), getid(4, to), 1);
        add_edge(getid(4, to), getid(3, from), 0);
    }
    for (int i = 1; i <= n1; ++i) add_edge(getid(2, i), getid(3, i), 1), add_edge(getid(3, i), getid(2, i), 0);
    s = 0, t = (n1 << 1) + n2 + n3 + 1;
    for (int i = 1; i <= n2; ++i) add_edge(s, getid(1, i), 1), add_edge(getid(1, i), s, 0);
    for (int i = 1; i <= n3; ++i) add_edge(getid(4, i), t, 1), add_edge(t, getid(4, i), 0);
    printf("%d\n", dinic());
    return 0;
}

6.[USACO12OPEN]书架Bookshelf

当农夫约翰闲的没事干的时候,他喜欢坐下来看书。多年过去,他已经收集了 N 本书 (1 <= N <= 100,000), 他想造一个新的书架来装所有书。

每本书 i 都有宽度 W(i) 和高度 H(i)。书需要按顺序添加到一组书架上;比如说,第一层架子应该包含书籍1 ... k,第二层架子应该以第k + 1本书开始,以下如此。每层架子的总宽度最大为L(1≤L≤1,000,000,000)。每层的高度等于该层上最高的书的高度,并且整个书架的高度是所有层的高度的总和,因为它们都垂直堆叠。

请帮助农夫约翰计算整个书架的最小可能高度。

有N(1 <= N <= 100000)本书,每本书有一个宽度W(i),高度H(i),(1 <= H(i) <= 1,000,000; 1 <= W(i) <= L)。

现在有足够多的书架,书架宽度最多是L (1 <= L <= 1,000,000,000),把书按顺序(先放1,再放2.....)放入书架。某个书架的高度是该书架中所放的最高的书的高度。

将所有书放入书架后,求所有书架的高度和的最小值?


首先考虑$O(N^2)$的暴力DP,很好写出以下的转移方程:

$$ F_i=\min(F_j+\max(H_{j+1},H_{j+2},\cdots,H_i)) $$

其中满足

$$ W_{j+1},W_{j+2},\cdots,W_i\le L $$

不难发现$W$的限制满足单调性,所以我们可以通过二分得到

也就是说

$$ F_i=\min(F_j+\max(H_{j+1},H_{j+2},\cdots,H_i)) $$

满足

$$ left_i\le j $$

所以我们可以用线段树优化,维护区间赋值,区间最小值,单点修改三个操作。

但这样还不够,我们还需要开一个单调栈,当然单调队列也可以(不过我懒)

枚举每一个位置然后找出第一个大于$H_i$的位置$lef$,将$[lef+1,i]$这段区间的值赋为$H_i$

进而可以发现我们只需要每次区间修改$H$的值,查询直接在这一段区间查询就好了

#include <cstdio>
#include <iostream>
#include <algorithm>
#include <cstring>
#include <queue>
#include <stack>
#define mid ((l + r) >> 1)
#define lson (k << 1)
#define rson (k << 1 | 1)

using namespace std;
typedef long long ull;

const int SIZE = 100000 + 5;
const ull INF = 1e18;
stack < ull > S;
ull n, limit, W[SIZE], H[SIZE], pre[SIZE];
ull list[SIZE], dp[SIZE], val[SIZE << 2];
ull minval[SIZE << 2], flag[SIZE << 2];

inline void make(int k, int l, int r) {
    minval[k] = val[k] = flag[k] = INF;
    if (l ^ r) make(lson, l, mid), make(rson, mid + 1, r);
}

inline void push(int k) {
    if (flag[k] ^ INF) {
        minval[lson] = val[lson] + flag[k];
        minval[rson] = val[rson] + flag[k];
        flag[lson] = flag[rson] = flag[k];
        flag[k] = INF;
    }
}

inline int update(int k, int l, int r, int x, int y, int v) {
    if (l >= x && r <= y) return minval[k] = val[k] + v, flag[k] = v, 0;
    push(k);
    if (mid >= x) update(lson, l, mid, x, y, v);
    if (mid < y) update(rson, mid + 1, r, x, y, v);
    minval[k] = min(minval[lson], minval[rson]);
    val[k] = min(val[lson], val[rson]);
    return 0;
}

inline void modify(int k, int l, int r, int x) {
    if (l ^ r) {
        push(k);
        if (mid >= x) modify(lson, l, mid, x);
        else modify(rson, mid + 1, r, x);
        minval[k] = min(minval[lson], minval[rson]);
        val[k] = min(val[lson], val[rson]);
    }
    else minval[k] = INF, val[k] = dp[l - 1]; 
}

inline ull query(int k, int l, int r, int x, int y) {
    if (l >= x && r <= y) return minval[k];
    push(k); ull res = INF;
    if (mid >= x) res = min(res, query(lson, l, mid, x, y));
    if (mid < y) res = min(res, query(rson, mid + 1, r, x, y));
    return res;
}

signed main() {
    scanf("%lld %lld", &n, &limit);
    make(1, 1, n);
    S.push(1);
    for (int i = 1; i <= n; ++i) scanf("%lld %lld", &H[i], &W[i]), list[i] = list[i - 1] + W[i];
    for (int i = 2; i <= n; ++i) {
        while (S.size() && H[i] > H[S.top()]) S.pop();
        if (S.size()) pre[i] = S.top();
        S.push(i);
    }
    for (int i = 1; i <= n; ++i) {
        modify(1, 1, n, i);
        if (pre[i] + 1 <= i) update(1, 1, n, pre[i] + 1, i, H[i]);
        int lef = lower_bound(list, list + 1 + i, list[i] - limit) - list;
        if (lef < i) dp[i] = query(1, 1, n, lef + 1, i);
    }
    printf("%lld\n", dp[n]);
    return 0;
}

7.P1110 [ZJOI2007]报表统计

Q的妈妈是一个出纳,经常需要做一些统计报表的工作。今天是妈妈的生日,小Q希望可以帮妈妈分担一些工作,作为她的生日礼物之一。

经过仔细观察,小Q发现统计一张报表实际上是维护一个非负整数数列,并且进行一些查询操作。

在最开始的时候,有一个长度为N的整数序列,并且有以下三种操作:

INSERT i k:在原数列的第i个元素后面添加一个新元素kk;如果原数列的第ii个元素已经添加了若干元素,则添加在这些元素的最后(见下面的例子)
MIN_GAP:查询相邻两个元素的之间差值(绝对值)的最小值
MIN_SORT_GAP:查询所有元素中最接近的两个元素的差值(绝对值)
例如一开始的序列为5, 3, 15,3,1。

执行操作INSERT 2 9将得到:5, 3, 9, 15,3,9,1,此时MIN_GAP为22,MIN_SORT_GAP为22。

再执行操作INSERT 2 6将得到:5, 3, 9, 6, 15,3,9,6,1

注意这个时候原序列的第22个元素后面已经添加了一个99,此时添加的66应加在99的后面。这个时候MIN_GAP为22,MIN_SORT_GAP为11。

于是小Q写了一个程序,使得程序可以自动完成这些操作,但是他发现对于一些大的报表他的程序运行得很慢,你能帮助他改进程序么?


待坑,先留着

#include <cstdio>
#include <iostream>
#include <algorithm>
#include <cstring>
#include <queue>
#include <utility>
#define sgt_mid ((SGT[k].l + SGT[k].r) >> 1)
#define mid ((l + r) >> 1)
#define U int k, int l, int r
#define H int k, int x, int val
#define transfer SGT[k].val = min(SGT[lson].val, SGT[rson].val)
#define lson (k << 1)
#define rson (k << 1 | 1)
#define LRE lson, l, mid
#define RRE rson, mid + 1, r
#define LWA lson, x, val
#define RWA rson, x, val
#define int long long

using namespace std;

const int SIZE = 500000 + 5;
const int INF = 0x7fffffff;
int a[SIZE], b[SIZE], root;
int n, m, tot, MIN_GAP, MIN_SORT_GAP;
struct SPLAY {
    int ch[2];
    int val;
    int fa;
} SBT[SIZE];

struct TREE {
    int l;
    int r;
    int val;
} SGT[SIZE];

template < typename T >
inline T ads(T x) { return x > 0 ? x : -x; }

/*****************SplayArea*****************/

inline void rotate(int x) {
    int y = SBT[x].fa;
    int z = SBT[y].fa;
    int w = SBT[y].ch[1] == x;
    SBT[z].ch[SBT[z].ch[1] == y] = x;
    SBT[x].fa = z;
    SBT[y].ch[w] = SBT[x].ch[w ^ 1];
    SBT[SBT[x].ch[w ^ 1]].fa = y;
    SBT[x].ch[w ^ 1] = y;
    SBT[y].fa = x;
}

inline void splay(int x, int goal) {
    for (; SBT[x].fa ^ goal; rotate(x)) {
        int y = SBT[x].fa;
        int z = SBT[y].fa;
        if (z ^ goal)
            SBT[z].ch[1] ^ y ^ SBT[y].ch[1] ^ x ? rotate(x) : rotate(y);
    }
    if (!goal) root = x;
}

inline void insert(int x) {
    int u = root, fa = 0;
    while (u && SBT[u].val ^ x) fa = u, u = SBT[u].ch[x > SBT[u].val];
    if (u) return ;
    u = ++tot;
    SBT[u].fa = fa;
    if (fa) SBT[fa].ch[x > SBT[fa].val] = u;
    SBT[u].val = x;
    splay(u, 0);
}

inline void find(int x) {
    int u = root;
    if (!u) return ;
    while (SBT[u].ch[x > SBT[u].val] && SBT[u].val ^ x) u = SBT[u].ch[x > SBT[u].val];
    splay(u, 0);
}

inline int next_(int x, int f) {
    find(x);
    int u = root;
    if (SBT[u].val == x) return SBT[u].val;
    if (SBT[u].val < x && !f) return SBT[u].val;
    if (SBT[u].val > x && f) return SBT[u].val;
    u = SBT[u].ch[f];
    while (SBT[u].ch[f ^ 1]) u = SBT[u].ch[f ^ 1];
    return SBT[u].val;
}

/*****************EndSplay*****************/

/*****************SegmentTreeArea*****************/

inline void make(U) {
    SGT[k].l = l, SGT[k].r = r;
    if (l ^ r) make(LRE), make(RRE), transfer;
    else SGT[k].val = ads(a[l] - a[l - 1]);
}

inline void modify(H) {
    if (SGT[k].l ^ SGT[k].r)
        if (sgt_mid >= x) modify(LWA), transfer;
        else modify(RWA), transfer;
    else SGT[k].val = val;
}

/*****************EndSegmentTree*****************/

inline void Initialize() {
    MIN_GAP = INF, MIN_SORT_GAP = INF;
    scanf("%d %d", &n, &m);
    insert(INF), insert(-INF);
    a[0] = INF, a[n + 1] = INF;
    for (int i = 1; i <= n; ++i) {
        scanf("%d", &a[i]);
        if (i ^ 1) {
            int l_limit = next_(a[i], false), r_limit = next_(a[i], true);
            MIN_SORT_GAP = min(MIN_SORT_GAP, min(ads(l_limit - a[i]), ads(r_limit - a[i])));
        }
        insert(a[i]);
        b[i] = a[i];
    }
    make(1, 1, n);
    for (int i = 1; i <= m; ++i) {
        char S[SIZE];
        scanf("%s", S);
        if (*S == 'I') {
            int x, y;
            scanf("%d %d", &x, &y);
            int l_limit = next_(y, false);
            int r_limit = next_(y, true);
            MIN_SORT_GAP = min(MIN_SORT_GAP, min(ads(y - l_limit), ads(y - r_limit)));
            insert(y);
            MIN_GAP = min(MIN_GAP, ads(b[x] - y));
            modify(1, x + 1, ads(a[x + 1] - y));
            b[x] = y;
        }
        else if (S[4] == 'G') printf("%d\n", min(MIN_GAP, SGT[1].val));
        else printf("%d\n", MIN_SORT_GAP);
    }
}

signed main() {
    Initialize();
    return 0;
}

8.Link Cut Tree (动态树)

给定 n 个点以及每个点的权值,要你处理接下来的 m 个操作。
操作有四种,操作从 0 到 3 编号。点从 1 到 n 编号。

0 x y 代表询问从 x 到 y 的路径上的点的权值的 xor 和。保证 x 到 y 是联通的。

1 x y 代表连接 x 到 y,若 x 到 y 已经联通则无需连接。

2 x y 代表删除边 (x,y),不保证边 (x,y) 存在。

3 x y 代表将点 x 上的权值变成 y。


模板题没什么好说的,放一下代码就好了,如果要学LCT可以自己看博客(首先要学splay(not fhq-treap))

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

using namespace std;

const int SIZE = 3e5 + 5;
struct ReadNode {
    template < typename T>
    void operator >> (T &a) {
        a = 0; T f = 1; char ch;
        while (!isdigit(ch = getchar())) if (ch == '-') f = -1;
        while (isdigit(ch)) a = (a << 3) + (a << 1) + (ch ^ '0'), ch = getchar();
        a *= f;
    }
    
    template < typename T>
    void write(T x) {
        if (x < 0) x = -x, putchar('-');
        if (x > 9) write(x / 10);
        putchar(x % 10 + '0');
    }
    
    template < typename T>
    void operator << (T x) {
        write(x);
    }
} win;
int n, q, dis[SIZE];

/******************LinkCutTree******************/

class LinkCutTree {
private:
    struct TreeNode {
        int ch[2];
        int val;
        int sum;
        int rev;
        int fa;
    } T[SIZE + 5];
    int st[SIZE + 5];
    
    inline void exch(int &x, int &y) { x ^= y ^= x ^= y; }
    inline void reverse(int x) { exch(T[x].ch[0], T[x].ch[1]); T[x].rev ^= 1; }
    inline void link(int x, int y, int w) { T[T[x].fa = y].ch[w] = x; }
    inline bool push_up(int x) { return (T[x].sum = T[x].val ^ T[T[x].ch[0]].sum ^ T[T[x].ch[1]].sum), 1; }
    inline void push_down(int x) { T[x].rev && (reverse(T[x].ch[0]), reverse(T[x].ch[1]), T[x].rev = 0); }
    inline void makeroot(int x) { access(x); splay(x); reverse(x); }
    inline void split(int x, int y) { makeroot(x); access(y); splay(y); }
    inline bool isroot(int x) { return (T[T[x].fa].ch[0] ^ x && T[T[x].fa].ch[1] ^ x); }
    inline bool which(int x) { return T[T[x].fa].ch[1] == x; }
    inline void rotate(int x) { int y = T[x].fa, z = T[y].fa, w = which(x); !isroot(y) && (T[z].ch[which(y)] = x), T[x].fa = z, link(T[x].ch[w ^ 1], y, w), link(y, x, w ^ 1), push_up(y), push_up(x); }
    inline void splay(int x) { int y = x, top = 0; while (st[++top] = y, !isroot(y)) y = T[y].fa; while (top) push_down(st[top]), --top; while (!isroot(x)) y = T[x].fa, !isroot(y) && (rotate(which(x) ^ which(y) ? x : y), 0), rotate(x); }
    inline void access(int x) { for (int son = 0; x; x = T[son = x].fa) splay(x), T[x].ch[1] = son, push_up(x); }
    inline int getroot(int x) { access(x), splay(x); while (T[x].ch[0]) push_down(x), x = T[x].ch[0]; return splay(x), x; }
    
public:
    inline void init(int length, int *data) { for (int i = 1; i <= length; ++i) T[i].val = data[i]; }
    inline void connect(int x, int y) { makeroot(x), getroot(y) ^ x && (T[x].fa = y); }
    inline void erase(int x, int y) { makeroot(x), !(getroot(y) ^ x) && !(T[y].fa ^ x) && !(T[y].ch[0]) && (T[y].fa = T[x].ch[1] = 0, push_up(x)); }
    inline void insert(int x, int v) { splay(x), T[x].val = v; }
    inline int find(int x, int y) { return split(x, y), T[y].sum; }
} lct_mast;

/*****************EndLinkCutTree*****************/

signed main() {
    win >> n; win >> q;
    for (int i = 1; i <= n; ++i) win >> dis[i];
    lct_mast.init(n, dis);
    for (int i = 1; i <= q; ++i) {
        int opt, x, y;
        win >> opt; win >> x; win >> y;
        switch(opt) {
        case 0: win << lct_mast.find(x, y), puts(""); break;
        case 1: lct_mast.connect(x, y); break;
        case 2: lct_mast.erase(x, y); break;
        case 3: lct_mast.insert(x, y); break;
        }
    }
    return 0;
}
// 需要233.cpp

9.P5227 [AHOI2013]连通图

给定一个无向连通图和若干个小集合,每个小集合包含一些边,对于每个集合,你需要确定将集合中的边删掉后改图是否保持联通。集合间的询问相互独立

定义一个图为联通的当且仅当对于任意的两个顶点,都存在一条路径连接它们


暴力LCT,维护把时间当成权值的最大生成树

因为我们是离线算法,所以我们可以得到每一条边的删除时间

顺便维护一个权值最小的边的编号

%:include <cstdio>
%:include <iostream>
%:include <algorithm>
%:include <cstring>
%:include <queue>

using namespace std;

const int SIZE = 1e7 + 5;
const int INF = 0x7fffffff;
struct ReadNode <%
    template < typename T>
    void operator >> (T &a) <%
        a = 0; T f = 1; char ch;
        while (!isdigit(ch = getchar())) if (ch == '-') f = -1;
        while (isdigit(ch)) a = (a << 3) + (a << 1) + (ch ^ '0'), ch = getchar();
        a *= f;
    %>
    
    template < typename T>
    void write(T x) <%
        if (x < 0) x = -x, putchar('-');
        if (x > 9) write(x / 10);
        putchar(x % 10 + '0');
    %>
    
    template < typename T>
    void operator << (T x) <%
        write(x);
    %>
%> win;
int n, m;

/******************LinkCutTree******************/

class LinkCutTree <%
public:
    struct TreeNode <%
        int     ch<:2:>;
        int     fa;
        int     val;
        int     siz;
        int     cnt;
        int     miv;
        bool     rev;
    %> T<:SIZE:>;

    inline void exch(int &x, int &y) <% x ^= y ^= x ^= y; %>
    inline void push1(int x) <% T<:x:>.siz = T<:T<:x:>.ch<:0:>:>.siz + T<:T<:x:>.ch<:1:>:>.siz +T<:x:>.cnt + (x <= n); x > n && (T<:x:>.miv = x); %>
    inline void push2(int x) <% (T<:x:>.val >= T<:T<:T<:x:>.ch<:0:>:>.miv:>.val) && (T<:x:>.miv = T<:T<:x:>.ch<:0:>:>.miv); (T<:T<:x:>.miv:>.val >= T<:T<:T<:x:>.ch<:1:>:>.miv:>.val) && (T<:x:>.miv = T<:T<:x:>.ch<:1:>:>.miv); %>
    inline void transfer(int x) <% push1(x); push2(x); %>
    inline bool which(int x) <% return T<:T<:x:>.fa:>.ch<:1:> == x; %>
    inline bool isroot(int x) <% return (T<:T<:x:>.fa:>.ch<:0:> ^ x) && (T<:T<:x:>.fa:>.ch<:1:> ^ x); %>
    inline void rotate(int x) <% int y = T<:x:>.fa, z = T<:y:>.fa, w = which(x), test = which(y), s = T<:x:>.ch<:w ^ 1:>; (!isroot(y)) && (T<:z:>.ch<:test:> = x); T<:y:>.ch<:w:> = s, T<:x:>.ch<:w ^ 1:> = y; (s) && (T<:s:>.fa = y); T<:x:>.fa = z, T<:y:>.fa = x; transfer(y); %>
    inline void reverse(int x) <% exch(T<:x:>.ch<:0:>, T<:x:>.ch<:1:>), T<:x:>.rev ^= 1; %>
    inline void push_down(int x) <% if (T<:x:>.rev) <% if (T<:x:>.ch<:0:>) reverse(T<:x:>.ch<:0:>); if (T<:x:>.ch<:1:>) reverse(T<:x:>.ch<:1:>); T<:x:>.rev = 0; %> %>
    inline void push_up(int x) <% (!isroot(x)) && (push_up(T<:x:>.fa), 1); push_down(x); %>
    inline void splay(int x) <% push_up(x); for (; !isroot(x); rotate(x)) (!isroot(T<:x:>.fa)) && ((which(x) == which(T<:x:>.fa) ? rotate(T<:x:>.fa) : rotate(x)), 1); transfer(x); %>
    inline void access(int x) <% for (int i = 0; x; x = T<:i = x:>.fa) splay(x), T<:x:>.cnt += T<:T<:x:>.ch<:1:>:>.siz, T<:x:>.cnt -= T<:T<:x:>.ch<:1:> = i:>.siz, transfer(x); %>
    inline void makeroot(int x) <% access(x), splay(x), reverse(x); %>
    inline void split(int x, int y) <% makeroot(y), access(x), splay(x); %>
    inline int getroot(int x) <% access(x), splay(x), push_down(x); for (; T<:x:>.ch<:0:>; push_down(x = T<:x:>.ch<:0:>)); return splay(x), x; %>

    inline void init(int length, int data) <% for (int i = 0; i <= length; ++i) T<:i:>.val = data; %>
    inline void connect(int x, int y) <% split(x, y), T<:y:>.fa = x, T<:x:>.cnt += T<:y:>.siz, transfer(x); %>
    inline void erase(int x, int y) <% split(x, y), T<:y:>.fa = T<:x:>.ch<:0:> = 0, transfer(x); %>
    inline bool find() <% return access(1), splay(1), (T<:1:>.siz == n); %>
%> lct_mast;

/*****************EndLinkCutTree*****************/

int now<:SIZE:>, num, ans<:SIZE:>, st<:SIZE:>, top;
struct EdgeNode <%
    int from;
    int to;
    int dis;
%> edge<:SIZE:>;
struct LycNode <%
    bool opt, tag;
    int idx;
%> H<:SIZE:>;

signed main() <%
    win >> n; win >> m;
    lct_mast.init(n, INF);
    for (int i = 1; i <= m; ++i) <%
        int x, y;
        win >> x; win >> y;
        edge<:i:> = <%x, y%>;
        now<:i:> = i;
        H<:++num:> = <%1, 0, i%>;
    %>
    int k, tot = m;
    win >> k;
    for (int i = 1; i <= k; ++i) <%
        int x;
        win >> x;
        for (int j = 1; j <= x; ++j) <%
            int y;
            win >> y;
            edge<:now<:y:>:>.dis = i - 1;
            lct_mast.T<:now<:y:> + n:>.val = i - 1;
            H<:++num:> = <%0, 0, now<:y:>%>;
            edge<:++tot:>.from = edge<:now<:y:>:>.from;
            edge<:tot:>.to = edge<:now<:y:>:>.to;
            now<:y:> = tot;
            st<:++top:> = now<:y:>;
        %>
        H<:++num:>.tag = true;
        if (i ^ k) while (top) H<:++num:> = <%1, 0, st<:top--:>%>;
    %>
    for (int i = 1; i <= m; ++i)
        if (!edge<:now<:i:>:>.dis)
            edge<:now<:i:>:>.dis = k, lct_mast.T<:now<:i:> + n:>.val = k;
    tot += n;
    for (int i = 1; i <= tot; ++i) lct_mast.T<:i:>.siz = 1;
    for (int i = 1; i <= num; ++i) <%
        if (H<:i:>.tag) puts(lct_mast.find() ? "Connected" : "Disconnected");
        else <%
            int j = H<:i:>.idx, from = edge<:j:>.from;
            int to = edge<:j:>.to, dis = edge<:j:>.dis;
            lct_mast.makeroot(from);
            if (H<:i:>.opt) <%
                if (lct_mast.getroot(to) == from) <%
                    int mix = lct_mast.T<:from:>.miv;
                    if (lct_mast.T<:mix:>.val >= dis) continue;
                    lct_mast.erase(edge<:mix - n:>.from, mix);
                    lct_mast.erase(edge<:mix - n:>.to, mix);
                %>
                lct_mast.connect(from, j + n);
                lct_mast.connect(to, j + n);
            %> else <%
                if (lct_mast.getroot(to) == from) <%
                    lct_mast.transfer(j + n);
                    if (!lct_mast.T<:j + n:>.fa && !lct_mast.T<:j + n:>.siz) continue;
                    lct_mast.erase(edge<:j:>.from, j + n);
                    lct_mast.erase(edge<:j:>.to, j + n);
                %>
            %>
        %>
    %>
    return 0;
%>

10.[TJOI2018]异或

现在有一颗以$1$为根节点的由$n$个节点组成的树,树上每个节点上都有一个权值$v_i$。现在有$Q$次操作,操作如下:

  • $1\;x\;y$:查询节点$x$的子树中与$y$异或结果的最大值
  • $2\;x\;y\;z$:查询路径$x$到$y$上点与$z$异或结果最大值

这是一道可持久化$01Trie$树+树链剖分的好题。

但是树剖是出了名的难打,仔细看题,可以发现只需要$DFS$序便可以解决问题

对于第一个操作,直接用$DFS$序维护$sub\_tree$的信息即可

对于第二个操作,可以考虑在$DFS$遍历整棵树的同时插入权值

查询的话直接上$LCA$

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

using namespace std;

const int SIZE = (100000 + 5) << 1;
int head[SIZE], ver[SIZE];
int nxt[SIZE], edge[SIZE];
int n, q, fuck, a[SIZE];

inline void pushEdge(int x, int y, int z) {
    ver[++fuck] = y, edge[fuck] = z;
    nxt[fuck] = head[x], head[x] = fuck;
}

struct ZeroOneTrie {
    int root[SIZE], cnt;
    int trie[SIZE << 6][2];
    int frie[SIZE << 6];

    ZeroOneTrie() { root[0] = cnt = 1; }
    
    inline void insert(int last, int &lyc, int x) {
        int rt = lyc = ++cnt;
        for (int i = 30; ~i; --i) {
            int now = (x >> i) & 1;
            trie[rt][!now] = trie[last][!now];
            trie[rt][now] = ++cnt;
            rt = trie[rt][now];
            last = trie[last][now];
            frie[rt] = frie[last] + 1;
        }
    }

    inline int find(int l, int r, int x) {
        int res = 0;
        for (int i = 30; ~i; --i) {
            int now = (x >> i) & 1;
            if (frie[trie[r][!now]] - frie[trie[l][!now]]) {
                r = trie[r][!now];
                l = trie[l][!now];
                res |= 1 << i;
            } else {
                r = trie[r][now];
                l = trie[l][now];
            }
        }
        return res;
    }
} t0, t1;

int dfn[SIZE], L[SIZE], R[SIZE];
int num, fa[SIZE][30], depth[SIZE];

inline void dfs(int x, int pre) {
    t1.insert(t1.root[pre], t1.root[x], a[x]);
    fa[x][0] = pre;
    depth[x] = depth[pre] + 1;
    L[x] = ++num;
    dfn[num] = a[x];
    for (int i = head[x]; ~i; i = nxt[i]) if (ver[i] ^ pre) dfs(ver[i], x);
    R[x] = num;
}

inline int lca_mast(int x, int y) {
    if (depth[x] < depth[y]) swap(x, y);
    for (int i = 25; ~i; --i) if (depth[fa[x][i]] >= depth[y]) x = fa[x][i];
    if (x ^ y) {
        for (int i = 25; ~i; --i)
            if (fa[x][i] ^ fa[y][i]) x = fa[x][i], y = fa[y][i];
        return fa[x][0];
    } else return x;
}

signed main() {
    memset(head, -1, sizeof head);
    scanf("%d %d", &n, &q);
    for (int i = 1; i <= n; ++i) scanf("%d", &a[i]);
    for (int i = 1; i < n; ++i) {
        int from, to;
        scanf("%d %d", &from, &to);
        pushEdge(from, to, 1);
        pushEdge(to, from, 1);
    }
    dfs(1, 0);
    for (int j = 1; j <= 25; ++j)
        for (int i = 1; i <= n; ++i)
            fa[i][j] = fa[fa[i][j - 1]][j - 1];
    for (int i = 1; i <= n; ++i) t0.insert(t0.root[i - 1], t0.root[i], dfn[i]);
    for (int i = 1; i <= q; ++i) {
        int opt, x, y, z;
        scanf("%d %d %d", &opt, &x, &y);
        if (opt ^ 1) {
            scanf("%d", &z);
            int ans = lca_mast(x, y);
            printf("%d\n", max(t1.find(t1.root[fa[ans][0]], t1.root[x], z), t1.find(t1.root[fa[ans][0]], t1.root[y], z)));
        } else {
            printf("%d\n", t0.find(t0.root[L[x] - 1], t0.root[R[x]], y));
        }
    }
    return 0;
}

NOI-Online-T1-序列

img spfaed

其实这道题是全场最难的……

我这里给出一种并查集的做法。

首先我们把操作2中的 $u$ 和 $v$ 合并

对于操作1我们可以把他转化为操作2来做。

比如我们针对操作1给出 $(u,v)$ 和 $(v,t)$ 两条边,对 $(u,v)$ 进行同增,对 $(v,t)$ 进行同减。

这样就变成了 $u++,t--$ 了。

然后我们把操作2缩点,然后把操作1的边连到操作2缩的点上。

然后对操作1合并。

此时,图中的每个点的度数最多为一。

那么对于一条边 $(x,y)$ 如果 $a_{x}-b_{x}=a_{y}-b_{y}$ 那么就是YES;

对于一个自环 $(x,x)$ 如果 $(a_{x}-b_{x})$ 为偶数,那么就是YES;

对于一个度数为零的点 $x$ 如果 $a_{x}=b_{x}$ 那么就是YES;

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

using namespace std;

char buf[1 << 21], *p1 = buf, *p2 = buf;
#ifndef ONLINE_JUDGE
#define gc() getchar()
#else
#define gc() (p2 == p1 && (p2 = (p1 = buf) + fread(buf, 1, 1 << 21, stdin), p1 == p2) ? EOF : *p1++)
#endif
#define is_number (ch >= '0' && ch <= '9')

template < typename Type >
void read(Type& a) {
    a = 0; bool f = 0; char ch;
    while (!(ch = gc(), is_number)) if (ch == '-') f = 1;
    while (is_number) a = (a << 3) + (a << 1) + (ch ^ '0'), ch = gc();
    a = (f ? -a : a);
}

template < typename Type, typename... Args >
void read(Type& t, Args&... args) {
    read(t), read(args...);
}

const int MAXN = 2e5 + 5;
int T, n, m, vis[MAXN];
int u[MAXN], v[MAXN];
int a[MAXN], b[MAXN];
int nxt[MAXN], to[MAXN];
int head[MAXN], tot;
struct UnionFindSet {
    int fa[MAXN];

    void init(int n) {
        for (int i = 1; i <= n; ++i)
            fa[i] = i;
    }

    int find(int x) {
        if (x ^ fa[x]) fa[x] = find(fa[x]);
        return fa[x];
    }

    void merge(int x, int y) {
        int u = find(x);
        int v = find(y);
        if (u ^ v) {
            fa[v] = u;
            a[u] += a[v];
            b[u] += b[v];
        }
    }
} ufs;

void add(int x, int y) {
    to[++tot] = y;
    nxt[tot] = head[x];
    head[x] = tot;
}

signed main() {
    for (read(T); T; --T) {
        read(n, m);
        tot = 0;
        memset(head, 0, sizeof head);
        ufs.init(n);
        for (int i = 1; i <= n; ++i) read(a[i]);
        for (int i = 1; i <= n; ++i) read(b[i]);
        for (int i = 1, opt; i <= m; ++i) {
            read(opt, u[i], v[i]);
            if (opt ^ 1) vis[i] = 1, ufs.merge(u[i], v[i]), --i, --m;
        }
        for (int i = 1; i <= m; ++i) {
            add(ufs.find(u[i]), ufs.find(v[i]));
            add(ufs.find(v[i]), ufs.find(u[i]));
        }
        for (int i = 1; i <= n; ++i) {
            int t = ufs.find(to[head[i]]);
            for (int x = nxt[head[i]]; x; x = nxt[x])
                ufs.merge(t, ufs.find(to[x]));
        }
        for (int i = 1; i <= n; ++i) {
            if (head[i]) {
                int x = ufs.find(i);
                int y = ufs.find(to[head[i]]);
                if (x ^ y) {
                    if ((a[x] - b[x]) ^ (a[y] - b[y])) {
                        puts("NO");
                        goto there;
                    }
                }
                else {
                    if ((a[x] - b[y]) & 1) {
                        puts("NO");
                        goto there;
                    }
                }
            }
            else if (ufs.fa[i] == i) {
                if (a[i] ^ b[i]) {
                    puts("NO");
                    goto there;
                }
            }
        }
        puts("YES");
        there: ;
    }
}

NOI-Online-T2-冒泡排序

这道题我在考场上的做法很玄,本来是奔着40pts的部分分去的,结果爆零了(至今没找到原因)

我们设

$$bigger_{i}=\sum_{j=1}^{i-1}[a_{j}>a_{i}]$$

显然逆序对数量为 $\sum bigger$

于是问题就转化为了动态维护 $bigger$。

手玩几组数据后我们可以发现,每轮冒泡 $bigger$ 都会有一下的变化:

$$bigger_{i}=\max\{bigger_{i}-1,0\}$$

于是树状数组维护即可

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

char buf[1 << 21], *p1 = buf, *p2 = buf;
#ifndef ONLINE_JUDGE
#define gc() getchar()
#else
#define gc() (p1 == p2 && (p2 = (p1 = buf) + fread(buf, 1, 1 << 21, stdin), p1 == p2) ? EOF : *p1++)
#endif
#define is() (ch >= '0' && ch <= '9')
template < typename Type >
void read(Type& a) {
    a = 0; bool f = 0; char ch;
    while (!(ch = gc(), is())) if (ch == '-') f = 1;
    while (is()) a = (a << 3) + (a << 1) + (ch ^ '0'), ch = gc();
    a = (f ? -a : a);
}

template < typename Type, typename... Args >
void read(Type& t, Args&... args) {
    read(t), read(args...);
}

using namespace std;

const int MAXN = 2e5 + 5;
int n, m, bigger[MAXN], bucket[MAXN], a[MAXN];
long long bit[MAXN], init_inver_tot;

void Update(int x, long long y) {
    for (; x <= n; x += x & -x) bit[x] += y;
}

long long GetAnswers(int x) {
    long long res = 0;
    for (; x; x -= x & -x) res += bit[x];
    return res;
}

signed main() {
    read(n, m);
    for (int i = 1; i <= n; ++i) read(a[i]), init_inver_tot += (bigger[i] = i - 1 - GetAnswers(a[i])), bucket[bigger[i]]++, Update(a[i], 1);
    memset(bit, 0, sizeof bit), Update(1,  init_inver_tot), init_inver_tot = 0;
    for (int i = 0; i < n; ++i) init_inver_tot += 1LL * bucket[i], Update(i + 1 + 1, init_inver_tot - n);
    for (int i = 0, op, x; i < m; ++i) {
        read(op, x);
        if (n - 1 < x) x = n - 1;
        if (op ^ 2) {
            if (a[x + 1] > a[x]) {
                swap(a[x], a[x + 1]);
                swap(bigger[x], bigger[x + 1]);
                Update(1, 1);
                Update(bigger[x + 1]++ + 2, -1);
            }
            else {
                swap(a[x], a[x + 1]);
                swap(bigger[x], bigger[x + 1]);
                Update(1, -1);
                Update(--bigger[x] + 2, 1);
            }
        }
        else printf("%lld\n", GetAnswers(x + 1));
    }
    return 0;
}

NOI-Online-T3-最小环

全场最水的一道题,但是可怕的心理作用还是让我放弃了这道题。

首先有一个显然的结论,我们需要把这 $n$ 个数分为 $\gcd(n,k)$ 个环。

虽说是显然但是不画画图还真的玩不动

给一下图示意一下:

Annotation 2020-03-11 094939.png

图中那个看起来像个五角星的东西其实就是个环

Annotation 2020-03-11 095158.png

这个图中有 $\gcd(n,k)$ 个环,这就是我们的结论。

具体到实现,我们采用的是预处理出所有答案。

还有 $k=0$ 的情况需要特殊处理一下

#include <cstdio>
#include <algorithm>
#include <queue>

using namespace std;

const int MAXN = 5e5 + 5;
vector < int > integer(MAXN);
vector < long long > ans(MAXN);

int gcd(int x, int y) {
    return !y ? x : gcd(y, x % y);
}

signed main() {
    int n, k;
    scanf("%d %d", &n, &k);
    for (int i = 1; i <= n; ++i) scanf("%d", &integer.at(i)), ans.at(0) += (long long)integer.at(i) * (long long)integer.at(i);
    sort(integer.begin() + 1, integer.begin() + 1 + n);
    for (int i = 1; i <= (n >> 1); ++i) {
        if (!(n % i)) {
            int t = n / i;
            vector < int > process(MAXN);
            int tot = 0;
            for (int j = 2; j < t; j += 2) process.at(++tot) = j;
            process.at(++tot) = t;
            for (int j = t - 1 - (t & 1); j > 0; j -= 2) process.at(++tot) = j;
            for (int j = t + 1; j <= n; ++j) process.at(j) = process.at(j - t) + t;
            for (int j = 1; j <= n; ++j)
                if (!(j % t)) ans.at(i) += (long long)integer.at(process.at(j)) * (long long)integer.at(process.at(j + 1 - t));
                else ans.at(i) += (long long)integer.at(process.at(j)) * (long long)(integer.at(process.at(j + 1)));
        }
    }
    for (int i = 0, x; i < k; ++i) scanf("%d", &x), printf("%lld\n", ans.at(x ? gcd(n, x) : 0));
    return 0;
}

总结

(其实我不是很会写这玩意儿)

果然心理素质还是不行……错过了T3这样的水题。

总体来说,把握住机会,把题目都当作大白菜(雾)。

然后就是多去做题吧,题量多少都不嫌多。

就这样(

普及组口胡

说了是口胡所以没代码不保证正确/xyx

至于题目难度这是NOIOL不是NOIpOL给了出题人放飞自我的空间。

T1:普通NOIp普及难度,各位巨佬随便切

T2: 基础多项式,会的人就很套路。不会的话就n方dp骗骗分

T3: 这道题我不太确定,应该是Floyd+矩阵。

完结撒六花