NOIP提高组模拟赛8


A. 队长快跑

完全没想到可以这样DP,一直在想排序然后怎么搞,中间因为理解有误打了个奇奇怪怪的线段树,发现错误已经过去2h了,看看实在没思路直接跑路,然后基本暴零。。。。。WTCL

因为\(A_{P_i}<=B_{p_i}\)就会爆炸,所以后摧毁的水晶的B一定小于前面已经摧毁的A的最小值

\(f[i][j]\)表示前\(i\)个水晶\(a[i]\)最小值为j能摧毁的最多的水晶数

如果不摧毁当前水晶\(f[i][j]=f[i-1][j]\)

摧毁当前水晶\(f[i][min(j,a[i])]=max(f[i-1][j])+1\)

这样转移显然是\(O(N^2)\)的,考虑如何优化

我们将\(min\)展开,分情况讨论

原来DP可以写成

        for (int j = 1; j <= tot; ++j)
            f[i][j] = f[i - 1][j];
        if (a[i].b > a[i].a)
            for (int j = a[i].b + 1; j <= tot; ++j)
                f[i][a[i].a] = max(f[i][a[i].a], f[i - 1][j] + 1);
        else
        {
            for (int j = a[i].b + 1; j < a[i].a; ++j)
                f[i][j] = f[i - 1][j] + 1;
            for (int j = a[i].a; j <= tot; ++j)
                f[i][a[i].a] = max(f[i][a[i].a], f[i - 1][j] + 1);
        }

然后,就要用到OIER最喜欢的线段树了,继承操作就是啥都不做,取\(max\)就是区间最值,更新就是单点修改,还有个区间加,直接用线段树板子即可

由于\(Eafoo\)大佬的操作,马蜂有点诡异

#include 
#include 
#include 

using namespace std;

const int maxn = 100005;

struct node
{
    int a, b;
} a[maxn];

int ls[maxn << 1 | 1], tot;

void lsh(int n)
{
    tot = n << 1;
    for (int i = 1; i <= n; ++i)
        ls[(i << 1) - 1] = a[i].a, ls[i << 1] = a[i].b;
    sort(ls + 1, ls + tot + 1);
    tot = unique(ls + 1, ls + tot + 1) - ls - 1;
    for (int i = 1; i <= n; ++i)
    {
        a[i].a = upper_bound(ls + 1, ls + tot + 1, a[i].a) - ls - 1;
        a[i].b = upper_bound(ls + 1, ls + tot + 1, a[i].b) - ls - 1;
    }
}

int tree[maxn << 3], lazy[maxn << 3];

void push_down(int x, int l, int r)
{
    int mid = (l + r) >> 1;
    tree[x << 1] += lazy[x];
    tree[x << 1 | 1] += lazy[x];
    lazy[x << 1] += lazy[x];
    lazy[x << 1 | 1] += lazy[x];
    lazy[x] = 0;
}

void add(int x, int l, int r, int L, int R)
{
    if (L <= l && r <= R)
    {
        ++lazy[x];
        ++tree[x];
        return;
    }
    if (lazy[x])
        push_down(x, l, r);
    int mid = (l + r) >> 1;
    if (L <= mid)
        add(x << 1, l, mid, L, R);
    if (R > mid)
        add(x << 1 | 1, mid + 1, r, L, R);
    tree[x] = max(tree[x << 1], tree[x << 1 | 1]);
}

int ask(int x, int l, int r, int L, int R)
{
    if (L <= l && r <= R)
        return tree[x];
    if (tree[x])
        push_down(x, l, r);
    int mid = (l + r) >> 1;
    int ans = -1;
    if (L <= mid)
        ans = ask(x << 1, l, mid, L, R);
    if (R > mid)
        ans = max(ans, ask(x << 1 | 1, mid + 1, r, L, R));
    return ans;
}

void update(int x, int l, int r, int d, int ne)
{
    if (l == r)
    {
        tree[x] = max(tree[x], ne);
        return;
    }
    if (tree[x])
        push_down(x, l, r);
    int mid = (l + r) >> 1;
    if (d <= mid)
        update(x << 1, l, mid, d, ne);
    if (d > mid)
        update(x << 1 | 1, mid + 1, r, d, ne);
    tree[x] = max(tree[x << 1], tree[x << 1 | 1]);
}

int main()
{
    int n;
    scanf("%d", &n);
    for (int i = 1; i <= n; ++i)
        scanf("%d%d", &a[i].a, &a[i].b);
    lsh(n);
    for (int i = 1; i <= n; ++i)
    {

        if (a[i].b >= a[i].a)
        {
            int k = ask(1, 1, tot, a[i].b + 1, tot) + 1;
            update(1, 1, tot, a[i].a, k);
        }
        else
        {
            int k = ask(1, 1, tot, a[i].a, tot) + 1;
            update(1, 1, tot, a[i].a, k);
            add(1, 1, tot, a[i].b, a[i].a - 1);
        }
    }
    printf("%d\n", tree[1]);
    return 0;
}

B. 影魔

又是一道我不会的题真的是太弱了

颜色分开考虑,该颜色对所在子树均有贡献,子树中有多个颜色贡献只算一次,用树上差分的思想,维护链并(又一个新名词?),在dfs序上对应结点+1,与相邻的同色结点的lca处-1,这样就保证贡献只有1,为了得到dfs序相邻的结点编号,使用set进行维护(set是有序的)。

然后所有颜色搞完时候,问题就变成了求子树权值和,因为还有深度所以使用主席树,每个深度对应的dfs序为一个新的版本
在对应版本查找对应子树即可。。。

由于本人太菜所以写不出什么东西了,不懂的看其他大佬的博客吧

代码完全仿fengwu学长的写法(因为我还不会主席树)

学了主席树以后再看有没有需要修正的吧。挖坑


#include 
#include 
#include
#include
using namespace std;
const int maxd=100005;
struct edge{
   int net,to;
}e[maxd<<1|1];
int head[maxd],tot;
int n,m;
void add(int u,int v){
   e[++tot].net=head[u];
   head[u]=tot;
   e[tot].to=v;
}
struct node{
    int col,dep,dfsl,dfsr,id;
    bool operator < ( node x)const{
        return x.dfsl>dfsl;
    }
}d[maxd];
setst[maxd];
bool cmp(node x,node y){
    if(x.dep!=y.dep)return x.dep=0;--i)if(dep[fa[x][i]]>=dep[y])x=fa[x][i];
    if(x==y)return x;
    for(int i=20;i>=0;--i)if(fa[x][i]!=fa[y][i])x=fa[x][i],y=fa[y][i];
    return fa[x][0];
}
struct president_tree{
    int left_son[maxd*80],right_son[maxd*80];
    int siz[maxd*80];
    int tmp;
    void pushup(int x){
        siz[x]=0;
        if(left_son[x])siz[x]+=siz[left_son[x]];
        if(right_son[x])siz[x]+=siz[right_son[x]];
        return;
    }
    void insert(int pre,int &x,int l,int r,int pos,int v){
        if(!x){x=++tmp;siz[x]=siz[pre];left_son[x]=left_son[pre];right_son[x]=right_son[pre];}
        if(l==r){siz[x]+=v;return;}
        int mid=(l+r)>>1;
        if(pos<=mid){
            if(left_son[x]==left_son[pre])left_son[x]=0;
            insert(left_son[pre],left_son[x],l,mid,pos,v);
        }else{
            if(right_son[x]==right_son[pre])right_son[x]=0;
            insert(right_son[pre],right_son[x],mid+1,r,pos,v);
        }
        pushup(x);return;
    }
    int query(int x,int l,int r,int L,int R){
        if(!x)return 0;
        if(L<=l&&r<=R)return siz[x];
        int mid=(l+r)>>1,ans=0;
        if(L<=mid)ans+=query(left_son[x],l,mid,L,R);
        if(R>mid)ans+=query(right_son[x],mid+1,r,L,R);
        return ans;
    }
}tree;

int main(){
   //freopen(,r,stdin);
   scanf("%d%d",&n,&m);
   for(int i=1;i<=n;++i)scanf("%d",&d[i].col),d[i].id=i;
   for(int i=2;i<=n;++i){int x;scanf("%d",&x);add(x,i);}
   d[1].dep=1;dfs(1);
   for(int i=1;i<=n;++i){
       dep[i]=d[i].dep;
       dfsl[i]=d[i].dfsl;
       dfsr[i]=d[i].dfsr;
   }
   sort(d+1,d+n+1,cmp);
   for(int i=1;i<=n;++i){
       tree.insert(root[d[i].dep-1],root[d[i].dep],1,n,d[i].dfsl,1);
       st[d[i].col].insert(d[i]);
       int lca;
       node nu;nu.id=0;
       node pre=st[d[i].col].find(d[i])==st[d[i].col].begin()?nu:*--st[d[i].col].find(d[i]);
       node nxt=((++st[d[i].col].find(d[i]))==st[d[i].col].end())?nu:*++st[d[i].col].find(d[i]);
       if(pre.id){
           lca=LCA(d[i].id,pre.id);
           tree.insert(root[d[i].dep-1],root[d[i].dep],1,n,dfsl[lca],-1);
       }
       if(nxt.id){
           lca=LCA(d[i].id,nxt.id);
           tree.insert(root[d[i].dep-1],root[d[i].dep],1,n,dfsl[lca],-1);
       }
       if(pre.id&&nxt.id){
           lca=LCA(pre.id,nxt.id);
           tree.insert(root[d[i].dep-1],root[d[i].dep],1,n,dfsl[lca],1);
       }
   }
   for(int i=1;i<=m;++i){
       int u,d;scanf("%d%d",&u,&d);
       printf("%d\n",tree.query(root[min(dep[u]+d,max_dep[u])],1,n,dfsl[u],dfsr[u]));
   }
    return 0;
}

C. 抛硬币

本次考试最水的题,然而他放到了T3

\(f[i][j]\)表示后i个字符,以第i个字符开头,有多少长度为j的本质不同的子序列

可以发现如果i后面有多个相同的字母,因为后面相同字母开头的序列,前面的一定能得到,那么本质不同的个数就是第一个字母的方案数

\(f[i][j]=\sum_{k=1}^{26} f[pos[i][k]][j-1]\)

预处理出每个位置向后(不包括自身)a-z第一个字母出现的位置

然后记忆化搜索即可

#include 
#include 
using namespace std;
const int mod=998244353;
char c[3015];
int n;
int rem[3005][3005];
int flag[27],re[3005][27];
bool vis[3005][3005];
int ask(int x,int l){
    if(vis[x][l])return rem[x][l];
    vis[x][l]=1;
    if(n-x+1==l)return rem[x][l]=1;   
    if(n-x+1=1;--i){
      for(int j=1;j<=26;++j)re[i][j]=flag[j];  
      rem[i][1]=1;vis[i][1]=1;
      flag[c[i]-'a'+1]=i;
    }
    long long ans=0;
    for(int i=1;i<=n;++i)int k=ask(i,l);
    for(int i=1;i<=26;++i){
        int k=flag[i];
        ans=(ans+rem[flag[i]][l])%mod;
    }
    printf("%lld\n",ans);
    return 0;
}

相关