2019牛客暑期多校训练营(第四场)

目录

 

A 、meeting  (树的直径)

 C 、sequence  (单调栈+线段树)

 D 、triples I  (构造)

 J、 free  (分层图)

K 、number   (DP  前缀和)


A 、meeting  (树的直径)

题意:

给出一个树,然后k个人分别在k个节点处,他们要聚会吃饭(一起走到一个点上),走每条边花费的时间是1,计算相遇在每个点的最小时间(每个点的时间是k个人的最大时间)

分析:

画一下很容易发现是一个树的直径的裸题,所以直接计算k个人的最大距离即可。

#include

using namespace std;

const int maxn=1e5+10;

int n,k;
int tot,head[maxn];

struct node{
    int to,next;
}edge[maxn<<1];

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

bool vis[maxn];
bool x[maxn];
int dis[maxn];

void dfs(int u,int fa,int dep){
    vis[u]=1;
    for(int i=head[u];i!=-1;i=edge[i].next){
        int v=edge[i].to;
        if(v==fa||vis[v]) continue;
        if(x[v]) dis[v]=dep;
        dfs(v,u,dep+1);
    }
}

void init(){
    tot=0;
    memset(head,-1,sizeof head);
}

int main(){

    init();
    scanf("%d%d",&n,&k);
    for(int i=0;imx){
            mx=dis[i];b=i;
        }
    }
    memset(vis,0,sizeof vis);
    dfs(b,0,1);
    mx=0;
    for(int i=1;i<=n;i++){
        if(x[i]&&dis[i]>mx){
            mx=dis[i];a=i;
        }
    }
//    cout<>1);
    return 0;
}

 C 、sequence  (单调栈+线段树)

题意:

给出序列a,b,计算

分析:

对于序列a,显然可以根据单调栈计算出以当前点为最小值的点所能拓展的最左边跟最右边,那么对于序列b,就正好是b序列的最左边和最右边,那么可以用线段树进行更新区间最值。

#include

#define mm(a,b) memset(a,b,sizeof(a))
#define ACCELERATE (ios::sync_with_stdio(false),cin.tie(0))
typedef long long ll;
typedef long double ld;
typedef unsigned long long ull;
#define PI acos(-1.0)
#define E exp(1.0)
//#define io
using namespace std;

const int inf=0x3f3f3f3f;
const ll INF=0x3f3f3f3f3f3f3f3f;

const int maxn=3e6+10;

int L[maxn],R[maxn];
ll a[maxn],b[maxn];

struct node{
    ll lmx,rmx;
    ll sum;
};

struct Tree{

    struct node{
        ll lmx,rmx;
        ll sum;
    }tree[maxn<<2];

    void push_up(int i){
        tree[i].lmx=max(tree[i<<1].lmx,tree[i<<1].sum+tree[i<<1|1].lmx);
        tree[i].rmx=max(tree[i<<1|1].rmx,tree[i<<1|1].sum+tree[i<<1].rmx);
        tree[i].sum=tree[i<<1].sum+tree[i<<1|1].sum;
    }

    void build(int l,int r,int i){
        if(l==r){
            tree[i].sum=tree[i].lmx=tree[i].rmx=b[l];
            return ;
        }
        int mid=l+r>>1;
        build(l,mid,i<<1);
        build(mid+1,r,i<<1|1);
        push_up(i);
    }

    node query(int l,int r,int x,int y,int i){
        if(l>=x&&r<=y) return tree[i];
        int mid=l+r>>1;
        if(x>mid){
            return query(mid+1,r,x,y,i<<1|1);
        }else if(y<=mid){
            return query(l,mid,x,y,i<<1);
        }else{
            node zuo=query(l,mid,x,mid,i<<1);
            node you=query(mid+1,r,mid+1,y,i<<1|1);
            node tmp;
            tmp.sum=zuo.sum+you.sum;
            tmp.lmx=max(zuo.lmx,zuo.sum+you.lmx);
            tmp.rmx=max(you.rmx,you.sum+zuo.rmx);
            return tmp;
        }
    }

}T[2];

int main(){
    #ifdef io
    freopen("in.txt","r",stdin);
    freopen("out.txt","w",stdout);
    #endif
    int n;
    scanf("%d",&n);
    for(int i=1;i<=n;i++){
        scanf("%lld",&a[i]);
    }
    for(int i=1;i<=n;i++){
        scanf("%lld",&b[i]);
    }
    for(int i=1;i<=n;i++)
        L[i]=R[i]=i;

    for(int i=2;i<=n;i++){
        int now=i;
        while(now>1&&a[i]<=a[now-1]) now=L[now-1];
        L[i]=now;
    }
    for(int i=n-1;i;i--){
        int now=i;
        while(now=0){
            ll rmx=T[0].query(1,n,L[i],i,1).rmx;
            ll lmx=T[0].query(1,n,i,R[i],1).lmx;
            ans=max(ans,(rmx+lmx-b[i])*a[i]);
//            cout<

 D 、triples I  (构造)

题意:

给出一个数,构造最少的数,使得进行或操作后是这个数并且这些数均为3的倍数。

分系:

思路挺好想的,感觉大部分人挂在了构造上上,我跟ljt做了一下午,思路都想到了,但是构造感觉没问题但就是不对,无奈只好放弃去看题解。才恍然大雾,T_T;

2019牛客暑期多校训练营(第四场)_第1张图片

2019牛客暑期多校训练营(第四场)_第2张图片2019牛客暑期多校训练营(第四场)_第3张图片

 

#include

#define mm(a,b) memset(a,b,sizeof(a))
#define ACCELERATE (ios::sync_with_stdio(false),cin.tie(0))
typedef long long ll;
typedef long double ld;
typedef unsigned long long ull;
#define PI acos(-1.0)
#define E exp(1.0)
//#define io
using namespace std;

const int inf=0x3f3f3f3f;

int main(){
    #ifdef io
    freopen("in.txt","r",stdin);
    freopen("out.txt","w",stdout);
    #endif

    int T;
    scanf("%d",&T);
    while(T--){
        ll a;
        scanf("%lld",&a);
        if(a%3==0) {
            printf("1 %lld\n",a);continue;
        }
        vector v[3];
        for(int i=0;i<=61;i++){
            if(a&(1ll<=2){
                ll p=v[1][0],q=v[1][1];
                ans1=a-p,ans2=a-q;
            }else if(v[1].size()==1){
                ll p=v[1][0],q=v[2][0];
                ans1=a-p,ans2=p+q;
            }else{
                ll p=v[2][0],q=v[2][1],r=v[2][2];
                ans1=a-p-q;ans2=p+q+r;
            }
        }else{
            if(v[2].size()>=2){
                ll p=v[2][0],q=v[2][1];
                ans1=a-p,ans2=a-q;
            }else if(v[2].size()==1){
                ll p=v[2][0],q=v[1][0];
                ans1=a-p,ans2=p+q;
            }else{
                ll p=v[1][0],q=v[1][1],r=v[1][2];
                ans1=a-p-q;ans2=p+q+r;
            }
        }
        printf("2 %lld %lld\n",ans1,ans2);
    }
    return 0;
}

 J、 free  (分层图)

题意:

计算从S-T的最短路,其中有k条边的权值可以改为0

分析:

这是一个原题,分层图,dis[i][j]表示到达i点,已经改了j次边权,此时的最短路。

相当于将原图复制成了k层,每改变一次,就向下走一层。

两种情况(如果可以变优):

(1)不用变0技能:转移到dis[v][j] = dis[u][j] + w

(2)用变0技能:转移到dis[v][j+1] = dis[u][j]

#include

using namespace std;

const int maxn=1e3+10;
const int inf=0x3f3f3f3f;

int n,m,s,t,k;

int dis[maxn][maxn];
int tot,head[maxn];

struct Edge{
    int v,w,next;
}edge[maxn<<1];

struct node{
    int id,w,num;
    bool operator <(const node &p)const {return w>p.w;}
};

priority_queue q;

void addedge(int u,int v,int w){
    edge[tot].v=v;
    edge[tot].w=w;
    edge[tot].next=head[u];
    head[u]=tot++;
}

inline void init(){
    tot=0;
    memset(head,-1,sizeof head);
    memset(dis,inf,sizeof dis);
}

int dijkstra(int str,int tar){
    q.push(node{str,0,0});
    dis[s][0]=0;
    while(!q.empty()){
        node f=q.top();
        q.pop();
        if(f.w!=dis[f.id][f.num]) continue;
        if(f.id==tar) return f.w;
        int u=f.id;
        for(int i=head[u];i!=-1;i=edge[i].next){
            int v=edge[i].v;
            int w=edge[i].w;
            if(dis[v][f.num]>dis[u][f.num]+w) {
                dis[v][f.num]=dis[u][f.num]+w;
                q.push(node{v,dis[v][f.num],f.num});
            }
            if(f.numdis[u][f.num]){
                dis[v][f.num+1]=dis[u][f.num];
                q.push(node{v,dis[v][f.num+1],f.num+1});
            }
        }
    }
    return 0;
}

int main(){

    init();
    scanf("%d%d%d%d%d",&n,&m,&s,&t,&k);
    while(m--){
        int u,v,w;
        scanf("%d%d%d",&u,&v,&w);
        addedge(u,v,w);
        addedge(v,u,w);
    }
    printf("%d\n",dijkstra(s,t));
    return 0;
}

K 、number   (DP  前缀和)

题意:

给出一段字符串计算其中是300的倍数的子串。

分析:

( 1 )  O(n) 算法。

300的倍数不好计算,那么可以考虑3的倍数,再再辞基础上考虑100的倍数即可。

考虑3的倍数

如果一个数能整除3,那么所有位上的数字和是3的倍数根据这一点,我们可以计算(mod 3)的前缀和,那么  pre[l]==pre[r]的区间所组成的数肯定就是3的倍数。

考虑100的倍数

肯定要出现2个0.

还有就是一些细节,注意有前导0

( 2 )O(n*300)算法

考虑DP,dp[i][j] 表示到第i个字符且余数为j的子串个数,那么很容易的到递推式。详见代码。

#include

using namespace std;

typedef long long ll;
const int maxn =1e5+10;

ll a[maxn];
char sr[maxn];

int main(){

    scanf("%s",sr+1);
    int len = strlen(sr+1);
    if(len==1&&sr[1]=='0'){
        printf("1\n");
        return 0;
    }
    for (int i=1;i<=len;i++)
        a[i]=(a[i-1]+sr[i]-'0')%3;
    ll num[3]={0};
//    num[0]=1ll;
    ll ans=0ll;
    for(int i=0;i<=len-2;i++){
        ll cnt=0;
        if(sr[i+1]=='0'&&sr[i+2]=='0'){
            for(int j=i+1;j<=len&&sr[j]=='0';j++){
                cnt++;
            }
            ans+=(num[a[i]])*(cnt-1);
            ans+=cnt*(cnt+1)/2;
            i=i+cnt;
        }else if(sr[i+1]=='0'&&sr[i+2]!='0'){
            ans++;
        }
        num[a[i]]+=cnt+1;
    }
    printf("%lld\n",ans);
    return 0;
}
#include

using namespace std;

typedef long long ll;
const int maxn =1e5+10;

char sr[maxn];
ll dp[maxn][305];

int main(){

    scanf("%s",sr);
    dp[0][sr[0]-'0']=1ll;
    ll ans=dp[0][0];
    for(int i=1;sr[i];i++){
        dp[i][sr[i]-'0']=1ll;
        for(int j=0;j<300;j++){
            dp[i][(j*10+sr[i]-'0')%300]+=dp[i-1][j];
        }
        ans+=dp[i][0];
    }
    printf("%lld\n",ans);
    return 0;
}

 

你可能感兴趣的:(总结)