CSP-S2周末刷题班(第四场)

CSP-S2周末刷题班(第四场)

A. 挑战

时间限制: 1.0 秒

空间限制: 512 MB

原题地址

题目描述

企鹅豆豆在玩一款叫做 Slay the spire 的游戏。为了简化游戏,我们将游戏规则魔改如下:

主角一开始血量为 H,游戏里一共有 N 个房间,每个房间里有一些怪物,第 i 个房间需要受到 Di 的伤害才能解决所有怪物, 解决怪物后会获得对应的

[C_i ]

的血量恢复,

[C_i ]

严格小于

[D_i ]

,一个房间的怪物只能最多打一次。主角只有在血量为负数时才会死亡,即使血量刚好为 0 主角也能凭借强大的意志继续打怪。

现在给出每个房间的数据,豆豆想知道他最多能打完多少个房间?

输入格式

从标准输入读入数据。

输入第一行两个正整数 N,H,表示房间数目和主角的血量。

输入第二行有 N 个正整数

[D_i, ]

第 i 个数对应第 i 个房间。

输入第三行有 N 个非负整数

[C_i ]

, 第 i 个数对应第 i 个房间。

输出格式

输出到标准输出。

输出一行一个整数表示最多能打完的房间数目。

样例1输入

4 12
4 8 2 1
2 0 0 0

样例1输出

3

样例1解释

他可以选择 1,3,4号房间。

样例2

下发文件

子任务

对于 30% 的数据, 1≤N≤10。

对于另外 10% 的数据,

[C_i=0。 ]

对于另外 10% 的数据,

[D_i−C_i=1。 ]

对于另外 30% 的数据,1≤N≤1000。

对于 100% 的数据,

[1≤N≤5×10^3。 ]

对于所有数据,

对于所有数据,

[0≤C_i<D_i≤5000,1≤D_i,H≤5000。 ]

100分解法

#include<bits/stdc++.h>
using namespace std;
typedef pair<int,int> PII;
#define fi first
#define se second

const int maxn = 5005;
const int maxm = 5100;

int n,H;
PII a[maxn];
int dp[maxm];


bool cmp(const PII &a, const PII &b)
{
    return a.se != b.se ? a.se > b.se : a.fi - a.se > b.fi - b.se;
}

int solve()
{
    int ans = 0;
    sort(a, a + n, cmp);
    memset(dp, -1, sizeof(dp));
    dp[0] = 0;
    for (int i = 0; i < n; i ++)
    {
        for (int j = H - a[i].fi; j >= 0; j --)
        {
            if (dp[j] != -1)
            {
                dp[j + a[i].fi - a[i].se] = max(dp[j + a[i].fi - a[i].se], dp[j] + 1);
                ans = max(ans, dp[j + a[i].fi - a[i].se]);
            }
        }
    }
    return ans;
}

int main(){
    scanf("%d%d", &n, &H);
    for (int i = 0; i < n; i ++)
        scanf("%d", &a[i].first);
    for (int i = 0; i < n; i ++)
        scanf("%d", &a[i].second);
    int ans = solve();
    printf("%d", ans);
    return 0;
}

B. 航班复杂度

时间限制: 1.0 秒

空间限制: 512 MB

原题地址

题目描述

企鹅国有 N 座城市和 M 条不同的航班,每条航班都有指定的

[起点u_i 和终点v_i ]

,乘坐这个航班可以

[从城市u_i到达城市v_i ]

,但是不能

[从v_i到u_i ]

,也就是这个航班是单向的。

企鹅豆豆最近被企鹅航空抽中成为 SSSSVIP,并获得了 L 张航班卷,一张航班卷可以免费乘坐一次航班。企鹅豆豆迫不及待地要开始他的 环国之旅,他会根据自己心情随意选择一个起点城市,然后连续乘坐 L 次航班。对于每种连续乘坐航班的方案,我们会记录他到达每个 城市的顺序,生成一个对应的序列,比如 3,2,3 表示他乘坐了两次航班,从 3 号城市飞到 2 号城市再飞到 3 号城市。两个连续乘坐航班方案不同,当且仅当对应的序列不同。

企鹅豆豆现在现在想知道他最多能有多少种不同的连续航班乘坐方案。但是由于 L 实在是太太太大了,所以答案 Ans 也非常非常大,所以他并不想求出具体值,他发明了一个叫做航班复杂度的计量方式——我们称航班复杂度是整数 k,当且仅当存在常数 c 使得对于充分大的

[L, cL^k>Ans ]

(注意 Ans 也是会随 L 的增大而增大)。

豆豆现在想知道,当前的航班复杂度 k 最小能是多少?

对于c的解释

“当且仅当存在常数 cc 使得” 是指,你能够找到一个c的特定值,比如 1000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000,使得对于任意大的 L都满足要求。但是C不能取无限大,因为“无限大”不是常数

关于c的取值

这里说C是某一个常数,即为C是一个固定的可以写出来的数字,如1000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000,但是“无限大”不是常数

输入格式

从标准输入读入数据。

第一行两个正整数 N,M,表示城市数目和航班数。

接下里 M 行,每行两个正整数

[u_i,v_i(u_i≠v_i) ]

描述一个航班。

输出格式

输出到标准输出。

输出一行一个整数 k 表示答案。 如果不存在任何整数 k 满足要求,请输出 -1

样例1输入

5 5
1 2
2 3
3 4
4 5
5 1

样例1输出

0

样例1解释

由于从任意城市出发坐 L 次航班只有一种方案,所以令常数 c=2 有

[cL^0>1 ]

,且不存在更小的整数 k 满足这个要求。

样例2

下发文件

子任务

对于 15% 的数据,航班形成多个不相交的环。

对于另外 15% 的数据,如果存在航班 (ui,vi) 则存在航班 (vi,ui)。

对于另外 15% 的数据,航班形成的简单环只有两个,以及一些不形成环的航班。

对于另外 20% 的数据,1≤n≤100,1≤m≤300。

对于另外 20% 的数据,1≤n≤1000,1≤m≤3000。

对于另外 5% 的数据,m=5。

对于 100% 的数据,

[1≤n,m≤10^5 ]

100分算法

#include<bits/stdc++.h>
using namespace std;
#define REP(i,n) for(int _n=n, i=0;i<_n;++i)
#define FORD(i,a,b) for(int i=(a),_b=(b);i>=_b;--i)
#define FOREACH(it,c) for(auto it=(c).begin();it!=(c).end();++it)
typedef vector<int> VI;
template<class T> inline int size(const T&c) { return c.size(); }
 
int V,E;
vector<vector<int> > edges;
vector<vector<int> > backEdges;
vector<int> scc;
vector<int> order;
vector<vector<int>> groups;
vector<char> vis;
 
vector<int> groupType;
vector<int> groupCnt;
 
void dfs1(int x) {
  if(vis[x]) return;
  vis[x]=true;
  FOREACH(it, edges[x]) dfs1(*it);
  order.push_back(x);
}
 
void dfs2(int x) {
  if(vis[x]) return;
  vis[x] = true;
  groups.back().push_back(x);
  scc[x] = size(groups)-1;
  FOREACH(it, backEdges[x]) dfs2(*it);
}
 
void calcSCC() {
  order.clear();
  vis.assign(V, 0);
  REP(i, V) if(!vis[i]) dfs1(i);
  vis.assign(V, 0);
  scc.assign(V, -1);
  FORD(i, V-1, 0) if(!vis[order[i]]) {
    groups.push_back(VI());
    dfs2(order[i]);
  }
}
 
bool checkOK() {
  groupType.assign(size(groups), 1);
  REP(g, size(groups)) {
    for(int x : groups[g]) {
      int cnt = 0;
      for(int y : edges[x]) if(scc[y]==g) {
        ++cnt;
      }
      if(cnt>1) return false;
      if(cnt==0) groupType[g]=0;
    }
  }
  return true;
}
 
void calcGroupCnt() {
  groupCnt = groupType;
  REP(g, size(groups)) {
    for(int x : groups[g]) {
      for(int y : backEdges[x]) if(scc[y]!=g) {
        int h = scc[y];
        groupCnt[g] = max(groupCnt[g], groupType[g] + groupCnt[h]);
      }
    }
  }
}

int main(){
    scanf("%d%d",&V,&E);
    edges.resize(V);
    backEdges.resize(V);
    REP(i,E){
        int u,v;
        scanf("%d%d",&u,&v);
        u--,v--;
        edges[u].push_back(v);
        backEdges[v].push_back(u);
    }
    calcSCC();
    if(!checkOK()) return puts("-1"),0;
    calcGroupCnt();
    int res = *max_element(groupCnt.begin(), groupCnt.end());
    if(res>0) --res;
    printf("%d",res);
    return 0;
}

C. 数据生成器

时间限制: 1.0 秒

空间限制: 512 MB

原题地址

题目描述

豆豆正在熬夜造下周NOIP模拟赛的数据。

他在写一道“求一个数列最长连续不下降子串的长度”的数据生成器, 第i个整数的生成范围是

[[L_i,R_i] ]

他想知道生成的数列的答案最大能是多少?

这里 最长连续不下降子串 是指原序列最长的连续区间,该区间内数值单调不减。

输入格式

从标准输入读入数据。

输入第一行两个整数 N,表示数列长度。

接下来 N 行,每行两个数

[Li 和 Ri ]

输出格式

输出到标准输出。

输出一行一个整数表示最大的答案。

样例1输入

6
6 10
1 5
4 8
2 5
6 8
3 5

样例1输出

4

样例1解释

一个可能的数列为:

6,3,5,5,7,3

样例2

下发文件

子任务

对于 10%10% 的数据,N≤10,1≤Li≤Ri≤6

对于 30% 的数据,N≤300

对于 40% 的数据,N≤2000

对于 70% 的数据,N≤100000

对于 100% 的数据,

[N≤3000000,1≤Li≤Ri≤10^9 ]

请选手选择较快的读入方式。

100分算法

#include<cstdio>  
#include<cstring>  
#include<algorithm>  
using namespace std;  
int left[3000010],right[3000010],que[3000010],h,t;  
int n,ans;  
inline int read(){
    char ch=getchar();  
    int opt=1,x=0;  
    while(!(ch>='0'&&ch<='9')) if(ch=='-') opt=-1,ch=getchar();  
    while(ch>='0'&&ch<='9') x=x*10+(ch-'0'),ch=getchar();  
    return (x*opt);   
}  
int main(){  
    int i,j;  
    n=read();  
    for(i=1;i<=n;++i){  
        int x=read(),y=read();  
        left[i]=x; right[i]=y;  
    }
    h=1; ans=1;  
    for(i=1;i<=n;++i){  
        while(h<=t&&left[que[t]]<=left[i]) --t;  
        que[++t]=i;  
        while(h<=t&&left[que[h]]>right[i]) ++h;  
        ans=max(ans,i-que[h-1]);  
    }  
    printf("%d
",ans);  
    return 0;  
 }   

D. 排列组合

时间限制: 1.0 秒

空间限制: 512 MB

原题地址

题目描述

企鹅豆豆最近沉迷排列无法自拔,他现在有一个长度为 N 的排列,他现在拥有神力每次可以把这个排列的一个非空区间里所有数字变成 这个区间数字的最大值。比如他可以对于 1,3,2,4 的 [3,4] 区间进行这个操作,使之变为 1,3,4,4。

他喜欢没见过的排列,所以他想知道通过不超过 K 个这样的操作,可以把一个排列变成多少种不同的序列?

一个长度为 N 排列是指一个长度为 N 含有正整数 1 到 N 且任意两个不同位置的数字不相同的序列。 两个序列不同当且仅当他们有一个位置上的数值不同。

输入格式

从标准输入读入数据。

输入第一行两个整数 N 和 K,表示排列的长度以及最多的操作次数。

接下来一行 N 个正整数表示这个排列。

输出格式

输出到标准输出。

输出一行一个非负整数表示所有能够变成的不同的序列个数。由于答案可能会非常大,所以你需要把答案对 998244353 取模。

样例1输入

3 2
3 1 2

样例1输出

4

样例1解释

所有可能的序列有:

  • 3,1,23,1,2 (可以不操作)
  • 3,2,23,2,2
  • 3,3,23,3,2
  • 3,3,33,3,3

样例2

下发文件

样例3

下发文件

子任务

对于 1−6号测试点,第 i 个测试点保证

[N=2_i ]

对于 7−8 号测试点,保证K=1。

对于 9−12 号测试点,保证序列严格上升,且 9,10 测试点 N=K。

对于 13−14 号测试点,保证N,K≤20。

对于 15−16 号测试点,保证N,K≤50。

对于 17−18 号测试点,保证 N,K≤100。

对于所有数据保证

[1≤N≤200,0≤K≤200 ]

100分算法

#include<bits/stdc++.h>
using namespace std;

#define forn(i, n) for (int i = 0; i < (int)(n); i++)

const int MAXN = 205;
const int MOD = 998244353;

int d[MAXN][MAXN][MAXN];
int p[MAXN], l[MAXN], r[MAXN];
int n, K;

int solve(){
    memset(d, 0, sizeof(d));
    forn(i, n) {
        l[i] = i;
        while (l[i] >= 0 && p[l[i]] <= p[i]) l[i]--;
        r[i] = i;
        while (r[i] < n && p[r[i]] <= p[i]) r[i]++;
        l[i]++;
        r[i]--;
    }
    d[0][0][0] = 1;
    forn(i, n) {
        forn(j, n + 1) {
            forn(k, K + 1) {
                if (d[i][j][k] == 0) continue;
                d[i + 1][j][k] += d[i][j][k];
                if (d[i + 1][j][k] >= MOD) d[i + 1][j][k] -= MOD;
                if (j < l[i]) {
                    continue;
                }
                int start = max(l[i], j);
                for (int pos = start; pos <= r[i]; pos++) {
                    int nk = k + 1;
                    if (start == i && pos == i) nk--;
                    d[i + 1][pos + 1][nk] += d[i][j][k];
                    if (d[i + 1][pos + 1][nk] >= MOD) d[i + 1][pos + 1][nk] -= MOD;         
                }            
            }
        }
    }

    int ans = 0;
    forn(k, K + 1) ans = (ans + d[n][n][k]) % MOD;
    return ans;
   }

int main(){
    scanf("%d%d", &n, &K);
    forn(i, n) {
        scanf("%d", &p[i]);
    }
    int ans = solve();
    printf("%d", ans);
    return 0;
}
原文地址:https://www.cnblogs.com/royann/p/13905806.html