2017 暑假艾教集训 day6

本文解析了三道经典算法题目:CF240F利用二维线段树解决区间更新与查询问题;Cf343D通过DFS序加线段树处理区间修改与查询;POJ 2104应用主席树解决动态区间查询。每道题目均提供了完整的代码实现。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >


CF 240F
做法:二维线段树,分别记录一个区间的小写字母个数,每次从小到大给两边分配(字典序最小)(区间更改)!


#include <bits/stdc++.h>
#define lson rt<<1,begin,mid
#define rson rt<<1|1,mid+1,end
using namespace std;
const int maxn= 100100;
char str[maxn];
int tree[maxn<<3][28];
int lazy[maxn<<3];
int book[28];
int n,m;

void cls(int rt)
{
    lazy[rt]=-1;
    for(int i=0;i<26;++i) tree[rt][i]=0;
}
void pushup(int rt)
{
    for(int i=0;i<26;++i) tree[rt][i] = tree[rt<<1][i] + tree[rt<<1|1][i];
}
void pushdown(int rt,int begin,int end)
{
    if(lazy[rt]!=-1)
    {
        int mid=(begin + end)/2;
        int id=lazy[rt];
        cls(rt<<1); cls(rt<<1|1);

        tree[rt<<1][id]= (mid-begin+1);
        tree[rt<<1|1][id] = (end-mid);
        lazy[rt<<1]=lazy[rt<<1|1] =lazy[rt];
        lazy[rt]=-1;
    }
}
void build(int rt,int begin,int end)
{
    cls(rt);
    if(begin==end)
    {
        int id=str[begin] - 'a';
        tree[rt][id]++;
        lazy[rt]=id;
        return;
    }
    int mid=(begin + end)>>1;
    build(rson);
    build(lson);
    pushup(rt);
}
void updata(int rt,int begin,int end,int l,int r,int id)
{
    if(begin>=l && r>=end)
    {
        cls(rt);
        tree[rt][id] = (end-begin+1);
        lazy[rt]=id;
        return ;
    }
    pushdown(rt,begin,end);
    int mid=(begin + end)>>1;
    if(mid>=l) updata(lson,l,r,id);
    if(r>mid) updata(rson,l,r,id);
    pushup(rt);
}
void query(int rt,int begin,int end,int l,int r)
{
   if(begin >=l && r>=end)
   {
       for(int i=0;i<26;++i) book[i]+=tree[rt][i];
       return ;
   }
   pushdown(rt,begin,end);
   int mid=(begin + end)>>1;
   if(mid>=l) query(lson,l,r);
   if(r>mid) query(rson,l,r);
   pushup(rt);
}
void work(int l,int r)
{
    memset(book,0,sizeof(book));
    query(1,1,n,l,r);
    int flag=0;
    for(int i=0;i<26;++i)
    {
        if(book[i] % 2) flag++;
        if(flag > 1) return;
    }

    int st=l,en=r;
    int mid=l+(r-l+1)/2;
    for(int i=0;i<26;++i)
    {
        if(book[i]==0) continue;

        int temp= book[i]/2;
        if(temp)
        {
            updata(1,1,n,st,st+temp-1,i);
            updata(1,1,n,en-temp+1,en,i);

            st= st+temp;
            en= en-temp;
        }
        if(book[i]%2) updata(1,1,n,mid,mid,i);
    }
}
int main()
{
    freopen("input.txt","r",stdin);
	freopen("output.txt","w",stdout);
    scanf("%d%d",&n,&m);
    scanf("%s",str+1);
    build(1,1,n);
    int a,b;
    while(m--)
    {
        scanf("%d%d",&a,&b);
        work(a,b);
    }
    for(int i=1;i<=n;++i)
    {
        memset(book,0,sizeof(book));
        query(1,1,n,i,i);
        for(int j=0;j<26;++j)
        {
            if(book[j])
            {
                putchar(j+'a');
                break;
            }
        }
    }
    putchar(10);
    return 0;
}


Cf 343D
做法:DFS序加线段树 ,这里要注意的是二操作,因为要区间修改其祖先很难实现。这时候我们可以这么想,先对修改点单点修改,然后在询问时,取DFS序子节点的min值即可。
注意:这里有一个wa点,如果当前点为0的话,在进行操作1的时候需要把这个0扔给它的祖先,避免在更改1后出现漏更改的情况!!!

#include <bits/stdc++.h>
#define lson rt<<1,begin,mid
#define rson rt<<1|1,mid+1,end
using namespace std;
const int maxn=500005;
int tree[maxn<<3];
int lazy[maxn<<3];
//如果其儿子有一点为0的话,那么本身也一定是0
void pushup(int rt)
{
    tree[rt] = tree[rt<<1] && tree[rt<<1|1];
}
void pushdown(int rt)
{
    if(lazy[rt]!=-1)
    {
        tree[rt<<1] = tree[rt<<1|1] =lazy[rt];
        lazy[rt<<1|1] =lazy[rt<<1] =lazy[rt];
        lazy[rt] = -1;
    }
}
void updata(int rt,int begin,int end,int l,int r ,int x)
{
    if(begin >= l && r>=end)
    {
        tree[rt] = x;
        lazy[rt] = x;
        return ;
    }
    pushdown(rt);
    int mid=(begin + end)>>1;
    if(mid >= l) updata(lson,l,r,x);
    if(r>mid) updata(rson,l,r,x);
    pushup(rt);
}
//单点更新
void updata2(int rt,int begin,int end,int pos)
{
    if(begin==end)
    {
        tree[rt] = 0;
        return ;
    }
    pushdown(rt);
    int mid=(begin +end)>>1;
    if(mid >=pos) updata2(lson,pos);
    else updata2(rson,pos);
    pushup(rt);
}
//儿子中如果有0的话,那么其点一定是0
int  query(int rt,int begin, int end,int l,int r)
{
    if(begin >=l && r>= end)
    {
        return tree[rt];
    }
    pushdown(rt);
    int mid=(begin +end )>>1;
    int a=3;
    if(mid >=l) a= min( query(lson ,l,r),a);
    if(r>mid) a=min(a, query(rson,l,r));
    return a;
}

struct node
{
    int to,next;
}edge[maxn<<1];
int head[maxn];
int cnt=0;

void add(int u,int v)
{
    edge[cnt].to=v; edge[cnt].next=head[u]; head[u]=cnt++;
}

int fis[maxn];
int two[maxn];
int tot;
int fa[maxn];
void dfs(int u , int pre)
{
    fis[u] = ++tot;
    fa[u]=pre;
    for(int i=head[u] ; i!=-1; i=edge[i].next )
    {
        int v=edge[i].to;
        if(fis[v]==-1) dfs(v,u);
    }
    two[u]=tot;
}
int main()
{
    int n;
    while(scanf("%d",&n)!=EOF)
    {
        memset(head,-1,sizeof(head)); cnt=0;
        for(int i=1;i<n;++i)
        {
            int a,b;
            scanf("%d%d",&a,&b);
            add(a,b); add(b,a);
        }

        memset(fis,-1,sizeof(fis)); tot=0;
        dfs(1,-1);

        memset(tree,0,sizeof(tree));
        memset(lazy,-1,sizeof(lazy));

        int m;
        scanf("%d",&m);
        while(m--)
        {
           int op,v;
           scanf("%d%d",&op,&v);
           if(op==1)
           {
               if(!query(1,1,tot,fis[v],two[v]) && fa[v]!=-1) updata2(1,1,tot,fis[fa[v]]);
               // 如果其为0的话,那说明它的祖先也一定为0
               updata(1,1,tot,fis[v],two[v],1);
           }
           else if(op==2)
           {
              updata2(1,1,tot,fis[v]);
              //单点更新
           }
           else if(op==3)
           {
               printf("%d\n",query(1,1,tot,fis[v],two[v]));
           }
        }

    }
    return 0;
}



POJ  2104
做法:主席树模板(默默收藏一发)


#include <algorithm>
#include <iostream>
#include <cstdio>
#include <cstring>
using namespace std;
const int maxn= (100005<<2);
int tree[maxn*30],lson[maxn*30],rson[maxn*30];
int root[maxn],tot;
int a[maxn],myhash[maxn];

void build(int &rt,int begin ,int end)
{
    rt = ++tot;
    tree[rt]=0;
    if(begin == end) return;
    int mid=(begin+end)>>1;
    build(lson[rt],begin,mid);
    build(rson[rt],mid+1,end);
}
void updata(int pre,int &rt,int begin, int end,int pos)
{
    rt =  ++tot;
    tree[rt] = tree[pre] +1;
    lson[rt] = lson[pre];
    rson[rt] = rson[pre];
    if(begin == end) return;
    int mid=(begin + end) >>1;
    if(mid >= pos) updata(lson[pre],lson[rt],begin,mid,pos);
    else  updata(rson[pre],rson[rt],mid+1,end,pos);
}
int query(int pre,int rt,int begin ,int end,int k)
{
    if(begin == end)
    {
        return begin;
    }
    int have = (tree[lson[rt]]-tree[lson[pre]]);
    int mid =  (begin+end)>>1;
    if(have>=k) return query(lson[pre],lson[rt],begin,mid,k);
    else return  query(rson[pre],rson[rt],mid+1,end,k-have);
}
int main()
{
    int n,m;
    while(scanf("%d%d",&n,&m)!=EOF)
    {
        for(int i=1;i<=n;++i)
        {
            scanf("%d",&a[i]);
            myhash[i]=a[i];
        }
        sort(myhash+1,myhash+n+1);
        int cnt = unique(myhash+1,myhash+n+1) - myhash -1;
        build(root[0], 1, cnt);
        for(int i=1;i<=n;++i)
        {
            int temp = lower_bound(myhash+1,myhash+cnt+1,a[i]) - myhash;
            updata(root[i-1],root[i],1,cnt,temp);
        }
        while(m--)
        {
            int l,r,k;
            scanf("%d%d%d",&l,&r,&k);
            printf("%d\n",myhash[query(root[l-1],root[r],1,cnt,k)]);
        }
    }
    return 0;
}


### 关于雅礼集训 2017 Day1 的题目及解析 #### 题目概述 根据已知引用内容[^3],雅礼集训 2017 Day1 的核心问题是关于矩阵操作的优化问题。给定一个 \(n \times m\) 的字符矩阵,其中 `#` 表示黑色格子,`.` 表示白色格子。目标是最小化将整个矩阵变为全黑所需的步数。 --- #### 解析与算法思路 ##### 输入描述 输入的第一行为两个整数 \(n\) 和 \(m\),分别代表矩阵的行数和列数。接下来 \(n\) 行每行包含长度为 \(m\) 的字符串,表示矩阵的内容。 ##### 输出描述 输出最小的操作次数使得整个矩阵变成全是黑色格子的状态。如果没有可行方案,则输出 `-1`。 --- ##### 算法设计 1. **可行性判断** 如果初始矩阵没有任何黑色格子 (`#`) 存在,则无法通过任何有限次操作使矩阵变黑,因此直接返回 `-1`[^4]。 2. **计算最少步数** 对于每一行,定义两种可能的操作方式: - 将该行全部涂黑。 - 不改变该行状态,仅依赖后续列操作来覆盖剩余白格。 同样地,对于每一列也存在类似的策略选择。最终的目标是综合考虑行列操作的影响,找到全局最优解。 3. **动态规划或贪心求解** 使用简单的遍历方法统计各行列中的黑白分布情况,并基于此决定最佳行动顺序。特别注意边界条件处理以及特殊情况下的额外开销评估。 ```cpp #include <bits/stdc++.h> using namespace std; const int MAXN = 1e3 + 5; int n, m, h[MAXN], l[MAXN]; char s[MAXN][MAXN]; int main(){ cin >> n >> m; bool has_black = false; for (int i = 1; i <= n; ++i){ cin >> (s[i]+1); for (int j = 1; j <= m; ++j){ if (s[i][j] == '#'){ has_black = true; h[i]++; l[j]++; } } } if (!has_black){ cout << "-1"; return 0; } int res = INT_MAX; for (int i = 1; i <= n; ++i){ res = min(res, m - h[i] + !l[i]); } int extra_cost = 0; for (int j = 1; j <= m; ++j){ if (l[j] != n) extra_cost += 1; } cout << res + extra_cost; } ``` 上述代码实现了基本逻辑框架,包括读取数据、初步分析是否存在解决方案的可能性以及最后一步汇总总成本的过程[^3]。 --- #### 复杂度分析 时间复杂度主要取决于两次嵌套循环扫描整个矩阵所需的时间量级 O(n*m),空间消耗同样维持在线性范围内 O(n+m)。 --- #### 注意事项 - 当前实现假设所有测试实例均满足合理范围内的尺寸规格;实际应用时需增加更多健壮性的错误检测机制。 - 结果验证阶段应充分考虑到极端情形比如完全空白或者满布障碍物等情况是否被妥善处置。 ---
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值