线段树

https://vjudge.net/contest/213797#problem/A习题集

求区间最大值

#include<cstdio>
#include<algorithm>
#include<cstring>
using namespace std;

const int maxmnode = 1 << 18;
const int maxm = 5e4 + 5;


struct NODE {
int value;
int left, right;
} node[maxmnode];

int index[maxm];

void buildtree(int i, int left, int right) {
node[i].left = left;
node[i].right = right;
node[i].value = 0;
if(left == right) {
    index[left] = i;
    return;
}
buildtree(i << 1, left, (int)(floor(left + right) / 2.0));
buildtree((i << 1) + 1, (int)(floor(left + right) / 2.0) + 1, right);
}

void updatetree(int ri) {
if(ri == 1) return;
int fi = ri / 2;
int a = node[fi << 1].value;
int b = node[ (fi << 1) + 1 ].value;
node[fi].value = max(a, b);
updatetree(fi);
}

int MAX;

void query(int i, int l, int  r) {
if(node[i].left == l && node[i].right == r) {
    MAX = max(MAX, node[i].value);
    return;
}
i = i << 1;
if(l <= node[i].right) {
    if(r <= node[i].right) query(i, l, r);
    else query(i, l ,node[i].right);
}
i++;
if(r >= node[i].left) {
    if(l >= node[i].left) query(i, l, r);
    else query(i, node[i].left, r);
}

}



int main() {
int n, m, q;
while(~scanf("%d%d", &n, &m)) {
    buildtree(1, 1, n);
    for(int i = 1; i <= n; i++) {
        scanf("%d", &q);
        node[ index[i] ].value = q;
        updatetree(index[i]);
    }
    char ch[10];
    int a,b;
    while(m--) {
        scanf("%s%d%d", ch, &a, &b);
        if(ch[0] == 'Q') {
            MAX = 0;
            query(1, a, b);
            printf("%d\n", MAX);
        }
        else {
            node[ index[a] ].value = b;//值无所谓,b;
            updatetree(index[a]);
        }
    }
}


return 0;
}

 

A - 敌兵布阵

 HDU - 1166

求区间和。

 

#include<cstdio>
#include<algorithm>
#include<cstring>
using namespace std;

const int maxm = 5e4 + 5;



int sum[maxm << 2];


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

void buildtree(int rt, int le, int ri) {
    if(le == ri) {
        scanf("%d", &sum[rt]);
        return;
    }
    int m = (le + ri) >> 1;
    buildtree(rt << 1, le, m);
    buildtree(rt << 1 | 1, m + 1, ri);
    pushup(rt);
}
void updatetree(int rt, int pos, int val, int le, int ri){
    if(le == ri){
        sum[rt] += val;
        return;
    }
    int m = (le + ri) >> 1;
    if(m >= pos) updatetree(rt << 1, pos, val, le, m);
    else updatetree(rt << 1 | 1, pos, val, m + 1, ri);
    pushup(rt);
}

int query(int rt, int l, int r, int le, int ri) {
    if(l <= le && r >= ri) {
        return sum[rt];
    }
    int ans = 0;
    int m = (le + ri) >> 1;
//    if(r <= m) ans += query(rt << 1, l, r, le, m);
//    else if(l > m) ans += query(rt << 1 | 1, l, r, m + 1, ri);
//    else {
//        ans += query(rt << 1, l, m, le, m);
//        ans += query(rt << 1 | 1, m + 1, r, m + 1, ri);
//    }
    if(l <= m) ans += query(rt << 1, l, r, le, m);
    if(r > m) ans += query(rt << 1 | 1, l, r, m + 1, ri);
    return ans;
}
int main() {
    int n, t;
    scanf("%d", &t);
    for(int k = 1; k <= t; k++) {
        printf("Case %d:\n", k);
        scanf("%d", &n);
        buildtree(1, 1, n);
        char ch[10];
        int a, b;
        while(~scanf("%s", ch)) {
            if(ch[0] == 'E') break;
            scanf("%d%d", &a, &b);
            if(ch[0] == 'A') {
                updatetree(1, a, b, 1, n);
            }
            else if(ch[0] == 'S') {
                updatetree(1, a, -b, 1, n);
            }
            else {
                printf("%d\n", query(1, a, b, 1, n));
            }
        }
    }
    return 0;
}

 

 

 

 I Hate It

 HDU - 1754 

区间最大值。

 

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

using namespace std;
const int maxm = 2e5 + 5;

int sum[maxm << 2];

void pushup(int rt) {
    sum[rt] = max(sum[rt << 1], sum[rt << 1 | 1]);
}

void buildtree(int rt, int le, int re) {
    if(le == re) {
        scanf("%d", &sum[rt]);
        return;
    }
    int m = (le + re) >> 1;
    buildtree(rt << 1, le, m);
    buildtree(rt << 1 | 1, m + 1, re);
    pushup(rt);
}

void update(int rt, int pos, int val, int le, int re) {
    if(le == re) {
        sum[rt] = val;
        return;
    }
    int m = (le + re) >> 1;
    if(pos <= m) update(rt << 1, pos, val, le, m);
    else update(rt << 1 | 1, pos, val, m + 1, re);
    pushup(rt);
}
int query(int rt, int l ,int r, int le, int re) {
    if(l <= le && r >= re) {
        return sum[rt];
    }
    int m = (le + re) >> 1;
    int ans1 = 0;
//    if(r <= m) ans1 = query(rt << 1, l, r, le, m);
//    else if(l > m) ans1 = query(rt << 1 | 1, l, r, m + 1, re);
//    else ans1 = max(query(rt << 1, l, m, le, m), query(rt << 1 | 1, m + 1, r, m + 1, re));
    if(l <= m) ans1 = max(ans1, query(rt << 1, l, r, le, m));
    if(r > m) ans1 = max(ans1, query(rt << 1 | 1, l, r, m + 1, re));
    return ans1;
}
int n, m;
int main() {
    while(~scanf("%d%d", &n, &m)) {
        buildtree(1, 1, n);
        char ch[3];
        int x, y;
        while(m--) {
            scanf("%s", ch);
            if(ch[0] == 'Q') {
                scanf("%d%d", &x, &y);
                printf("%d\n", query(1, x, y, 1, n));
            }
            else {
                scanf("%d%d", &x, &y);
                update(1, x, y, 1, n);
            }
        }
    }

    return 0;
}

 

 

 

 区间修改加区间求和

A Simple Problem with Integers

 POJ - 3468 

 

#include<cstdio>
#include<cstring>
#include<algorithm>
using namespace std;
typedef long long ll;
const int maxm = 1e5 + 5;
ll sum[maxm << 2], add[maxm << 2];


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

void pushdown(int rt, int m) {
    if(add[rt]) {
        add[rt << 1] += add[rt];
        add[rt << 1 | 1] += add[rt];
        sum[rt << 1] += add[rt] * (m - (m >> 1));
        sum[rt << 1 | 1] += add[rt] * (m >> 1);
        add[rt] = 0;
    }
}

void buildtree(int le, int re, int rt) {
    add[rt] = 0;
    if(le == re) {
        scanf("%lld", &sum[rt]);
        return;
    }
    int m = (le + re) >> 1;
    buildtree(le, m, rt << 1);
    buildtree(m + 1, re,  rt << 1 | 1);
    pushup(rt);
}

void updatetree(int c, int l ,int r, int rt, int le, int re) {
    if(l <= le && r >= re) {
        add[rt] += c;
        sum[rt] += (ll) c * (r - l + 1);
        return;
    }
//    if(le == re) return;
    pushdown(rt, re - le + 1);
    int m = (le + re) >> 1;
    if(r <= m) updatetree(c, l, r, rt << 1, le, m);
    else if(l > m) updatetree(c, l, r, rt << 1 | 1, m + 1, re);
    else {
        updatetree(c, l, m, rt << 1, le, m);
        updatetree(c, m + 1, r, rt << 1 | 1, m + 1, re);
    }
    pushup(rt);
}

ll query(int l, int r, int rt, int le, int re) {
    if(l <= le && r >= re) return sum[rt];
    pushdown(rt, re - le + 1);
    int m = (le + re) >> 1;
    ll res = 0;
    if(r <= m) res += query(l, r, rt << 1, le, m);
    else if(l > m) res += query(l, r, rt << 1 | 1, m + 1, re);
    else {
        res += query(l, m, rt << 1, le, m);
        res += query(m + 1, r, rt << 1 | 1, m + 1, re);
    }
    return res;
}

int main() {
    int n, m;
    while(~scanf("%d%d", &n, &m)) {
        buildtree(1, n, 1);
        while(m--) {
            char ch[3];
            scanf("%s", ch);
            int a, b, c;
            if(ch[0] == 'Q') {
                scanf("%d%d", &a, &b);
                printf("%lld\n", query(a, b, 1, 1, n));
            }
            else {
                scanf("%d%d%d", &a, &b, &c);
                updatetree(c, a, b, 1, 1, n);
            }
        }
    }
    return 0;
}

 

 

树状数组做法

https://blog.csdn.net/SSL_ZYC/article/details/81940902

这个博客里有讲解。

可以当做树状数组区间修改,区间求和的模板。

 

#include<iostream>
#include<cstdio>
#include<algorithm>
#include<cmath>
#include<cstring>
#include<vector>
#define maxx 100005
using namespace std;
int n,q;
long long a[maxx];
long long  c[2][maxx];
void change(int k,int x,int p)
{
    for(;x<=n;x+=x&-x)c[k][x]+=p;
}

long long query(int k,int x)
{
    long long ans=0;
    for(;x;x-=x&-x)ans+=c[k][x];
    return ans;
}
long long query(int x)
{
    return a[x]+(x+1)*query(0,x)-query(1,x);
}
int main()
{
    cin>>n>>q;
    for(int i=1;i<=n;i++)
    {
        scanf("%lld",a+i);
        a[i]+=a[i-1];
    }
    char s[3];
    int l,r,d;
    while(q--)
    {
        scanf("%s",s);
        if(s[0]=='Q')
        {
            scanf("%d%d",&l,&r);
            printf("%lld\n",query(r)-query(l-1));
        }
        else
        {
            scanf("%d%d%d",&l,&r,&d);
            change(0,l,d);
            change(0,r+1,-d);
            change(1,l,l*d);
            change(1,r+1,-(r+1)*d);
        }
    }
    return 0;
}

 

 

 

 Mayor's posters  更染色问题很相像。区间染色,求最终某个区间内有几个颜色。

 离散化

 

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

using namespace std;
const int maxm = 2e5 + 5;
typedef pair<int, int> pii;


int lazy[maxm << 3], col[maxm << 3], vis[maxm << 2];
int res;
int a[maxm << 3], b[maxm << 3];

void pushdown(int rt) {
    if(lazy[rt]) {
        lazy[rt << 1] = lazy[rt << 1 | 1] = lazy[rt];
        col[rt << 1] = col[rt << 1 | 1] = lazy[rt];
    }
    lazy[rt] = 0;
}

void pushup(int rt) {
    if(col[rt << 1] == 0 || col[rt << 1 | 1] == 0) {
        col[rt] = 0;
    }
    if(col[rt << 1] != col[rt << 1 | 1]) {
        col[rt] = 0;
    }
    else col[rt] = col[rt << 1];
}

void build(int rt, int le, int re) {
    col[rt] = 0;
    lazy[rt] = 0;
    if(le == re) {
        return;
    }
    int m = (le + re) >> 1;
    build(rt << 1, le, m);
    build(rt << 1 | 1, m + 1, re);
}

void update(int rt, int l, int r, int val, int le, int re) {
    if(l <= le && r >= re) {
        lazy[rt] = val;
        col[rt] = val;
        return;
    }
    pushdown(rt);
    int m = (le + re) >> 1;
    if(r <= m) {
        update(rt << 1, l, r, val, le, m);
    }
    else if(l > m) {
        update(rt << 1 | 1, l, r, val, m + 1, re);
    }
    else {
        update(rt << 1, l, m, val, le, m);
        update(rt << 1 | 1, m + 1, r, val, m + 1, re);
    }
    pushup(rt);
}

void query(int rt, int l ,int r, int le, int re) {
    if(l <= le && r >= re && col[rt] && !vis[ col[rt] ]) {
        res++;
        vis[ col[rt] ] = 1;
        return;
    }
  if(l <= le && r >= re && col[rt] && vis[ col[rt] ]) return;
if(le == re) { return; }//这个因为前面If判断并不完全包括这种情况,所以要特判一下,防止无限递归。可能这一整个数都没被更新过,所以col都为零,往下走。 pushdown(rt); int m = (le + re) >> 1; if(r <= m) query(rt << 1, l, r, le, m); else if(l > m) query(rt << 1 | 1, l, r, m + 1, re); else { query(rt << 1, l, m, le, m); query(rt << 1 | 1, m + 1, r, m + 1, re); } } int t, n; int main() { scanf("%d", &t); while(t--) { scanf("%d", &n); memset(vis, 0, sizeof(vis)); memset(lazy, 0, sizeof(lazy)); memset(col, 0, sizeof(col)); for(int i = 1; i <= 2 * n; i++) { scanf("%d", &a[i]); a[i + 2 * n] = a[i] + 1;
//看下面的样例。 b[i]
= a[i]; } sort(a + 1, a + 4 * n + 1); int ant = unique(a + 1, a + 4 * n + 1) - a; for(int i = 1; i <= 2 * n; i++) { b[i] = lower_bound(a + 1, a + ant, b[i]) - a; } build(1, 1, ant); for(int i = 1; i <= 2 * n; i += 2) { update(1, b[i], b[i + 1], i, 1, ant); } res = 0; query(1, 1, ant, 1, ant); printf("%d\n", res); } return 0; }
/*
该题目的数据有点水,错误的离散化也可以过
这里注意在离散化的时候不仅要保留相对大小关系,还要保证数据之间的连续性
例如对于这组数据
1 3 1 10 1 4 6 10
离散化完之后是四个点,4和6之间的5在离散化的过程中消失了,但是因为第一次海报是1 10,所以5是有颜色的,
但是维护离散化后的线段树没有该点的数据,普通离散化之后认为4和6是连续的了,应该每个点都插入一个+1的点,这样离散化能够保证连续性
*/

 

 

 

 Tunnel Warfare 题意:开始村庄是联通的,随意炸毁,或者修复刚炸毁的那个,,然后查询某个村庄的联通村庄数。村庄1-n.

 HDU - 1540 线段树单点更新,求最大连续子段和。

线段树要记住pushup是从下面开始更新的

 

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

using namespace std;
const int maxm = 50005;
struct NODE {
    int l ,r;
    int li, ri, mi;
    //从左边开始的最大连续和,右边的,最终的最大连续和(也就是最大的)。
    int mid() {
        return (l + r) >> 1;
    }
} node[maxm << 2];
int n, k;
int ro[maxm], num;
char ch[2];


void pushup(int ri) {
    if(node[ri << 1].li == node[ri << 1].r - node[ri << 1].l + 1) {
        node[ri].li = node[ri << 1].li + node[ri << 1 | 1].li;
    }
    //如果左孩子的左连续区间是满的(事实上其他的也是满的),那么父节点的左连续区间就等于左孩子的加上右孩子的(因为连在一起了)。
    else {
        node[ri].li = node[ri << 1].li;
     //不是满的就等于左孩子的左连续长度
    }
    if(node[ri << 1 | 1].ri == node[ri << 1 | 1].r - node[ri << 1 | 1].l + 1) {
        node[ri].ri = node[ri << 1 | 1].ri + node[ri << 1].ri;
    }
    else {
        node[ri].ri = node[ri << 1 | 1].ri;
    }
    node[ri].mi = max( max(node[ri << 1].mi, node[ri << 1 | 1].mi), node[ri << 1].ri + node[ri << 1 | 1].li );
    //最终的连续长度等于左边的最大连续长度,右边的最大连续长度,左边的右连续长度加上右边的左连续长度。
}

void build(int ri, int l, int r) {
    node[ri].l = l;
    node[ri].r = r;
    node[ri].li = node[ri].ri = node[ri].mi = r - l + 1;
    //开始时满的
    if(node[ri].l == node[ri].r) {
        node[ri].li = node[ri].ri = node[ri].mi = 1;
    //叶子节点是1.
        return;
    }
    int m = node[ri].mid();
    build(ri << 1, l, m);
    build(ri << 1 | 1, m + 1, r);
    pushup(ri);
}

void update(int ri, int pos, int flag) {
    if(node[ri].l == node[ri].r) {
        if(flag) {
    //在叶子节点更新。
            node[ri].li = node[ri].mi = node[ri].ri = 1;
        }
        else {
            node[ri].li = node[ri].mi = node[ri].ri = 0;
        }
        return;
    }
    int m = node[ri].mid();
    if(pos <= m) {
        update(ri << 1, pos, flag);
    }
    else {
        update(ri << 1 | 1, pos, flag);
    }
    pushup(ri); 
}

int query(int ri, int pos) {
    if(node[ri].l == node[ri].r || node[ri].mi == node[ri].r - node[ri].l + 1 || node[ri].mi == 0) {
        return node[ri].mi;
    }
    //如果是满的,为零,或者到了叶子节点就返回值。
    int m = node[ri].mid();
    if(pos <= m) {
        if(pos >= node[ri << 1].r - node[ri << 1].ri + 1) {
            return node[ri << 1].ri + node[ri << 1 | 1].li;
        }
    //如果在自己的左孩子右最大连续区间内,那么最大的连续长度就是自己的右边加上右边兄弟的左边的。(因为如果继续往下查肯定更小,而且如果自己左边的连续区间与右边的连续区间重合,那么这个区间就算完了。)
//第一种,左孩子的左区间与右区间是分开的,那么这个点在左孩子中的最大连续字段就是右区间,然后自然可以加上右孩子的左区间。
//第二种,左孩子的左区间与右区间是连在一起的,这种情况说明整个区间都是相连的,左右连续区间都是整个左孩子的长度。
else { return query(ri << 1, pos); //继续查询左孩子。 } } else { if(pos <= node[ri << 1 | 1].l + node[ri << 1 | 1].li - 1) { return node[ri << 1].ri + node[ri << 1 | 1].li; } else { return query(ri << 1 | 1, pos); } } } int main() { while(~scanf("%d%d", &n, &k)) { build(1, 1, n); int x; while(k--) { scanf("%s", ch); if(ch[0] == 'D') { scanf("%d", &x); ro[num++] = x; //记录刚炸毁的那条。 update(1, x, 0); } else if(ch[0] == 'Q') { scanf("%d", &x); int ans = query(1, x); printf("%d\n", ans); } else { int y = ro[--num]; update(1, y, 1); } } } return 0; }

 

 

I - Infinite Inversions CodeForces - 540E

题意:一个无限的自然数序列进行n次操作,每次交换其中2个位置上的数,求逆序数个数。

题解:操作数很大,如果直接求逆序数个数会超时。而最多有1e5的操作次数,那么可以把点离散化,两个交换点之间加入新的点,该点的权值即是两个点之间的点的个数。离散化后用树状数组直接求其逆序数。

相当于把区间转化成一个点,点的权值表示该区间里面元素的个数,然后在求逆序对。

http://codeforces.com/problemset/problem/540/E

#include<cstdio>
#include<cstring>
#include<algorithm>
using namespace std;
typedef long long ll;
const int maxm = 5e5 + 5;
struct Node
{
    ll a,b;
}node[maxm];
ll n, cnt, len, tot;
ll p[maxm], x[maxm], y[maxm], w[maxm], sum[maxm];
ll lowbit(ll x)
{
    return x & -x;
}
void add(ll x,ll v)
{
    while(x <= maxm)
    {
        sum[x] += v;
        x += lowbit(x);
    }
}
ll getsum(ll x)
{
    ll s = 0;
    while(x > 0)
    {
        s += sum[x];
        x -= lowbit(x);
    }
    return s;
}
int main() {
scanf("%lld", &n);
for(int i = 1; i <= n; i++) {
    scanf("%lld%lld", &node[i].a, &node[i].b);
    x[++cnt] = node[i].a;
    x[++cnt] = node[i].b;
}
sort(x + 1, x + cnt + 1);
len = unique(x + 1, x + cnt + 1) - (x + 1);
//unique函数排除重复元素,返回第一个重复元素的位置。
for(int i = 1; i <= len; i++) { y[++tot] = x[i]; w[tot] = 1; if(x[i] + 1 < x[i + 1]) { y[++tot] = x[i] + 1; w[tot] = x[i + 1] - x[i] - 1; } } for(int i = 1; i <= tot; i++) p[i] = i; for(int i = 1; i <= n; i++) { ll p1 = lower_bound(y + 1, y + tot + 1, node[i].a) - y; ll p2 = lower_bound(y + 1, y + tot + 1, node[i].b) - y;
  //调换顺序。虽然是区间的那种点没有交换,但是后面求逆序数的时候会算到那些是区间的点。 swap(p[p1], p[p2]); }
//上面都是离散化操作。 ll res
= 0; for(int i = tot; i >= 1; i--) { res += w[p[i]] * getsum(p[i]); add(p[i],w[p[i]]);
//从后面开始,相当于每个点都求一下逆序数。 } printf(
"%lld\n", res); return 0; }

 

M - Decomposition into Good Strings

 Gym - 100971M 

题意:好字符串就是一个字符串正好含有k个不同的字符,求该字符串的前缀,能够最少分成几个好字符串的拼接。

解法:先求出每一位字符往前形成好字符串的范围,l[i] 到r[i],注意求左边是如果是k+1个的话要加2(手动模拟一下)其他情况只是加1.然后得到最大值之后,就一个一个遍历,同时更新该点的最小值,如果能够延伸到

1的话,那肯定最小好字符串就是1.然后注意这个线段树区间是从零开始,因为查询时端点要用到0,把他设置为inf,因为是从1开始,所以只不会变,主要是为了防止越界错误。

#include<bits/stdc++.h>

using namespace std;
typedef long long ll;
typedef pair<int, int> pii;
const int inf = 0x3f3f3f3f;
const int mod = 1e9 + 7;
const int maxm = 2e5 + 5;
int sum[maxm << 2];
struct Node {
    int l, r;
    int mid() {
        return (l + r) >> 1;
    }
} node[maxm << 2];
int k;
char ch[maxm];
int num[30];
int l[maxm], r[maxm];
int c[maxm];
void pushup(int ri) {
//    sum[ri] = min(sum[ri], min(sum[ri << 1], sum[ri << 1 | 1]));
sum[ri] = min(sum[ri << 1], sum[ri << 1 | 1]);
}

void build(int ri, int ll, int rr) {
//    printf("sn\n");
    node[ri].l = ll, node[ri].r = rr;
    sum[ri] = inf;
    if(ll == rr) return;
    int m = node[ri].mid();
    build(ri << 1, ll, m);
    build(ri << 1 | 1, m + 1, rr);
}

void update(int ri, int ind, int val) {
//    printf("zx\n");
    if(node[ri].l == node[ri].r) {
        sum[ri] = val;
        return;
    }
    int m = node[ri].mid();
    if(ind <= m) {
        update(ri << 1, ind, val);
    }
    else if(ind > m) {
        update(ri << 1 | 1, ind, val);
    }
    pushup(ri);
}

int query(int ri, int ll, int rr) {
//    printf("dc\n");
    if(node[ri].l == ll && node[ri].r == rr) {
        return sum[ri];
    }
    int m = node[ri].mid();
    int ans = inf;
    if(rr <= m) {
        ans = min(ans, query(ri << 1, ll, rr));
    }
    else if(ll > m) {
        ans = min(ans, query(ri << 1 | 1, ll, rr));
    }
    else {
        ans = min(ans, query(ri << 1, ll, m));
        ans = min(ans, query(ri << 1 | 1, m + 1, rr));
    }
    return ans;
}

int main() {
scanf("%d", &k);
scanf("%s", ch + 1);
int len = strlen(ch + 1);
int val = 0, t = len;
memset(l, -1, sizeof(l));
memset(r, -1, sizeof(r));
for(int i = len; i >= 1; i--) {
    while(val <= k && t > 0) {
        if(num[ch[t] - 'a' ] == 0) val++;
        num[ch[t] - 'a' ]++;
        t--;
    }
    if(val == k + 1) {
        l[i] = t + 2;
    }
    else if(val == k && t == 0) {
        l[i] = t + 1;
    }
    if(num[ch[i] - 'a' ] == 1) val--;
    num[ch[i] - 'a' ]--;
}
memset(num, 0, sizeof(num));
val = 0, t = len;
for(int i = len; i >= 1; i--) {
    while(val < k && t > 0) {
        if(num[ch[t] - 'a' ] == 0) val++;
        num[ch[t] - 'a' ]++;
        t--;
    }
    if(val == k) {
        r[i] = t + 1;
    }
    if(num[ch[i] - 'a' ] == 1) val--;
    num[ch[i] - 'a' ]--;
}
//for(int i = 1; i <= len; i++) {
//    printf("%d %d\n", l[i], r[i]);
//}
build(1, 0, len);
int mi = 0;
for(int i = 1; i <= len; i++) {
//printf("cac\n");
    if(l[i] == -1) {
        c[i] = -1;
    }
    else if(l[i] == 1) {
        c[i] = 1;
        update(1, i, 1);
    }
    else {
//        printf("buics\n");
//        int ll ,rr;

        mi = query(1, l[i] - 1, r[i] - 1);
        if(mi != inf) {
            c[i] = mi + 1;
            update(1, i, mi + 1);
        }
        else c[i] = -1;
    }
}
for(int i = 1; i <= len; i++) {
    printf("%d%c", c[i], i == len ? '\n': ' ');
}
return 0;
}

 然后这一题还可以用dp做,

相当于还是尺取去寻找“好字符串”,然后如果i - 1这个点与前面的字符可以组成好字符串的话,就可以用它来更新t这个点一级t后面可以组成好字符串的点,标准的dp思想,用前面的点去更新后面的点。

#include<bits/stdc++.h>

using namespace std;
typedef long long ll;
typedef pair<int, int> pii;
const int inf = 0x3f3f3f3f;
const int mod = 1e9 + 7;
const int maxm = 2e5 + 5;
int k;
char ch[maxm];
int dp[maxm];
int c[maxm];
int num[30];
int main() {
scanf("%d", &k);
scanf("%s", ch + 1);
int n = strlen(ch + 1);
int t = 1, cnt = 0;
memset(dp, inf, sizeof(dp));
memset(num, 0, sizeof(num));
dp[0] = 0;
for(int i = 1; i <= n; i++) {
    while(t <= n && cnt < k) {
        if(num[ch[t] - 'a' ] == 0) cnt++;
        num[ch[t] - 'a' ]++;
        t++;
    }
    if(cnt == k) {
        if(dp[i - 1] != inf){
            dp[t - 1] = min(dp[t - 1], dp[i - 1] + 1);
//                if(t == 6) printf("%d %d %d\n", dp[t], i - 1, dp[i - 1]) ;
            while(t <= n) {
                if(num[ch[t] - 'a' ] == 0) break;
                dp[t] = min(dp[i - 1] + 1, dp[t]);
                num[ch[t]  - 'a']++;
                t++;
            }
        }
    }
    if(num[ch[i] - 'a' ] == 1) cnt--;
    num[ch[i] - 'a' ]--;
}
for(int i = 1; i <= n; i++) {
    printf("%d%c", dp[i] == inf ? -1 : dp[i], i == n ? '\n' : ' ');
}
return 0;
}

 http://acm.hdu.edu.cn/showproblem.php?pid=6514 吉司机线段树

 

https://blog.csdn.net/LSD20164388/article/details/89413657

题意:

给你n,m(n,m<=5e5),表示有编号1~n的人,m个区间

对于每个区间[l,r],表示编号为[l,r]的人之间任意两两配对的方法总数

要求对于每个区间,输出其任意两两配对的方法总数减去前面的区间已经有的选法总数

例如[2,5] 可以有 23 24 25 34 35 45      6种

再给你[4,6] 则只有 46 56 2种(45在上面出现过了)

思路:

把这题当图论来想。。。一段区间一定是连续的

令a[i]=k表示(k,i)(k+1,i)...,(i-1,i)已经被算入答案

初始化a[i]=i

对于每次给定的区间[ql,qr],如果max{a[ql],a[ql+1],...,a[qr]}中的最大值<=ql,那么表示区间i属于[ql,qr]中所有(ql,i)的方法总数已经计算过了

如果max{a[ql],a[ql+1],...,a[qr]}中的最大值>ql 那么就说明至少存在一个i(a[i]>ql)属于[ql,qr],使(ql,i)(ql+1,i),...,(a[i]-1,i)可以被算入答案,然后更新a[i]=ql

发现上述过程可以用一个线段树维护区间最大值。。。但是因为要计算答案,每次我们必须更新到底。。。(max{a[ql],a[ql+1],...,a[qr]}中的最大值<=ql,那么就可以直接return 0 了,否则继续往下更新)

在更新的同时统计答案

这就是传说中的吉司机线段树。。。

#include<bits/stdc++.h>
#define ll long long
#define inf 0x3f3f3f3f
#define lson  (i<<1)
#define rson  (i<<1|1)
#define mid   ((l+r)>>1)
#define rep(i,a,b) for(register int i=(a);i<=(b);i++)
#define dep(i,a,b) for(register int i=(a);i>=(b);i--)
using namespace std;
const int maxn=500010;
int n,m,k;
struct node
{
    int ma,v;
}a[maxn<<2];
void build(int i,int l,int r)
{
    if(l==r)
    {
        a[i].ma=a[i].v=l;
        return ;
    }
    build(lson,l,mid);
    build(rson,mid+1,r);
    a[i].ma=max(a[lson].ma,a[rson].ma);
}
ll update(int i,int l,int r,int ql,int qr,int x)
{
    ll res=0;
    if(x>=a[i].ma) return res;
    if(l==r)
    {
        res+=a[i].v-x;
        //cout<<i<<" "<<ql<<" "<<qr<<" "<<res<<endl;
        a[i].ma=a[i].v=x;
        return res;
    }
    if(ql<=l&&r<=qr)
    {
        res+=update(lson,l,mid,ql,mid,x);
        res+=update(rson,mid+1,r,mid+1,qr,x);
        a[i].ma=max(a[lson].ma,a[rson].ma);
        return res;
    }
    if(ql<=mid) res+=update(lson,l,mid,ql,min(qr,mid),x);
    if(qr>mid) res+=update(rson,mid+1,r,max(mid+1,ql),qr,x);
    a[i].ma=max(a[lson].ma,a[rson].ma);
    return res;
}
int main()
{
    while(scanf("%d%d",&n,&m)!=EOF)
    {
        build(1,1,n);
        rep(i,1,m)
        {
            int x,y;
            scanf("%d%d",&x,&y);
            printf("%lld\n",update(1,1,n,x,y,x));
        }
    }
    return 0;
}

 

 

https://blog.csdn.net/MrBird_to_fly/article/details/77141881  吉司机线段树题目

http://acm.hdu.edu.cn/showproblem.php?pid=5306   HDU 5306


0 x y t: For every xiy, we use min(ai,t) to replace the original ai's value.
1 x y: Print the maximum value of ai that xiy.
2 x y: Print the sum of ai that xiy.

case 1:t大于最大值,此时区间不变; 
case 2:t小于严格次大值,此时至少把最大值和次大值变得相同,即使得区间变得相同,允许暴力更新; 
case 3:t大于严格次大值,小于最大值,这里可以打懒标记。

考虑查询,只需维护最大值,最大值个数,严格次大值即可。

复杂度为nlogn或nlogn2.

 

//吉司机宇宙线段树之王!
#include <bits/stdc++.h>
using namespace std;
const int maxn=1000005;
typedef long long ll;

int mx[maxn<<2];//区间最大值
int cnt[maxn<<2];//最大值个数
int se[maxn<<2];//区间严格次小值,就是必须小于最大值
int lazy[maxn<<2];
ll sum[maxn<<2];//区间和
int a[maxn];
int n, m;

void putlazy(int u, int t){
    sum[u]-=1LL*cnt[u]*(mx[u]-t);
    mx[u]=t;
    lazy[u]=t;
}

void pushdown(int u){
    if(lazy[u]==-1)return;
    if(mx[2*u]>lazy[u]){
        sum[2*u]-=1LL*cnt[2*u]*(mx[2*u]-lazy[u]);
        mx[2*u]=lazy[u];
        lazy[2*u]=lazy[u];
    }
    if(mx[2*u+1]>lazy[u]){
        sum[2*u+1]-=1LL*cnt[2*u+1]*(mx[2*u+1]-lazy[u]);
        mx[2*u+1]=lazy[u];
        lazy[2*u+1]=lazy[u];

    }
    lazy[u]=-1;
}

void pushup(int u){
    if(mx[2*u]==mx[2*u+1]){
        mx[u]=mx[2*u];
        cnt[u]=cnt[2*u]+cnt[2*u+1];
        se[u]=max(se[2*u], se[2*u+1]);
        sum[u]=sum[2*u]+sum[2*u+1];
    }
    else if(mx[2*u]>mx[2*u+1]){
        mx[u]=mx[2*u];
        cnt[u]=cnt[2*u];
        se[u]=max(se[2*u], mx[2*u+1]);
        sum[u]=sum[2*u]+sum[2*u+1];
    }
    else {
        mx[u]=mx[2*u+1];
        cnt[u]=cnt[2*u+1];
        se[u]=max(mx[2*u], se[2*u+1]);
        sum[u]=sum[2*u]+sum[2*u+1];
    }
}

void build(int u, int l, int r){
    lazy[u]=-1;
    if(l==r){
        mx[u]=sum[u]=a[l];
        cnt[u]=1;
        se[u]=-1;
        return;
    }
    int mid=(l+r)>>1;
    build(2*u, l, mid);
    build(2*u+1, mid+1, r);
    pushup(u);
}

void update(int u, int ql, int qr, int t, int l, int r){
    if(mx[u]<=t)return;
    if(ql<=l && r<=qr && se[u] < t){
        putlazy(u, t);
        return;
    }
    if(l == r) return;
    pushdown(u);
    int mid=(l+r)>>1;
    if(qr <= mid) update(u << 1, ql, qr, t, l, mid);
    else if(ql > mid) update(u << 1 | 1, ql, qr, t, mid + 1, r);
    else update(u << 1, ql, mid, t, l, mid), update(u << 1 | 1, mid + 1, qr, t, mid + 1, r);
//    update(2*u, ql, qr, t, l, mid);
//    update(2*u+1, ql, qr, t, mid+1, r);
    pushup(u);
}

int getmx(int u, int ql, int qr, int l, int r){
//    if(ql>r||qr<l)return 0;
    if(ql<=l&&r<=qr)return mx[u];
    pushdown(u);
    int mid=(l+r)>>1;
    int ans=0;
    if(qr <= mid) ans = max(ans, getmx(u << 1, ql, qr, l, mid));
    else if(ql > mid) ans = max(ans, getmx(u << 1 | 1, ql, qr, mid + 1, r));
    else {
        ans = max(ans, getmx(u << 1, ql, mid, l, mid));
        ans = max(ans, getmx(u << 1 | 1, mid + 1, qr, mid + 1, r));
    }
//    ans=max(ans, getmx(2*u, ql, qr, l, mid));
//    ans=max(ans, getmx(2*u+1, ql, qr, mid+1, r));
    return ans;
}

ll getsum(int u, int ql, int qr, int l, int r){
//    if(ql>r||qr<l)return 0;
    if(ql<=l&&r<=qr)return sum[u];
    pushdown(u);
    int mid=(l+r)>>1;
    ll ans=0;
    if(qr <= mid) ans += getsum(u << 1, ql, qr, l, mid);
    else if(ql > mid) ans += getsum(u << 1 | 1, ql, qr, mid + 1, r);
    else {
        ans += getsum(u << 1, ql, mid, l, mid);
        ans += getsum(u << 1 | 1, mid + 1, qr, mid + 1, r);
    }
//    ans+=getsum(2*u, ql, qr, l, mid);
//    ans+=getsum(2*u+1, ql, qr, mid+1, r);
    return ans;
}

int main(){
    int T;
    scanf("%d", &T);
    while(T--){
        scanf("%d%d", &n, &m);
        for(int i=1;i<=n;i++)scanf("%d", &a[i]);
        build(1, 1, n);
        for(int i=1;i<=m;i++){
            int tag;
            scanf("%d", &tag);
            if(tag==0){
                int x, y, t;
                scanf("%d%d%d", &x, &y, &t);
                update(1, x, y, t, 1, n);
            }
            else if(tag==1){
                int x, y;
                scanf("%d%d", &x, &y);
                printf("%d\n", getmx(1, x, y, 1, n));
            }
            else {
                int x, y;
                scanf("%d%d", &x, &y);
                printf("%lld\n", getsum(1, x, y, 1, n));
            }
        }
    }
}

 

 

https://www.cnblogs.com/GXZlegend/p/8349722.html

https://www.lydsy.com/JudgeOnline/status.php?user_id=downrainsun 提交的地方。

 

维护一个长度为N的序列a,现在有三种操作:
1)给出参数U,V,C,将a[U],a[U+1],...,a[V-1],a[V]都赋值为C。
2)给出参数U,V,C,对于区间[U,V]里的每个数i,将a[i]赋值为max(a[i]+C,0)。
3)给出参数U,V,输出a[U],a[U+1],...,a[V-1],a[V]里值为0的数字个数。

 

Input

第一行包含两个正整数N,M(1<=N,M<=300000),分别表示序列长度和操作个数。
第二行包含N个整数,其中第i个数表示a[i](0<=a[i]<=10^9),描述序列的初始状态。
接下来M行描述M个操作,保证1<=U<=V<=N,对于操作1,0<=C<=10^9,对于操作2,|C|<=10^9。

 

//吉司机线段树
//牛逼啊
#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
const int maxn=300005;

int n, m;
ll mn[maxn<<2];
int cnt[maxn<<2];
ll se[maxn<<2];
ll lzcut[maxn<<2];
ll lzadd[maxn<<2];
int a[maxn];

void putcut(int u, ll c){
    mn[u]=c;
    lzcut[u]=c;
}

void putadd(int u, ll c){
    lzadd[u]+=c;
    if(lzcut[u]!=-1)lzcut[u]+=c;
    mn[u]+=c;
    if(se[u]!=-1)se[u]+=c;
}

void pushdown(int u){
    if(lzadd[u]){
        putadd(2*u, lzadd[u]);
        putadd(2*u+1, lzadd[u]);
        lzadd[u]=0;
    }
    if(lzcut[u]!=-1){
        if(mn[2*u]<lzcut[u]){
            mn[2*u]=lzcut[u];
            lzcut[2*u]=lzcut[u];
        }
        if(mn[2*u+1]<lzcut[u]){
            mn[2*u+1]=lzcut[u];
            lzcut[2*u+1]=lzcut[u];
        }
        lzcut[u]=-1;
    }
}

void pushup(int u){
    if(mn[2*u]==mn[2*u+1]){
        mn[u]=mn[2*u];
        cnt[u]=cnt[2*u]+cnt[2*u+1];
        if(se[2*u]==-1&&se[2*u+1]==-1)se[u]=-1;
        else if(se[2*u]==-1)se[u]=se[2*u+1];
        else if(se[2*u+1]==-1)se[u]=se[2*u];
        else se[u]=min(se[2*u], se[2*u+1]);
    }
    else if(mn[2*u]<mn[2*u+1]){
        mn[u]=mn[2*u];
        cnt[u]=cnt[2*u];
        if(se[2*u]==-1){
            se[u]=mn[2*u+1];
        }
        else {
            se[u]=min(mn[2*u+1], se[2*u]);
        }
    }
    else {
        mn[u]=mn[2*u+1];
        cnt[u]=cnt[2*u+1];
        if(se[2*u+1]==-1){
            se[u]=mn[2*u];
        }
        else {
            se[u]=min(mn[2*u], se[2*u+1]);
        }
    }
}

void build(int u, int l, int r){
    lzcut[u]=-1;
    lzadd[u]=0;
    if(l==r){
        mn[u]=a[l];
        cnt[u]=1;
        se[u]=-1;
        return;
    }
    int mid=(l+r)/2;
    build(2*u, l, mid);
    build(2*u+1, mid+1, r);
    pushup(u);
}

void cover(int u, int ql, int qr, int c, int l, int r){
    if(r<ql||qr<l)return;
    if(ql<=l&&r<=qr&&se[u]==-1){
        putadd(u, (ll)c-mn[u]);
        return;
    }
    int mid=(l+r)/2;
    pushdown(u);
    cover(2*u, ql, qr, c, l, mid);
    cover(2*u+1, ql, qr, c, mid+1, r);
    pushup(u);
}

void add(int u, int ql, int qr, int c, int l, int r){
    if(r<ql||qr<l||(se[u]==-1&&mn[u]==0&&c<=0))return;
    if(ql<=l&&r<=qr){
        if(mn[u]+c>=0){
            putadd(u, c);
            return;
        }
        else if(se[u]==-1||se[u]+c>0){
            putadd(u, c);
            putcut(u, 0);
            return;
        }
    }
    int mid=(l+r)/2;
    pushdown(u);
    add(2*u, ql, qr, c, l, mid);
    add(2*u+1, ql, qr, c, mid+1, r);
    pushup(u);
}

int query(int u, int ql, int qr, int l, int r){
    if(r<ql||qr<l)return 0;
    if(ql<=l&&r<=qr){
        if(mn[u]==0)return cnt[u];
        return 0;
    }
    int mid=(l+r)/2;
    int ret=0;
    pushdown(u);
    ret+=query(2*u, ql, qr, l, mid);
    ret+=query(2*u+1, ql, qr, mid+1, r);
    return ret;
}


int main(){
    scanf("%d%d", &n, &m);
    for(int i=1;i<=n;i++){
        scanf("%d", &a[i]);
    }
    build(1, 1, n);
    for(int i=1;i<=m;i++){
        int tag;
        scanf("%d", &tag);
        if(tag==1){
            int l, r, c;
            scanf("%d%d%d", &l, &r, &c);
            cover(1, l, r, c, 1, n);
        }
        else if(tag==2){
            int l, r, c;
            scanf("%d%d%d", &l, &r, &c);
            add(1, l, r, c, 1, n);
        }
        else {
            int l, r;
            scanf("%d%d", &l, &r);
            printf("%d\n", query(1, l, r, 1, n));
        }
    }
}

 

 

http://codeforces.com/contest/1199/problem/D

一串序列,两种操作,第一种把某个点的值改为某个值,第二种:把一个区间内小于x的数全部改为x.

这一题正解并不是线段树。

线段树做法:lazy标记,mi最小值数组,cnt表示值。

 

#include<bits/stdc++.h>

using namespace std;
typedef long long ll;
const int N = 2e5 + 5;
ll cnt[N << 2];
ll lazy[N << 2];
ll mi[N << 2];

void push_down(int rt) {
    if(lazy[rt] != -1) {
        if(mi[rt << 1] < mi[rt]) {
            mi[rt << 1] = mi[rt];
            lazy[rt << 1] = lazy[rt];
        }
        if(mi[rt << 1 | 1] < mi[rt]) {
            mi[rt << 1 | 1] = mi[rt];
            lazy[rt << 1 | 1] = lazy[rt];
        }
        lazy[rt] = -1;
    }
}

void pushup(int rt) {
    mi[rt] = min(mi[rt << 1], mi[rt << 1 | 1]);
}

int n;
void print(int rt, int l, int r) {
    if(l == r) {
        printf("%I64d%c", max(cnt[rt], mi[rt]), l == n ? '\n' : ' ');
        return;
    }
    push_down(rt);
    int m = (l + r) >> 1;
    print(rt << 1, l, m);
    print(rt << 1 | 1, m + 1, r);
}

void build(int rt, int l, int r) {
    if(l == r) {
        scanf("%I64d", &cnt[rt]), lazy[rt] = -1, mi[rt] = cnt[rt];
        return;
    }
//    push_down(rt);
    int m = (l + r) >> 1;
    build(rt << 1, l, m);
    build(rt << 1 | 1, m + 1, r);
    pushup(rt);
}

void update1(int rt, int l ,int r, int pos, ll val) {
    if(l == r) {
        cnt[rt] = val;
        mi[rt] = val;
        return;
    }
    push_down(rt);
    int m = (l + r) >> 1;
    if(pos <= m) update1(rt << 1, l, m, pos, val);
    else update1(rt << 1 | 1, m + 1, r, pos, val);
    pushup(rt);
}

//void update2(int rt, int l, int r, ll val) {
//    if(mi[rt] >= val) return;
//    else {
//        mi[rt] = val;
//        lazy[rt] = val;
//        return;
//    }
////    push_down(rt);
////    int m = (l + r) >> 1;
////    update2(rt << 1, l, m, val);
////    update2(rt << 1 | 1, m + 1, r, val);
////    pushup(rt);
//}

int main() {
    scanf("%d", &n);
    build(1, 1, n);
    int q;
    scanf("%d", &q);
    int opt, x;
    ll y;
    while(q--) {
        scanf("%d", &opt);
        if(opt == 1) {
            scanf("%d%I64d", &x, &y);
            update1(1, 1, n, x, y);
        }
        else {
            scanf("%I64d", &y);
            if(mi[1] < y) {
                mi[1] = y;
                lazy[1] = y;
            }
//            update2(1, 1, n, y);
        }
    }
    print(1, 1, n);
    return 0;
}

 

 

https://ac.nowcoder.com/acm/contest/888/E

线段树,叶子节点表示区间。

这个题是给你一个图,每条边有一个允许通过的体型范围,然后要从一号节点到n号节点,要求可以通过的体型值有多少个。

线段树+按秩合并并查集

https://blog.csdn.net/qq_41117236/article/details/99179573

 

对于每个原始区间的l与r,存为l与r + 1,并放入一个数组中去重排序,最后找区间的时候起点lower_bound找起点区间,重点lower_bound找到后还要减一,因为终点所代表的区间并不在这个范围内,

那么然后叶子节点代表的区间就是q[l + 1] - q[l]。

#include<bits/stdc++.h>
using namespace std;
typedef pair<int,int> P;
const int N=1e5+10;
struct edge{ //
    int u,v,l,r;
}f[N];
int pre[N],rk[N]; //pre[]表示父节点,rk[]表示并查集的秩
int q[N<<1];
vector <P> a[N<<3]; //线段树
vector <int> t[30];
P V;
int n,m,ans;
void build(int x,int l,int r,int fl,int fr)
{
    if(l==fl&&r==fr){
//        printf("%d %d\n", l, r);
        a[x].push_back(V);
        return;
    }
    int mid=(l+r)>>1;
    if(fr<=mid) build(x<<1,l,mid,fl,fr);
    else if(fl>mid) build(x<<1|1,mid + 1,r,fl,fr);
    else build(x<<1,l,mid,fl,mid),build(x<<1|1,mid + 1,r,mid + 1,fr);
}
int Find(int x)
{
    return pre[x]==x?x:Find(pre[x]);
}
void join(int rt,int x,int y,int dep)
{
    x=Find(x),y=Find(y);
    if(x!=y){
        if(rk[x]<=rk[y]){ //按秩合并
            pre[x]=y,t[dep].push_back(x);
            if(rk[x]==rk[y]) rk[y]++;
        }
        else{
            pre[y]=x,t[dep].push_back(y);
        }
    }
}
int cnt;
void dfs(int x,int l,int r,int dep) //x表示节点编号,dep表示节点深度
{
    t[dep].clear();
    for(int i = 0; i < a[x].size(); i++) join(x,a[x][i].first,a[x][i].second,dep);
//    printf("%d\n", dep);
    if(l==r){
//        printf("%d %d\n", l, r);
        if(Find(1)==Find(n)) ans+=q[r + 1]-q[l]; //更新贡献
//        for(auto i:t[dep]) pre[i]=i;
        for(int i = 0; i < t[dep].size(); i++) pre[t[dep][i] ] = t[dep][i];
        return;
    }
    int mid=(l+r)>>1;
    dfs(x<<1,l,mid,dep+1);
    dfs(x<<1|1,mid + 1,r,dep+1);
    for(int i = 0; i < t[dep].size(); i++) pre[t[dep][i] ] = t[dep][i];
//    for(auto i:t[dep]) pre[i]=i;
}
int main()
{
    scanf("%d%d",&n,&m);
    cnt=0;
    for(int i=1;i<=n;i++) pre[i]=i;
    for(int i=1;i<=m;i++){
        scanf("%d%d%d%d",&f[i].u,&f[i].v,&f[i].l,&f[i].r);
        q[++cnt]=f[i].l; q[++cnt]=f[i].r + 1;
    }
    sort(q+1,q+cnt+1); //把区间排序进行离散化
    cnt=unique(q+1,q+cnt+1)-q-1;
    for(int i=1;i<=m;i++){
        V=P(f[i].u,f[i].v);
        int p1=lower_bound(q+1,q+cnt+1,f[i].l)-q;
        int p2=lower_bound(q+1,q+cnt+1,f[i].r+1)-q - 1;
        build(1,1,cnt - 1,p1,p2); //以区间为关键字建树
    }
    ans=0;
//    printf("%d\n", cnt);
    dfs(1,1,cnt - 1,1); //从根1向下dfs
    printf("%d\n",ans);
    return 0;
}

 

posted @ 2019-02-07 23:03  downrainsun  阅读(208)  评论(0编辑  收藏  举报