牛客每日一题系列(持续更新)

牛客每日一题系列(持续更新)

题目链接 题解
十四、Accumulation Degree&&树学 换根dp
十五、矩阵取数游戏 区间dp+高精度

ps

供自己复习、记录题解所用

一 tokitsukaze and Soldier

题意:
n个士兵,需要从中选出一些士兵,但是每个士兵都有对应的战力值v和对人数的限制s
求:选出的士兵,最多能形成多大的战力值

题解:
我们需要维护选出的士兵形成的集合中对人数的限制,需要使得所有的限制中最小值要尽可能的大,我们可以对每个士兵对人数限制那个参数进行从大到小排序,每次选择还没有进队列中的最大的那个s,使其进入队列中

至于每个士兵的战力值这个参数,我们需要满足他们的和尽可能的大,也就是每当人数不符合要求时,我们将小的值移出集合
于是可以采用用**堆(即:优先队列)**来进行维护
(这是看的题解,当时没想出来)

代码:

#include<bits/stdc++.h>
#define lowbit(x) x&(-x)
#define ll long long
#define inf 0x3f3f3f3f
using namespace std;
const ll mod=1e9+7;
const int maxn=1e5+5;
int n;
struct node{
    int v,s;
};
node a[maxn];
priority_queue<int,vector<int>,greater<int> >q;
bool cmp(node x,node y){
    return x.s>y.s;
}
int main(){
    scanf("%d",&n);
    for(int i=1;i<=n;i++){
        scanf("%d%d",&a[i].v,&a[i].s);
    }
    sort(a+1,a+n+1,cmp);
    ll ans=0,temp=0;
    for(int i=1;i<=n;i++){
        q.push(a[i].v);
        temp+=a[i].v;
        while(q.size()>a[i].s){
            temp-=q.top();
            q.pop();
        }
        ans=max(ans,temp);
    }
    printf("%lld\n",ans);
    return 0;
}

二 合并回文子串

题意:
给定两个字符串,求合并后能获得的最长回文子串

题解:
一个比较难的dp问题,首先dp数组dp[i][j][k][l]
表示:字符串a:i~j ;字符串b:k~l
然后:

if(len1>1&&a[i]==a[j]) dp[i][j][k][l]+=dp[i+1][j-1][k][l];
if(len2>1&&b[k]==b[l]) dp[i][j][k][l]+=dp[i][j][k+1][l-1];
if(len1&&len2&&a[i]==b[l]) dp[i][j][k][l]+=dp[i+1][j][k][l-1];
if(len1&&len2&&a[j]==b[k]) dp[i][j][k][l]+=dp[i][j-1][k+1][l];

注意dp数组的初始化。

#include<bits/stdc++.h>
#define lowbit(x) x&(-x)
#define ll long long
#define inf 0x3f3f3f3f
using namespace std;
const int maxn=1000;
const ll mod=998244353;
int t;
char a[55],b[55];
int dp[110][110][110][110];
int main(){
    scanf("%d",&t);
    while(t--){
        scanf("%s",a+1);
        scanf("%s",b+1);
        int n=strlen(a+1);
        int m=strlen(b+1);
        int ans=0;
        for(int len1=0;len1<=n;len1++){
            for(int len2=0;len2<=m;len2++){
                for(int i=1;i<=n-len1+1;i++){
                    for(int k=1;k<=m-len2+1;k++){
                        int j=i+len1-1,l=k+len2-1;
                        if(len1+len2<=1){
                            dp[i][j][k][l]=1;
                        }
                        else{
                            dp[i][j][k][l]=0;
                            if(len1>1&&a[i]==a[j]) dp[i][j][k][l]+=dp[i+1][j-1][k][l];
                            if(len2>1&&b[k]==b[l]) dp[i][j][k][l]+=dp[i][j][k+1][l-1];
                            if(len1&&len2&&a[i]==b[l]) dp[i][j][k][l]+=dp[i+1][j][k][l-1];
                            if(len1&&len2&&a[j]==b[k]) dp[i][j][k][l]+=dp[i][j-1][k+1][l];
                        }
                        if(dp[i][j][k][l])
                            ans=max(ans,len1+len2);
                    }
                }
            }
        }
        printf("%d\n",ans);
    }
    return 0;
}

三 数学考试

题意:
求两个长度为k的区间的区间和最大

题解:
前缀和+线性dp

开始还觉得区间和可能会存不下,以为会暴long long,但是是我算错了

开始的想法是用滑动窗口来维护区间和,发现还是不能实现,这个dp开始还真没想到

dp1[i]=max(sum[i]-sum[i-k],dp1[i-1]);  //i之前的最大区间和
dp2[i]=max(sum[i+k-1]-sum[i-1],dp2[i+1]);  //i之后需的最大区间和
#include<bits/stdc++.h>
#define lowbit(x) x&(-x)
#define ll long long
#define inf 0x3f3f3f3f
using namespace std;
const ll mod=1e9+7;
const int maxn=2e5+5;
ll a[maxn],sum[maxn];
ll dp1[maxn]; //i之前的最大区间和
ll dp2[maxn]; //i之后的最大区间和
int main(){
    int t,n,k;
    scanf("%d",&t);
    while(t--){
        scanf("%d%d",&n,&k);
        sum[0]=0;
        memset(dp1,-inf,sizeof(dp1));
        memset(dp2,-inf,sizeof(dp2));
        for(int i=1;i<=n;i++){
            scanf("%lld",&a[i]);
            sum[i]=sum[i-1]+a[i];
        }
        for(int i=k;i<=n-k;i++){
            dp1[i]=max(sum[i]-sum[i-k],dp1[i-1]);
        }
        for(int i=n-k+1;i>=k+1;i--){
            dp2[i]=max(sum[i+k-1]-sum[i-1],dp2[i+1]);
        }
        ll ans=-1e18;
        for(int i=k;i<=n-k;i++){
            ans=max(ans,dp1[i]+dp2[i+1]);
        }
        printf("%lld\n",ans);
    }
    return 0;
}

四 滑动窗口

题意:
一个长度为k的滑动窗口,求窗口在各个位置的最大值和最小值

题解:
开始打算试试线段树能不能过,但是很遗憾,mle(内存超限)
于是就用队列来维护最大/小值

1.用数组模拟队列

//队列过长
if(l<=r&&(i-q[l])>=k)
    l++;
//将队列中比将要入队列的元素大的通通出队列
while(l<=r&&a[q[r]]>=a[i]){
    r--;
}

故完整代码:

#include<bits/stdc++.h>
#define lowbit(x) x&(-x)
#define ll long long
#define inf 0x3f3f3f3f
using namespace std;
const ll mod=1e9+7;
const int maxn=1e6+5;
int n,k;
int a[maxn],q[maxn];
int main(){
    scanf("%d%d",&n,&k);
    for(int i=1;i<=n;i++){
        scanf("%d",&a[i]);
    }
    q[1]=1;
    int l=1,r=1;
    int flag=0;
    for(int i=2;i<=n;i++){
        //队列过长
        if(l<=r&&(i-q[l])>=k)
            l++;
        while(l<=r&&a[q[r]]>=a[i]){
            r--;
        }
        q[++r]=i;
        if(i>=k){
            if(flag==0){
                printf("%d",a[q[l]]);
                flag=1;
            }
            else{
                printf(" %d",a[q[l]]);
            }
        }
    }
    printf("\n");
    l=1,r=1;
    q[1]=1;
    flag=0;
    for(int i=2;i<=n;i++){
        if(l<=r&&(i-q[l])>=k)
            l++;
        while(l<=r&&a[q[r]]<=a[i]){
            r--;
        }
        q[++r]=i;
        if(i>=k){
            if(flag==0){
                printf("%d",a[q[l]]);
                flag=1;
            }
            else{
                printf(" %d",a[q[l]]);
            }
        }
    }
    printf("\n");
    return 0;
}

2.使用STL的deque(支持头插尾插以及头删尾删):

#include<bits/stdc++.h>
#define lowbit(x) x&(-x)
#define ll long long
#define inf 0x3f3f3f3f
using namespace std;
const ll mod=1e9+7;
const int maxn=1e6+5;
int a[maxn];
deque<int>q;
int main(){
    int n,k;
    scanf("%d%d",&n,&k);
    for(int i=1;i<=n;i++){
        scanf("%d",&a[i]);
    }
    while(!q.empty()) q.pop_front();
    q.push_back(1);
    int flag=0;
    for(int i=2;i<=n;i++){
        if(!q.empty()&&(i-q.front())>=k){
            q.pop_front();
        }
        while(!q.empty()&&a[q.back()]>=a[i]){
            q.pop_back();
        }
        q.push_back(i);
        if(i>=k){
            if(flag==0){
                flag=1;
                printf("%d",a[q.front()]);
            }
            else{
                printf(" %d",a[q.front()]);
            }
        }
    }
    printf("\n");
    while(!q.empty()) q.pop_front();
    q.push_back(1);
    flag=0;
    for(int i=2;i<=n;i++){
        if(!q.empty()&&(i-q.front())>=k){
            q.pop_front();
        }
        while(!q.empty()&&a[q.back()]<=a[i]){
            q.pop_back();
        }
        q.push_back(i);
        if(i>=k){
            if(flag==0){
                flag=1;
                printf("%d",a[q.front()]);
            }
            else{
                printf(" %d",a[q.front()]);
            }
        }
    }
    printf("\n");
    return 0;
}

五 城市窗口

六 Rinne Loves Edges

题意:
n个顶点,m条边的无向连通图,每条边都有对应的权值
给出一个点s,删去一些边,使得不包括这个点,其他度数为1的点都不能和s联通

题解:
题目中说m=n-1,这很明显就是一棵树,现在问题就转化为给定根节点,要使叶子结点不能到达根节点,需要删除多少条边,求最少的代价

想到树形dp

dp[u]+=min(dp[v],c);
//u表示当前节点,v:u的儿子节点,c表示两节点之间的距离

使用链式向前星存图的代码:

#include<bits/stdc++.h>
#define lowbit(x) x&(-x)
#define ll long long
#define inf 0x3f3f3f3f
using namespace std;
const ll mod=1e9+7;
const int maxn=1e5+5;
int n,m,s;
struct node{
    int to,nxt;
    ll val;
}edge[maxn<<2];
int head[maxn],tot;
ll dp[maxn];
void init(){
    memset(head,-1,sizeof(head));
    tot=0;
}
void add(int u,int v,int c){
    edge[++tot].to=v;
    edge[tot].val=c;
    edge[tot].nxt=head[u];
    head[u]=tot;
}
void dfs(int u,int pre){
    int flag=0;
    for(int i=head[u];i!=-1;i=edge[i].nxt){
        int v=edge[i].to;
        if(v==pre) continue;
        flag=1;
        dfs(v,u);
        dp[u]+=min(dp[v],edge[i].val);
    }
    if(flag==0)
        dp[u]=inf;
}
int main(){
    scanf("%d%d%d",&n,&m,&s);
    init();
    int u,v,c;
    for(int i=1;i<=m;i++){
        scanf("%d%d%d",&u,&v,&c);
        add(u,v,c),add(v,u,c);
    }
    dfs(s,0);
    printf("%lld\n",dp[s]);
    return 0;
}

七 月月查华华的手机

题意:
给出一个长字符串,随之又给出n个短字符串,判断短字符串是不是长字符串的子序列

题解:

  1. 暴力
    时间2s,可以直接对长字符串和短字符串遍历即可
#include<bits/stdc++.h>
#define lowbit(x) x&(-x)
#define ll long long
#define inf 0x3f3f3f3f
using namespace std;
const ll mod=1e9+7;
const int maxn=1e6+5;
char str[maxn],s[maxn];
int main(){
    scanf("%s",str);
    int len1=strlen(str);
    int n;
    scanf("%d",&n);
    while(n--){
        scanf("%s",s);
        int len2=strlen(s);
        int i=0,j=0;
        while(i<len1&&j<len2){
            if(str[i]==s[j]){
                i++,j++;
            }
            else{
                i++;
            }
        }
        if(j==len2){
            printf("Yes\n");
        }
        else{
            printf("No\n");
        }
    }
    return 0;
}

  1. 枚举优化
    在这里插入图片描述
#include<bits/stdc++.h>
#define lowbit(x) x&(-x)
#define ll long long
#define inf 0x3f3f3f3f
using namespace std;
const ll mod=1e9+7;
const int maxn=1e6+5;
char str[maxn],s[maxn];
int last[30],nxt[maxn][30];
void init(){
    memset(last,-1,sizeof(last));
    int len=strlen(str);
    for(int i=len-1;i>=0;i--){
        for(int k=0;k<26;k++){
            nxt[i][k]=last[k];
        }
        last[str[i]-'a']=i;   //将last['a'...'z']复制给next[i]['a'...'z']
    }
}
int check(){
    int len1=strlen(str);
    int len2=strlen(s);
    int i=last[s[0]-'a'];
    if(i==-1)
        return 0;
    for(int j=1;j<len2;j++){   //遍历子串字母
        i=nxt[i][s[j]-'a'];   //i跳到前一个字母后面的当前字母
        if(i==-1)
            return 0;
    }
    return 1;
}
int main(){
    scanf("%s",str);
    init();
    int n;
    scanf("%d",&n);
    while(n--){
        scanf("%s",s);
        if(check()) printf("Yes\n");
        else printf("No\n");
    }
    return 0;
}

八 排列计算

题意:
n个数的序列,m次询问,每次询问给定一区间的左右端点,求该区间的和
最终求m次询问的总和

题解:
差分前缀和,直接见代码(也是学到了):

#include<bits/stdc++.h>
#define lowbit(x) x&(-x)
#define ll long long
#define inf 0x3f3f3f3f
using namespace std;
const ll mod=1e9+7;
const int maxn=2e5+5;
int a[maxn];
bool cmp(int x,int y){
    return x>y;
}
int main(){
    int n,m,l,r;
    memset(a,0,sizeof(a));
    scanf("%d%d",&n,&m);
    while(m--){
        scanf("%d%d",&l,&r);
        a[l]++,a[r+1]--;
    }
    for(int i=2;i<=n;i++){
        a[i]+=a[i-1];
    }
    sort(a+1,a+n+1,cmp);
    ll ans=0,num=n;
    for(int i=1;i<=n;i++){
        ans+=num*a[i];
        num--;
    }
    printf("%lld\n",ans);
    return 0;
}

九 Shortest Path

题目链接

题意:
n个节点,(n-1)条边,每条边都有对应的权值,然后题目要求对n个结点分成n/2对,最终需要求满足情况所有对的权值的和中的最小值

题解:
思维+dfs(思维很重要!!!)

看题目会发现是个树形结构

如果从点的角度出发,会比较难想,我最开始就是的,然后t了…
可以发现,不可能出现重边的情况,

  • 如果某个结点子树的结点总数(包括自身)是偶数的话,就直接在子树中选择边两两配对即可;
  • 否则的话就需要在其兄弟结点中选择
    于是的话,偶数结点情况就无需处理,奇数结点情况则加边权

对于子树结点,我们可以开一个数组存
然后至于图的话,向前星和vector都可以

#include<bits/stdc++.h>
#define lowbit(x) x&(-x)
#define ll long long
#define inf 0x3f3f3f3f
using namespace std;
const ll mod=1e9+7;
const int maxn=1e4+5;
int t,n;
struct Edge{
    int to,nxt,val;
}edge[maxn<<2];
int head[maxn],tot,cnt[maxn];   //cnt数组存当前结点所在子树的结点数(包括自己)
ll ans;
void init(){
    memset(head,-1,sizeof(head));
    memset(cnt,0,sizeof(cnt));
    tot=0;
}
void add(int u,int v,int c){
    edge[++tot].to=v;
    edge[tot].val=c;
    edge[tot].nxt=head[u];
    head[u]=tot;
}
void dfs(int u,int pre,int num){
    cnt[u]=1;
    for(int i=head[u];i!=-1;i=edge[i].nxt){
        int v=edge[i].to;
        if(v==pre) continue;
        dfs(v,u,edge[i].val);
        cnt[u]+=cnt[v];
    }
    if(cnt[u]&1) ans+=num;   //奇数
}
int main(){
    scanf("%d",&t);
    while(t--){
        init();
        scanf("%d",&n);
        int u,v,c;
        int pos=0;
        for(int i=1;i<n;i++){
            scanf("%d%d%d",&u,&v,&c);
            add(u,v,c),add(v,u,c);
        }
        ans=0;
        dfs(1,0,0);
        printf("%lld\n",ans);
    }
    return 0;
}

十 树

题目链接

题意:
一棵树,n个结点,k种颜色
一对结点如果颜色相同的话,它们中间的结点颜色也必须相同

其实这题跟树的长什么样是没有关系的,这里可以想到dfs序,将树形转化为线性(这谁想得到啊,我自闭了啊)

然后考虑dp[i][j]前i个结点使用了j种颜色

通过分析可以知道:如果当前结点需要用已经用过的颜色的话,就必须和父亲结点的颜色相同(这个还是比较容易理解)

然后状态转移方程就出来了

dp[i][j]=dp[i-1][j]+dp[i-1][j-1]*(k-j+1);

需要注意的是,这个dp数组是前i个结点用了j种颜色,最终我们求的答案需要对前n个结点分别用了多少种颜色求总和

#include<bits/stdc++.h>
#define lowbit(x) x&(-x)
#define ll long long
#define inf 0x3f3f3f3f
using namespace std;
const ll mod=1e9+7;
const int maxn=2e5+5;
ll dp[310][310];
int main(){
    int n,k;
    scanf("%d%d",&n,&k);
    int a,b;
    for(int i=1;i<n;i++) scanf("%d%d",&a,&b);
    dp[0][0]=1;
    for(int i=1;i<=n;i++){
        for(int j=1;j<=k;j++){
            dp[i][j]=(dp[i-1][j]+dp[i-1][j-1]*(k-j+1))%mod;
        }
    }
    ll ans=0;
    for(int i=1;i<=k;i++) ans=(ans+dp[n][i])%mod;
    printf("%lld\n",ans);
    return 0;
}

十一 校车

题目链接:西安邮电大学第五届ACM-ICPC校赛

本场比赛签到题之一,但是没做出来,不想说话,好好补题

题意:
给出n个人的上下车的情况,求:最少需要设置多少个站点,校车最少需要设置多少个位置

题解:
站点问题可以直接使用set去重

位置问题转化为:求最大重叠区间问题

对于每个人上下车的情况,我们很难想到策略,可以将其转化为:点问题
对于每个人的上下车的站点,上车则+1,下车则-1,然后遍历出现过的所有点,选择所有站点中:校车人数最多的情况即为答案

为了防止:判断当前出现的站点之前是否出现过,可以使用map这个数据结构来维护

代码如下:

#include<bits/stdc++.h>
#define lowbit(x) x&(-x)
#define ll long long
#define inf 0x3f3f3f3f
using namespace std;
const ll mod=1e9+7;
const int maxn=1e5+5;
int t,n;
set<int>st;
map<int,int>mp;
int main(){
    scanf("%d",&t);
    while(t--){
        st.clear();mp.clear();
        scanf("%d",&n);
        int l,r;
        for(int i=1;i<=n;i++){
            scanf("%d%d",&l,&r);
            st.insert(l),st.insert(r);
            mp[l]++,mp[r]--;
        }
        int ans=0,num=0;
        map<int,int>::iterator it;
        for(it=mp.begin();it!=mp.end();it++){
            num+=it->second;
            ans=max(ans,num);
        }
        printf("%d %d\n",st.size(),ans);
    }
    return 0;
}

十二 黑白树

题目链接

题意:
n个顶点的树,根为1;结点:2~n给出其对应的父节点,每个结点都有一个值:k[i],开始所有结点都是白色,每次操作可以选择任意结点,当前结点到根节点(即:1)的所有结点如果是白色则变为黑色,问最少需要多少次操作才能使所有结点都变成黑色

题解:

又是树类型的题,然后又挂了

官方题解

#include<bits/stdc++.h>
#define lowbit(x) x&(-x)
#define ll long long
#define inf 0x3f3f3f3f
using namespace std;
const ll mod=1e9+7;
const int maxn=1e5+5;
int n;
struct node{
    int to,nxt;
}edge[maxn<<2];
int head[maxn],tot,dp[maxn],k[maxn];
int ans;
void init(){
    memset(head,-1,sizeof(head));
    tot=0;
}
void add(int u,int v){
    edge[++tot].to=v;
    edge[tot].nxt=head[u];
    head[u]=tot;
}
void dfs(int u,int pre){
    for(int i=head[u];i!=-1;i=edge[i].nxt){
        int v=edge[i].to;
        if(v==pre) continue;
        dfs(v,u);
        dp[u]=max(dp[u],dp[v]-1);
        k[u]=max(k[u],k[v]-1);
    }
    if(dp[u]==0){
        ans++,dp[u]=k[u];
    }
}
int main(){
    init();
    scanf("%d",&n);
    int j;
    memset(dp,0,sizeof(dp));
    for(int i=2;i<=n;i++){
        scanf("%d",&j);
        add(i,j),add(j,i);
    }
    for(int i=1;i<=n;i++){
        scanf("%d",&k[i]);
    }
    dfs(1,0);
    printf("%d\n",ans);
    return 0;
}

十三 Running Median

题目链接
英文题,看了好久的题意,原来就是给定n个数,每当遇到第奇数个数时,输出当前已知的数中的中位数

自认为数据不大,就用一个优先队列来维护,可是每次更新队列都需要O(n^2logn),结果很显然,超时了

看了题解,不乏有用平衡树、主席树来维护的,但是学的是官方题解介绍的用小根堆+大根堆维护

题解:
大根堆:即从大到小排序,堆顶为最大值
小根堆:即从小到大排序,堆顶是最小是

c++STL中有priority_queue(即:优先队列来实现堆)

题目要求:中位数,不妨用小根堆存较大的那一部分,大根堆存较小的那一部分,
如果两个堆的大小之差大于1时,则更新下堆:
即:如果小根堆大小-大根堆大小>1,则说明大的那一部分多了,然后我们将小根堆的堆顶放进大根堆

int len1=q1.size(),len2=q2.size();
if(len1-len2>1){  //大根堆元素多了,将堆顶放进小根堆
       q2.push(q1.top());
       q1.pop();
}
else if(len2-len1>1){  //小根堆元素多了,将堆顶放进大根堆
       q1.push(q2.top());
       q2.pop();
}

代码如下:

#include<bits/stdc++.h>
#define lowbit(x) x&(-x)
#define ll long long
#define inf 0x3f3f3f3f
using namespace std;
const ll mod=987654321;
const int maxn=1e5+5;
int t,cas,n;
int ans[maxn];
int main(){
    scanf("%d",&t);
    while(t--){
        priority_queue<int,vector<int>, greater<int> >q1;  //存较大的一部分,小根堆(从小到大)
        priority_queue<int,vector<int>, less<int> >q2;    //存较小的一部分,大根堆(从大到小)
        scanf("%d%d",&cas,&n);
        int cnt=0,num;
        //第一个元素特殊处理
        scanf("%d",&num);
        q2.push(num);
        ans[++cnt]=num;
        for(int i=2;i<=n;i++){
            scanf("%d",&num);
            if(num<=q2.top()){
                q2.push(num);
            }
            else{
                q1.push(num);
            }
            int len1=q1.size(),len2=q2.size();
            if(len1-len2>1){  //大根堆元素多了,将堆顶放进小根堆
                q2.push(q1.top());
                q1.pop();
            }
            else if(len2-len1>1){  //小根堆元素多了,将堆顶放进大根堆
                q1.push(q2.top());
                q2.pop();
            }
            if(i&1){
                len1=q1.size(),len2=q2.size();
                if(len1>len2){
                    ans[++cnt]=q1.top();
                }else{
                    ans[++cnt]=q2.top();
                }
                //cout<<ans[cnt]<<' ';
            }
        }
        printf("%d %d\n",cas,cnt);
        for(int i=1;i<=cnt;i++){
            if(i%10==0) printf("%d\n",ans[i]);
            else printf("%d ",ans[i]);
        }
        if(cnt%10) printf("\n");
    }
    return 0;
}

猜你喜欢

转载自blog.csdn.net/boliu147258/article/details/105901694