【题目记录】——2021CCPC新疆省赛


题目集地址 2021CCPC新疆省赛

A balloon

题目大意:n个孩子分别可以跳到h的高度,m个气球每个有一个位置(高度),孩子起跳拿走所有能摸到的气球,孩子按照起跳高度从低到高依次起跳,问每个孩子能拿到的气球数量。
思路:把孩子们跳的高度从低到高排个序,气球的高度也从低到高排序,遍历一下每个孩子能拿到多少个气球就行了。

#include <bits/stdc++.h>
#define ll long long
#define pii pair<int,int>
using namespace std;
const int maxn=1e5+10;
const int N=5000000;
int n,m,k,s;
int u,v,w,t;

struct Node{
    int val,index;
}a[maxn];
int b[maxn],ans[maxn],ans2[maxn];

bool cmp(Node aa,Node bb)
{
    return aa.val<bb.val;
}

void solve()
{
    scanf("%d%d",&n,&m);
    int zan;
    for(int i=1;i<=n;i++)
    {
        scanf("%d",&zan);
        a[i].index=i;
        a[i].val=zan;
    }
    for(int i=1;i<=m;i++)
    {
        scanf("%d",&b[i]);
    }
    sort(a+1,a+1+n,cmp);
    sort(b+1,b+1+m);
    int xia=1;
    int res=0;
    for(int i=1;i<=n;i++)
    {
        res=0;
        for(;xia<=m;xia++)
        {
            if(b[xia]<=a[i].val)
                res++;
            else
                break;
        }
        ans[i]=res;
    }
    for(int i=1;i<=n;i++)
    {
        ans2[a[i].index]=ans[i];
    }
    for(int i=1;i<=n;i++)
    {
        printf("%d\n",ans2[i]);
    }
}


int main()
{
    t=1;
    //scanf("%d",&t);
    while(t--)
    {
        solve();
    }
    return 0;
}

B sophistry

题目大意:小k有n天在群里嘲笑管理员,小K在第i天嘲笑管理员,管理员会受到ai的伤害,如果这个伤害超过m值,管理员会将小k禁言d天,问小k能对管理员造成的最大伤害是多少。
思路:dp,dp的值表示到第i天最多对管理员造成多少伤害。但是我们要倒着进行dp,便于计算被禁言d天的情况下dp[i]的最大值。
a[i]<=m的时候,dp[i]=dp[i+1]+a[i]
a[i]>m的时候,dp[i]=max(dp[i+1],dp[i+1+d]+a[i]);
当a[i]>m的时候,我们可以选择今天不嘲笑,那么dp[i]的最大值就是dp[i+1]
如果我们今天嘲笑,那么就会被禁言d天,dp[i]=dp[i+1+d]+a[i]

#include<bits/stdc++.h>
#define ll long long
using namespace std;
const int N=1e5+5;
ll dp[N];
ll a[N];
int main()
{
    int n,d,m;
    cin >>n>>d>>m;
    for(int i = 1;i <= n;i++)
    {
        cin >> a[i];
    }
    dp[n]=a[n];
    for(int i = n-1;i >= 1;i--)
    {
        if(a[i]<=m)
        {
            dp[i]=dp[i+1]+a[i];
        }
        else
        {
            if(i+1+d>n)
            dp[i]=max(dp[i+1],a[i]);
            else{
                dp[i]=max(dp[i+1],dp[i+1+d]+a[i]);
            }
        }
    }
    cout << dp[1] <<endl;
    return 0;
}

C bomb

题目大意:给一个有向图,每次涂色可以涂任意多个点,但是任意两个点之间不能有一个点可以到达另一个点。
思路:对于无环的有向图,最后的答案就是最长的链的长度,那么对于有环的有向图,一个环相当于是一个强连通分量,将强连通分量缩成一个点,赋于一个权值即强连通分量的点的数量,然后按照无环图的方法继续找出权值和最大的那一条链即可。

#include <bits/stdc++.h>
#define int long long
using namespace std;
const int maxn=1e6+50;
int n,m,head[maxn],cnt,low[maxn],dfn[maxn],vis[maxn],ans,acc,belong[maxn];
stack<int>S;
//节点数,边数,链式前向星头,边计数,追溯值,时间戳
//访问标记,tarjan计数,分量计数,索引,栈
int large[maxn],degree[maxn];
struct node {
    int to,next,from;
} e[maxn],ee[maxn];
queue<int>q;
void Add(int from,int to) {
    e[++cnt].next=head[from];
    e[cnt].to=to;
    e[cnt].from=from;
    head[from]=cnt;
}
void Adde(int from,int to) {
    ee[++cnt].next=head[from];
    ee[cnt].to=to;
    head[from]=cnt;
}
void tarjan(int id) {
    vis[id]=1;//标记访问
    dfn[id]=++ans;//时间戳
    low[id]=ans;//初始化
    S.push(id);//压入栈中
    for(int i=head[id]; ~i; i=e[i].next) {
        int v=e[i].to;//dfs下一个节点
        if(!dfn[v]) {//如果没访问过{
            tarjan(v);//下一节点
            low[id]=min(low[id],low[v]);//必须在这里也加一个
        }
        if(vis[v])
            low[id]=min(low[id],low[v]);
        //如果已经dfs过了,而且还在栈里,代表节点v的子节点也已经dfs过了
        //更新链接关系
    }
    if(low[id]==dfn[id]) {//找到一个根
        belong[id]=++acc;//建立强连通分量索引
        vis[id]=0;
        while(1) {//清除栈中的强连通分量
            int t=S.top();
            belong[t]=acc;
            large[acc]++;
            vis[t]=0;
            S.pop();
            if(t==id)
                break;
        }
    }
}
int toupo() {
    int res=0,len=0;
    while(!q.empty()) {
        len=q.size();
        res++;
        for(int i=0; i<len; i++) {
            int u=q.front();
            q.pop();
            large[u]--;
            if(large[u]==0) {
                for(int j=head[u]; ~j; j=ee[j].next) {
                    int v=ee[j].to;
                    degree[v]--;
                    if(degree[v]==0)
                        q.push(v);
                }
            } else
                q.push(u);
        }
    }
    return res;
}
signed main() {
    ios::sync_with_stdio(0);
    cin >>n>>m;
    memset(head,-1,sizeof(head));
    for(int i=1; i<=m; i++) {
        int u,v;
        cin >>u>>v;
        Add(u,v);
    }
    for(int i=1; i<=n; i++)if(!dfn[i])tarjan(i);
    cnt=0;
    memset(head,-1,sizeof(head));
    for(int i=1; i<=m; i++) {
        int u=belong[e[i].from],v=belong[e[i].to];
        if(u!=v) {
            Adde(u,v);
            degree[v]++;
        }
    }
    for(int i=1; i<=acc; i++)
        if(degree[i]==0)
            q.push(i);
    cout <<toupo();
    return 0;
}

D xsum

题目大意:给一个长度为n的数组a,求前w大的区间和的值。
思路:处理一个前缀和和后缀和,我们只需要找前w小的前缀和和后缀和的和即可。
前缀和为l后缀和为r,我们用t来保存当前和的最大值,然后不断遍历前缀和和后缀和加入堆中,直到找到w个值。

#include<bits/stdc++.h>
#define ll long long
using namespace std;
const int N=1e5+5;
ll a[N];
ll L[N],R[N],p[N];
priority_queue<ll> q;
int main()
{
    int n,w;
    cin >>n>>w;
    for(int i = 1;i <= n;i++)
    {
        cin >> a[i];
    }
    for(int i = 1;i <= n;i++)
    {
        L[i]=L[i-1]+a[i];
    }
    for(int i = n;i>=1;i--)
    {
        R[i]=R[i+1]+a[i];
    }
    for(int i = 0;i <= n;i++)
    {
        p[i]=n+1;
    }
    int num=0;
    bool f = false;
    int l = 0;
    for(int i = 0;i <= n;i++)
    {
        for(int j = p[i];j>=1;j--)
        {
            if(L[i]+R[j]>L[l+1])
            {
                p[i]=j;
                if(i==l)
                {
                    l++;
                    i=-1;
                    if(num>=w)
                    {
                        f=true;
                    }
                }
                break;
            }
            q.push(L[n]-L[i]-R[j]);
            num++;
        }
        if(f)
        {
            break;
        }
    }
    while(w--)
    {
        cout << q.top() << " ";
        q.pop();
    }
    cout << endl;
    return 0;
}

我的方法比较复杂,我看题解是这样写的,更简单一些
因为ai为非负整数,所以如果当前最大的是 [l,r][l,r] 子段,那么易得 [l,r+1][l,r+1] 子段和 [l-1,r][l−1,r] 子段一定之前就已经取出。
最大的子段一定为 [1,n],所以首先将 [1,n] 加入堆。设堆顶为 [l,r],则[l+1,r] 和[l,r−1] 可以成为备选答案,加入堆并用 map 判重即可。

时间复杂度 O ( w log ⁡ ⁡ n ) O(w \log ⁡n) O(wlogn)

#include <iostream>
#include <algorithm>
#include <cstring>
#include <cstdio>
#include <map>
#include <queue>
using namespace std;
typedef long long ll;
const int N = 1e5 + 10;

typedef pair<int, int> pii;
int n, w;
ll a[N];
ll sum;
map<pii, bool>mp;
struct node {
    int l, r;
    ll sum;
    friend bool operator < (node x, node y) {
        return x.sum < y.sum;
    }

};
priority_queue<node>q;
void solve() {
    cin >> n >> w;
    for(int i = 1; i <= n; i++)
        cin >> a[i], sum += a[i];
    q.push({1, n, sum});
    mp[ {1, n}] = 1;
    while(w) {
        auto now = q.top();
        q.pop();
        w--;
        cout << now.sum << " ";
        if(now.l != now.r) {
            if(!mp[ {now.l + 1, now.r}])
                q.push({now.l + 1, now.r, now.sum - a[now.l]});
            if(!mp[ {now.l, now.r - 1}])
                q.push({now.l, now.r - 1, now.sum - a[now.r]});
            mp[ {now.l + 1, now.r}] = mp[ {now.l, now.r - 1}] = 1;
        }
    }
    return ;
}
signed main() {
    int t = 1;
    while(t--)
        solve();
    return 0;
}
  • 0
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值