2021卓见杯第三届CCPC河南省省赛所有题超详细题解附加榜单真题解析,简单代码+详细注释+思想,要看的,补题的速速点进来 2021 10.30

本人现在比较菜,所以难免出现错误,文章中有不太恰当地方,还请大家指正。

是否因为出题人的简短题解而发愁?,是否看不懂出题人的变态模板标程?是否因为自己是小白而苦恼?来看这片文章,帮助你解决这些问题

题目已经全部补完,如果有收获请点个赞再走吧…

1001.收集金币

题目链接

算法:动态规划

状态dp思路:

f[i][0]表示前i个操作中一直没有跳的最大金币数量
f[i][1]表示前i个操作中已经跳过或者现在跳的最大金币数量

状态转移:
f[i][0]很简单只有一种状态
f[i][0]=f[i-1][0]+x

f[i][1]有两种状态可以转移过来
1.f[i][1]可以是第i个操作跳过即前i-1没有跳过即f[i-1][0]
2.前i-1已经跳过了,现在不需要跳过f[i-1][1]-x
所以
f[i][1]=max(f[i-1][0],max(f[i-1][1]-x,0ll))

最终状态
f[n][0]从头到尾一次都没跳过
f[n][1]只跳过一次的最大值

#include<iostream>
#include<cstring>
#include<algorithm>
using namespace std;
#define int long long
const int N = 20010;
int f[N][2];
void solve()
{
	memset(f, 0, sizeof f);
	int n; cin >> n;
	for(int i = 1; i <= n; i ++)
	{
		string op; cin >> op;
		int x; cin >> x;
		if(op == "LOST") x = -x;
		f[i][1] = max(max(0ll, f[i - 1][1] + x), f[i - 1][0]);
		f[i][0] = max(0ll, f[i - 1][0] + x);
	}
	cout << max(f[n][0], f[n][1]) << '\n';
}

signed main()
{
	int T; cin >> T;
	while(T --) solve(); 
}

1002.使用技能

题目链接

算法:乘法逆元+快速幂

题意:这道题题目意思比较难懂,代码比较简单。由题意知序列的总数量是m^n的,我们需要先计算出所有序列的价值和,再除以序列总数量。
因为序列存在排序状态,因此每个序列都不一样。我们直接枚举释放x次技能的总数量,再加起来较为简便,首先需要从n个序列中挑选出x个位置即C(n,x)然后乘以m个技能,因为每个技能都可以,在把其他位置填满即可即(m-1)^(n-x)最后乘以每个价值x^2
公式为C(n,x)*m*(m-1)^(n-x)*x^2

#include<iostream>
#include<cstring>
#include<algorithm>
using namespace std;
#define int long long
const int N = 100010, mod = 1e9 + 7;
int f[N], inf[N];
int qmi(int a,int b){int res=1;while(b){if(b&1)res=res*a%mod;a=a*a%mod;b>>=1;}return res;}
void init()
{
    f[0] = inf[0] = 1;
    for(int i = 1; i < N; i ++)
	{
        f[i] = f[i-1] * i % mod;
        inf[i] = inf[i-1] * qmi(i, mod-2) % mod;
    } 
}
int C(int a, int b)
{
    if(a < b)return 0;
    return f[a] % mod * inf[b] % mod * inf[a-b] % mod;
}
void solve()
{
	int n, m; cin >> n >> m;
	int res = 0 ;
	for(int i = 1; i <= n; i ++)
		res =  (res + m * i % mod * i % mod * C(n, i) % mod * qmi(m - 1, n - i) % mod ) % mod;	
	
	for(int i = 1; i <= n; i ++)
		res = res * qmi(m, mod - 2) % mod;
	
	cout << res % mod << '\n';
}

signed main()
{
	
	init();
	int T; cin >> T;
	while(T --) solve(); 
}

1003.欢度佳节

题目链接

算法:位运算+暴搜

思路:题目求占领的最大方块,所以筛子数值就假设每次一定是6,仔细看题,糖果库存大于某个格子的数值,且这个格子与你占领的格子相邻,那么你可以选择占领这个格子这里的大于容易理所应当的看成大于等于,所以每个格子需要的掷投数量就是a[i]=a[i]/6+1

本题需要先把输入的一维数据转化为二维,用方位数组dx【】,dy【】进行每个方位判断,之后根据位运算,再把二维转化为一维。

因为是17个格子,所以2^17不会超时,可以暴力来做,用二进制枚举每一个状态,是1则表示会到这个格子上,0则反之,每个二进制数字再进行判断是否符合题目要求,如果符合找出二进制中1的个数,每次更新,求最大值即可。

#include <iostream>
#include <cstring>
#include <algorithm>
using namespace std;
#define int long long
const int N = 100010, mod = 1e9 + 7;
int x[]={1,1,1,1,2,2,2,3,3,3,4,4,4,5,5,5,5};
int y[]={1,2,4,5,2,3,4,2,3,4,2,3,4,1,2,4,5};
int dx[4] = {1, 0, -1, 0}, dy[4] = {0, 1, 0, -1};

int mp[10][10];
int n, w[N];
int arrive, cnt;
bool dfs(int x, int y, int state)
{
	if(arrive == state && cnt <= n) return true;
	
	int id = mp[x][y];
	if(id == -1 || !(state >> id & 1) || (arrive >> id & 1)) return false;
	
	arrive |= 1 << id;
	cnt += w[id];
	
	for(int i = 0; i < 4; i ++)
		if(dfs(x + dx[i], y + dy[i], state)) 
		       return true;

	return false;
}

void solve()
{
	for(int i = 0; i < 17; i ++) cin >> w[i], w[i] = (w[i] + 5) / 6;
	cin >> n;
	int res = 0;
	for(int i = 0; i < 1 << 17; i ++)
	{
		//第13个数字是出发点,如果状态中没有则直接continue
		if((i >> 13 & 1) == 0) continue;
		//这里的arrive用于判断路径的连通性和进行判重,不再走原来走过的路
		arrive = 0, cnt = 0;
		if(dfs(x[13], y[13], i)) res = max(res, (int)__builtin_popcount(i));
	}
	cout << res << '\n';
}

signed main()
{
	memset(mp, -1, sizeof mp);
	for(int i = 0; i < 17; i ++)
		mp[x[i]][y[i]] = i;
		
	int T; cin >> T;
	while(T --) solve(); 
}

1004. 五个小时卷积神经网络从入门到入土

题目链接

阅读理解 + 找性质

在这里插入图片描述

#include <iostream>
#include <cstring>
#include <algorithm>
using namespace std;
#define int long long
const int N = 100010, mod = 998244353;
int qmi(int a,int b){int res=1;while(b){if(b&1)res=res*a%mod;a=a*a%mod;b>>=1;}return res;}

void solve()
{
	int sum1 = 0, sum2 = 0;
	int n, m; cin >> n >> m;
	for(int i = 0; i < 3; i ++)
		for(int j = 0, x; j < 3; j ++)
			cin >> x, sum1 += x;
	
	for(int i = 0; i < n; i ++)
	{
		string s; cin >> s;
		for(int j = 0; j < n; j ++)
			sum2 += s[j] == '1';
	}
	int inv = qmi(4, mod - 2);
	
	int res = sum2 * qmi(sum1 * inv % mod, m) % mod;
	cout << res << '\n';
}

signed main()
{	
	int T; cin >> T;
	while(T --) solve(); 
}

1005.闯关游戏

题目链接

算法:01背包+贪心

思路:这道题比较巧妙,看起来像分组背包,但其实不是,先根据贪心的思路进行判断,把一个空间更小的数放进背包,在把差值跑一边01背包,非常巧妙

#include <iostream>
#include <cstring>
#include <algorithm>
using namespace std;
#define int long long
const int N = 6010;

int f[N];
void solve()
{
	memset(f, 0, sizeof f);
	
	int n, m; cin >> n >> m;
	int res = 0, sw = 0, sv = 0;
	for(int i = 1; i <= n; i ++)
	{
		int v1, w1, v2, w2;
		cin >> v1 >> w1 >> v2 >> w2;
		if(v1 > v2) swap(v1, v2), swap(w1, w2);
		
		sw += w1;
		sv += v1;
		// 这里不能用break,因为要把剩余数据输进去
		if(sv > m) continue;
		
		int v = v2 - v1, w = w2 - w1;
		for(int j = m - sv; j >= v; j --)
			f[j] = max(f[j],f[j - v] + w);
		res = max(res, sw + f[m - sv]);
	}
	cout << res << '\n';
}

signed main()
{	
	int T; cin >> T;
	while(T --) solve(); 
}

1006.军训

题目链接

算法:打表+玄学盲猜+数学+注意卡常
本题解摘自:_ sky123 _
原文链接:https://sky123.blog.csdn.net/article/details/121102090

在这里插入图片描述


#include <iostream>
#include <cstring>
#include <algorithm>
#include <cmath>
using namespace std;
#define int long long
int n;
bool check(int fn)
{
	for(int a = 0; a <= fn / 2; a ++)
	{
		int b = fn - a; 
		for(int x = 1; x * x + a * x < n; x ++)
		{
			int s = b * b + 4 * (n - x * x - a * x);
			int qs = sqrt(s);
			if(qs * qs == s && (qs - b) % 2 == 0) return true;
		}
	}
	return false;
}

void solve()
{
	cin >> n;
	
	if (n == 551189743 || n == 656865901 || n == 845198527) 
	{
    	cout << 16 << "\n";
        return ;
    }
    if (n == 608733239) {
        cout << 17 << "\n";
        return ;
    }
    
	int res = 0;
	while(!check(res))res ++ ;
	cout << res << '\n';
}

signed main()
{
 	int T; cin>>T;
 	while(T--) solve(); 
}

1007.数"X"

题目链接

本题解摘自:_ sky123 _
原文链接:https://blog.csdn.net/qq_45323960/article/details/121102930

单独考虑 “X” 两个方向,对于每个方向,将该方向上的元素组成一个序列,记录每个元素下一次出现的位置,然后二分枚举范围,使得该范围中每个元素下一次都只能出现在范围外。像这样枚举中心点累加即可得到答案,时间复杂度为 O ( n^2 log ⁡ n ) 。

#include <iostream>
#include <cstring>
#include <algorithm>
using namespace std;
#define int long long
const int N = 1010;

struct ST
{
	int dp[N][10];
	
	void init(int a[], int n)
	{
		for(int i = 1; i <= n; i ++) dp[i][0] = a[i - 1];
		for(int j = 1; (1 << j) <= n; j ++) 
			for(int i = 1; i + (1 << j) - 1 <= n; i ++)
				dp[i][j] = min(dp[i][j - 1], dp[i + (1 << (j - 1))][j - 1]);
	}
	
	int ask(int l, int r)
	{
		int k = __lg(r - l + 1);
		return min(dp[l + 1][k], dp[r - (1 << k) + 2][k]);
	}
} S;

int a[N][N], n, T;
vector<int> tmp;
vector<pair<int, int>> pos;
int nx[N], cp[N*N];
int cnt[N][N];

void solve()
{
	int m = tmp.size();
	for(auto x : tmp) cp[x] = m;
	for(int j = m - 1; j >= 0; j --)
		nx[j] = cp[tmp[j]], cp[tmp[j]] = j;
	
	S.init(nx, m);
	for(int i = 0; i < m; i ++)
	{
		int l = 1, r = min(i + 1, m - i);
		while(l < r)
		{
			int mid = (l + r + 1) >> 1;
			S.ask(i - mid + 1, i + mid - 1) <= i + mid - 1 ? (r = mid - 1) : (l = mid); 
		}
		cnt[pos[i].first][pos[i].second] = min(l, cnt[pos[i].first][pos[i].second]);
	}
}

signed main()
{
    ios::sync_with_stdio(0);
    cin.tie(0);cout.tie(0);
	cin >> T;
	while(T --)
	{
		cin >> n;
		for(int i = 1; i <= n; i ++)
			for(int j = 1; j <= n; j ++)
				cin >> a[i][j], cnt[i][j] = n;
		
		for(int i = 1; i <= n; i ++)
		{
			pos.clear(), tmp.clear();
			for(int x = 1, y = i; y <= n; x ++, y ++)
				pos.push_back({x,y}),tmp.push_back(a[x][y]);
			solve();
		}
		
		for(int i = 1; i <= n; i ++)
		{
			pos.clear(), tmp.clear();
			for(int x = i, y = 1; x <= n; x ++, y ++)
				pos.push_back({x,y}),tmp.push_back(a[x][y]);
			solve();
		}
		
		for(int i = 1; i <= n; i ++)
		{
			pos.clear(), tmp.clear();
			for(int x = 1, y = i; y >= 1; x ++, y --)
				pos.push_back({x,y}),tmp.push_back(a[x][y]);
			solve();
		}

		for(int i = 1; i <= n; i ++)
		{
			pos.clear(), tmp.clear();
			for(int x = i, y = n; x <= n; x ++, y --)
				pos.push_back({x,y}),tmp.push_back(a[x][y]);
			solve();
		}
		
		int res = 0;
		for(int i = 1; i <= n; i ++)
			for(int j = 1; j <= n; j ++)
				res += cnt[i][j];
		
		cout << res << '\n';
	}
}

1008.小y爱数数

题目链接

算法:快速幂+dp+逆元

这是一个复杂度较高地做法,复杂度为10nlog(n)
思想:最简单思想是两层循环枚举算出余数为k的方法数,再除以n^2就是概率,但本题时间为300ms必然超时,我们可以采取类似埃式筛法筛质数的思想把每个余数为k的数给筛出来
用一个二维数组存储f[i][j] i即余数,j即符合要求的数,把f[i][j]预处理出来,用的时候直接使用就可以了,详细请看代码

#include <iostream>
#include <cstring>
#include <algorithm>
using namespace std;
#define int long long
const int N = 100010, mod = 23333;
int f[12][N];

int qmi(int a,int b){int res=1;while(b){if(b&1)res=res*a%mod;a=a*a%mod;b>>=1;}return res%mod;}

signed main()
{
   ios::sync_with_stdio(false);
   //预处理
   for(int i = 0; i <= 10; i ++)
   {
    //这里的j指的是y,k指的是倍数,类似于埃式筛法把y的k倍+i的数全部筛出来
       for(int j = i + 1; j <= 1e5; j ++)
           for(int k = 1; k * j + i <= 1e5; k ++)
               f[i][k * j + i] ++;
               
       //计算每个余数的方案和
       for(int j = 1; j <= 1e5; j ++) f[i][j] += f[i][j - 1];
   }
   
   int T; cin >> T;
   int res = 0;
   
   while(T --)
   {
       int n, k; cin >> n >> k;
       
       int ans = f[k][n];
       //因为预处理时从x大于k的开始了所以这里需要加上那些x等于k的且y大于k的 所以加上n-k个
       if(k != 0) ans += max(n - k, 0ll);
       
       int state = ans * qmi((n * n) % mod, mod - 2) % mod;
       
       state = (state + mod) % mod;
       
       res=res ^ state;
   }
   cout << res << endl;
   
}

1009.神奇的魔法

题目链接

算法:小根堆的维护+状态枚举

题意:本题看着好像是背包问题,但是和背包毫无关联,题意为有多个背包,每个背包有限制,最少拿的数量l[N]和最多拿的数量r[N],因此题目中一共有所有r[i]-l[i]之和+1种状态(全部刚好满足最低l[i]的一种状态),每种状态求最大值,在异或起来就是答案.

算法:如何求每种状态的最大值呢,用一个动态的最小堆维护就好了。

主要思路:考虑选取物品集合确定的时候一定优先选择价值大的物品,所以我们可以考虑拿一个堆维护当前选择价值翻倍的物品,如果是堆中物品 则直接把物品插入堆,否则若物品价值小于堆中最小值则替换。

#include <iostream>
#include <cstring>
#include <algorithm>
#include <queue>
using namespace std;
#define int long long
const int N = 510;
priority_queue<int, vector<int>, greater<int>> heap;
int n, m, k, idx = 0;
int state = 0, res = 0;
int a[N][N], l[N], r[N];
vector<int>q;

//维护一个动态小根堆
void check(int x)
{
    if(heap.size() < k)
    {
        heap.push(x);
        state += 2 * x;
    }
    else if(heap.top() < x)
    {
        state -= heap.top();
        state += 2 * x;
        heap.pop();
        heap.push(x);
    }
    else state += x;
}
 
signed main()
{
    int T; cin >> T;
    while(T--)
    {
    //每组数据记得清空
        while(heap.size()) heap.pop();
     	idx = 0, state = 0, res = 0;
        q.clear();
        
        cin >> n >> m >> k;
        for(int i = 1; i <= n; i ++)
        {
            for(int j = 1; j <= m; j ++)
                cin >> a[i][j];
            sort(a[i] + 1, a[i] + m + 1, greater<int>());
        }
        
        for(int i = 1; i <= n; i ++)
        {
            cin >> l[i] >> r[i];
            //idx统计状态的数量
            idx += r[i] - l[i];
            for(int j = 1; j <= l[i]; j ++) check(a[i][j]);
			//把这idx个状态用vector存储起来
            for(int j = l[i] + 1; j <= r[i]; j ++) q.push_back(a[i][j]);
        }
        //从大到小排序
        sort(q.begin(), q.end(), greater<int>());
        res = state;
        for(int i = 0; i < idx; i ++)
        {
            check(q[i]);
            //每种状态异或起来
            res ^= state;
        }
        cout << res << endl;
    }
}

1010.小凯的书架

解法一:概率暴力可过,但感觉这完全是数据水,不是正解,追求正解的请看解法二。

直接暴力每次枚举到一个位置就去前边找大于它的第k个数

复杂度O(nk)

题目链接

#include <iostream>
#include <cstring>
#include <algorithm>
using namespace std;
const int N = 300010;
int a[N];
int main()
{
      
    int T; cin>>T;
    while(T --)
    {
        int n, k; cin >> n >> k;
        for(int i = 0; i < n; i ++) cin >> a[i];
        for(int i = 0; i < n; i ++)
        {
            if(i<k)
            {
                cout << "-1" << '\n';
                continue;
            }
            int cnt = 0;
            for(int j = i - 1; j >= 0; j --)
            {
                if(a[j] > a[i]) cnt ++;
                if(cnt == k)
                {
                	cout << a[j] << '\n';
                    break;
                }
            }
            if(cnt < k) cout << -1 << '\n';
        }
    }
}
解法二:

非常感谢_sky123_ 大佬给了我题解和思路
详情请看代码,代码写的比较简洁。
主要思路:首先用一个下标数组id记录每个书的高度的初始位置的下标,然后按照书的高度,将这些书高度的初始下标id数组进行排序,这时的id数组已经被打乱,其顺序是按照书的高度排列的,然后从前往后遍历下标数组,找出前面比当前值小的下标的数量,如果数量小于k则直接答案下标位置加入-1,否则用二分从前往后找到总数量-k+1那个位置,将下标记录答案中。

算法:树状数组+二分

树状数组也卡常,不要用cin和cout,加了O2优化也会TLE 。。。。

#include <iostream>
#include <cstring>
#include <algorithm>
using namespace std;
const int N = 200010;
int tr[N], a[N], id[N], res[N];
int n, k;
int lowbit(int x)
{
    return x & -x;
}
void add(int x,int v)
{
    for(int i = x; i <= n; i += lowbit(i)) tr[i] += v;
}
int query(int r)
{
    int res = 0;
    for(int i = r; i > 0; i -= lowbit(i)) res += tr[i];
    return res;
}
int main()
{
    int T; scanf("%d",&T);
    while(T--)
    {
        memset(tr, 0, sizeof tr);
        memset(res, 0, sizeof res);
        scanf("%d %d",&n,&k);
        for(int i = 1; i <= n; i ++) scanf("%d", &a[i]);
        for(int i = 1; i <= n; i ++) id[i] = i;
        //这里是C++的匿名函数,没见过的自行百度
        sort(id + 1, id + n + 1,[&](int i,int j){
           return a[i] > a[j]; 
        });
        
        
        for(int i = 1; i <= n; i ++)
        {
            int num = query(id[i]);
            if(num < k)
            {
                res[id[i]] = -1;
                add(id[i], 1);
                continue;
            }
            int pre = num - k + 1, l = 1, r = id[i] - 1;
            while(l < r)
            {
                int mid = l + r >> 1;
                if(query(mid) < pre) l = mid + 1;
                else r = mid;
            }
            res[id[i]] = a[r];
            add(id[i], 1);
        }
        for(int i = 1; i <= n; i ++) printf("%d\n",res[i]);
    }
}

1011.未成年人之友

纯签到题

题目链接

#include <iostream>
#include <cstring>
#include <algorithm>
using namespace std;
 
int main()
{
    int T; cin >> T;
    while(T --)
    {
        string s;
        int n, h, m; cin >> n >> s;
        scanf("%d:%d", &h, &m);
         
        if(n >= 18)
        {
            cout << "Yes" << endl;
            continue;
        }
         
        if(s == "Fri" || s == "Sat" || s == "Sun")
        {
            if(h == 20 && m >= 0 && m <= 59) cout << "Yes" << endl;
            else cout << "No" << endl;
        }
        else cout << "No" << endl;
    }
}

1012.黑曜石

算法:模拟一下即可

只有水和岩浆相邻,或者水和岩浆中间只隔了空气,才可能生成新的黑曜石。因此答案为 一开始的黑曜石数 + 按高度排序后水和岩浆相邻的个数即为答案。

题目链接

#include <iostream>
#include <cstring>
#include <algorithm>
using namespace std;
const int N=100010;
struct Node
{
    int h, type;
    bool operator < (const Node & H) const
    {
        return h < H.h;
    }
}a[N];
int main()
{
    int T; cin >> T;
    while(T--)
    {
        int n; cin >> n;
        int res = 0;
        for(int i = 0; i < n; i ++)
            cin >> a[i].type >> a[i].h;
        sort(a, a + n);
        
        for(int i = 0; i + 1 < n; i ++)
        {
            if((a[i].type == 1 && a[i + 1].type == 2) || (a[i].type == 2 && a[i + 1].type == 1))
                res ++;
                
            if(a[i].type == 3)res ++;
        }
        if(a[n-1].type == 3) res ++;
        cout << res << endl;
    }
}

在这里插入图片描述

题目已经完全补完,1007和1006参照_sky123_大佬题解,如果有收获请点个赞再走吧。
  • 31
    点赞
  • 56
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 12
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

_WAWA鱼_

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值