Codeforces Round 881 (Div. 3) DE题解

目录

D. Apple Tree

问题建模:

问题分析:

1.问题所求

2.求解苹果掉落位置数量

3.依据所求,使用算法

4.代码

E. Tracking Segments

问题建模:

问题分析:

1.分析修改操作与所求的关系

2.分析修改变化趋势

3.结合变化,使用算法

4.代码


D. Apple Tree

 

问题建模:

给定一棵有n个顶点的树,该树可以在任意位置放置一个苹果,每次摇动该树,苹果将向其所在节点的任意一个子节点移动,若当前节点为叶子节点,则该苹果将掉落。问给出q对放置苹果的位置,则最终这对苹果掉落位置构成的有序对数量可能为多少。

问题分析:

1.问题所求

问题所求有序对数量,等价于第一个苹果掉落位置数量*第二个苹果掉落位置数量

2.求解苹果掉落位置数量

苹果从一个位置出发,每次向任意根节点移动,最终移动到某一个叶子结点,则任意苹果的掉落数量,为以其初始位置所在子树的叶子节点数量。

3.依据所求,使用算法

由于有q对苹果有序对数量需要求解,则先采用记忆化搜索算法,从根节点开始,计算每一个节点所在子树的叶子结点数量并将其存储起来,方便后面q次查询进行直接的计算,时间复杂度为O(q+n)。

4.代码

#include<bits/stdc++.h>

#define x first
#define y second
using namespace std;
typedef unsigned long long ULL;
typedef long long LL;
typedef pair<int,int> PII;

const int N=2e5+10;
int dp[N];
vector<int> e[N];

int dfs(int u,int c){
    dp[u]=0;
    for(auto v:e[u] ) {
        if(v!=c) dp[u]+=dfs(v,u);
    }
    return dp[u]=max(dp[u],1);
}

void solve(){
    int n;
    cin >>n;

    for(int i=0;i<n-1;i++){
        int u,v;
        scanf("%d %d",&u,&v);
        e[u].push_back(v);
        e[v].push_back(u);
    }

    dfs(1,1);

    int q;
    cin >>q;
    while(q--){
        int x,y;
        scanf("%d %d",&x,&y);
        printf("%lld\n",(LL)dp[x]*dp[y]);
    }

    for(int i=1;i<=n;i++)    e[i].clear();
}

int main(){
    int t=1;
    cin >>t;
    while(t--)  solve();
    return 0;
}

E. Tracking Segments

 

问题建模:

给定m个子区间,以及一个长度为n,初始元素都为0的序列。进行q次修改,每次修改选择序列的一个位置,并将其元素修改为1,问最早一次修改时存在一个子区间内的1的个数大于0的个数的修改编号为多少,若不存在,则输出-1。

1<=m<=n<=1e5

1<=l<=r<=n

1<=1<=n

问题分析:

1.分析修改操作与所求的关系

每一次修改,将序列的某一个位置变为1,然后判断m个区间内存不存在一个区间符合要求,可以考虑用前缀和的方法判断,即将区间求和,得到该区间1的个数,然后和该区间内的0的个数(区间大小-区间前缀和)进行比较。

由于每一次查询后序列都会修改一次,若采用前缀和的方法则在每一次查询后需要花费O(n)的时间来建立前缀和数组,然后遍历m个子区间进行查询判断,每次查询的时间为O(1),则总判断时间为O(n+m)。

2.分析修改变化趋势

由于对于每一次查询,序列内1的个数会逐渐变多,则当某一次查询使得某一个区间满足要求,则在此次查询之后的所有查询都满足要求。

3.结合变化,使用算法

由于满足条件的情况在某一次查询之后将会稳定维持,则可以通过二分的方法找到一个查询编号mid,对于该所选的编号,将初始序列改变为编号1~编号mid所做操作后的序列,然后建立前缀和数组,然后对m个子区间进行判断。根据编号是否满足条件来考虑是将编号缩小还是放大,从而来找到最小满足条件的编号,其复杂度为(2*n+m)*log(q)。

4.代码

#include<bits/stdc++.h>

#define x first
#define y second
using namespace std;
typedef unsigned long long ULL;
typedef long long LL;
typedef pair<int,int> PII;
const int N=1e5+10,INF=1e9;
int q[N];
LL b[N];
PII sub[N];
int n,m,p;

bool check(int k){
    memset(b,0,sizeof(LL)*(n+1));
    for(int i=1;i<=k;i++){
        b[q[i]]=1;
    }

    for(int i=1;i<=n;i++){
        b[i]+=b[i-1];
    }

    for(int i=1;i<=m;i++){
        int one=b[sub[i].y]-b[sub[i].x-1];
        if(one>sub[i].y-sub[i].x+1-one){
            return true;
        }
    }

    return false;
}

void solve(){
    cin >>n >>m;
    for(int i=1;i<=m;i++){
        scanf("%d %d",&sub[i].x,&sub[i].y);
    }

    cin >>p;
    for(int i=1;i<=p;i++)   scanf("%d",&q[i]);

    if(check(p)){
        int l=1,r=p;
        while(l<r){
            int mid=(l+r)>>1;
            if(check(mid))  r=mid;
            else l=mid+1;
        }
        cout <<l <<"\n";
    }else puts("-1");
}

int main(){
    int t=1;
    cin >>t;
    while(t--)  solve();
    return 0;
}

  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值