题目集地址 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;
}