洛谷1月月赛2 & CSGRound 3

洛谷1月月赛2 & CSGRound 3蒟蒻陈大爷

A-压岁钱 原题网址

题目描述

一共存在有 m 个事件,且事件分为以下的 3 种类型。

1.小 Z 得到了 a 元压岁钱。
2.小 Z 花掉了 a 元压岁钱用于买皮肤。
3.小 Z 把自己的 a 元钱封印了起来,只有当第 b 个事件发生前 1 秒才会解除封印,并保证每次小 Z 现有的钱大于等于封印的钱。

当小 Z 的钱在某个事件不够花时,小 Z 会感到不开心,同时钱不够花时小 Z 便不会花钱。
请告诉小 Z ,他的钱在几个事件中会不够花。

简要思路

签到题, 此题%70的数据其实根本就没有第三种类型, 所以:直接按题意模拟即可。对于封印, 可以开一个map。 下表指的是解印的那天, 对应的数值是解印的钱数;对天数依次遍历, 每次先把当前解开封印的钱数加到持有钱里即可, 记住随时判钱是否够, 及时统计;

代码

#include<cstdio>
#include<iostream>
#include<cmath>
#include<cstring>
#include<algorithm>
#include<set>
#include<map>
#include<vector>
#include<stack>
#include<queue>
#include<deque>
#include<list>
#include<cstdlib>
#include<cctype>
#include<ctime>
using namespace std;

typedef long long ll;
ll m;
ll money;
ll ans;
map<ll, ll> mp;
int main()
{
	cin >> m;
	 for(ll i = 1; i <= m; i++)
	 {
  		int t;
 		 cin >> t;
 	 	money += mp[i];
 		 if(t == 1)
	 	 {
  			 int a;
 			 cin >> a;
  			 money += a;
  		}
 	 	else if(t == 2)
  		{
   			int a;
   			cin >> a;
   			if(money < a)
   			{
    				ans++;
   			}
   			else
   			{
    				money -= a;
   			}
  		}
  		else
  		{
   		int a, b;
   		cin >> a >> b;
   		money -= a;
   		mp[b] += a;
  		}
 	}
 	cout << ans << endl;
	return 0;
}
哦, 对了!!!!
别忘了开long long!!!

B-斗牛 原题网址

题目描述

给定 n 张牌,牌的大小为 1∼10。你需要挑选其中的 n−2 张牌加起来是 10 的倍数,另外两张牌和的个位数即为你所获得的点数。特别地,如果这两张牌的和是 10 的倍数,则点数为 10,也叫做牛哄哄。如果任意 n−2 张牌不能构成 10 的倍数,则点数为 0,也叫做牛不拢。
由于小 Z 想要更开心的玩耍,所以需要你来完成这个程序来帮助小 Z 在 1 秒内知道点数。

题目思路+代码

这道题当时在考场上一想就出来了
这道题打算用分层的思路来讲:
因为这道题子任务很重;ok, 让我们来看一下

subtask 1: n n = 5 ; \forall n | n = 5;

当时想的是枚举所有三张牌的组合。。。。25复杂度;
进行判断即可;

for(int i = 1; i <= 5; i++)
{	
	for(int j = i+1; j <= 5; j++)
	{
		for(int k = j+1; k <= 5; k++)
		{
			....
		}
	}
}

subtask2: n n 5 × 1 0 3 \forall n | n \leq 5 \times {10^3}

显然, 选n-2个数肯定超时, 因为你不可能进行n-2次循环吧。。。。
但我们可以反向思维 逃)如果我们选出两个数来了, 那么。。。剩下的n-2个数不也都确定了吗!!!
那么怎么判断呢: 简单, 记录所有数的和, 为sum;
用sum 减去那两个数就是n-2个数的和了呗, 再进行判断

 for(int i = 1; i <= n; i++)
  {
   for(int j = i+1; j <= n; j++)
   {
    int total = a[i] + a[j];
    if((sum - total) % 10 == 0)
    {
     if(total % 10 == 0)
     {
      cout << 10 << endl;
     }
     else
     {
      cout << total % 10 << endl;
     }
     return 0;
    }
   }
  }
时间复杂度O(n*n)

subtask 3: n 5 n 1 0 6 \forall n | 5 \leq n \leq 10^6

由此可见,O(n2)的时间复杂度已经过不了了;
这次, 我们抓住特点:卡牌上的数字只有1~10!!!
所以所有的两张不同类型搭配,也就是只有那么几种呗;
考虑枚举卡牌上的内容而不是卡牌了:考虑有重复的情况;开一个数组s记录每张牌出现的次数, 对于重复先判断:当且仅当s[i] >= 2时才有重复可能;
时间复杂度O(n);

 for(int i = 1; i <= n; i++)
 {
  cin >> a[i];
  s[a[i]]++;
  sum += a[i];
 }
 if(sum % 10 == 0)
 {
  cout << 10 << endl;
  return 0;
 }
 for(int i = 1; i <= 10; i++)
 {
  if(s[i] >= 2 && (sum - i * 2) % 10 == 0)
  {
   cout << i*2 % 10 << endl;
   return 0;
  }
 }
 for(int i = 1; i < 10; i++)
 {
  for(int j = i+1; j <= 10; j++)
  {
   if(s[i] > 0 && s[j] > 0 && (sum - i - j) % 10 == 0)
   {
    cout << (i+j) % 10 << endl;
    return 0;
   }
  }
 }
 cout << 0 << endl; 

C-游戏 原题网址

题目描述

有一个牌堆,一共有 n 张牌,第 i 张牌上有一个数 ai​,其中第一张牌是堆顶。
小 Z 先取牌,他可以从堆顶开始取连续若干张牌(可以取 000 张),取完的牌拿在手上,也就是不在牌堆里了。
然后小 Y 取牌,同样,她也可以从堆顶开始取连续若干张牌(可以取 若干 张)。
如果一个人手上的牌的数字和大于 X,那么他的分数就是 0,否则分数就是取得牌上的数字和。
分数高的人获胜,如果一样高,则无人获胜。
小 Z 为了获胜,使用了透视挂,即他知道牌堆里每张牌上写的数。
现在问你对于满足 1 X K 1 \leq X \leq K 的所有整数 X,哪些可以使得小 Z 有必胜策略,即小 Z 取完后,不管小 Y 怎么取都一定会输。

简要思路

第一: 游戏只有一个回合;第二:上面加粗字体
直接排除不取的情况, 因为不取的话分数为0, 就一定赢不了小Y;其实我们要找的X都是一个临界点, 小Y多拿一张就爆了, 少拿一张就和法那样的临界点;所以根据这两种状况进行判断即可;
那是如何转移, 现在想怎么表示状态;看到连续取牌, 想到区间求和:设 S i , j S_{i, j} 表示从i到j的区间和;则由于小z是从堆顶开始取得,设小Z取了x张牌, 所以小Z的分数为 S 1 , x S_{1, x} ;小Y接着去取, 设小Y取到y, 则小Y的分数为 S x + 1 , y S_{x+1, y}
那么要求就是
1 S x + 1 , y < S 1 , x X K 1 \leq S_{x+1, y} \lt S_{1, x} \leq X \leq K
又因为 S x + 1 , y = S 1 , y S 1 , x S_{x+1, y} = S_{1, y} - S_{1, x}
所以
1 S 1 , y S 1 , x < S 1 , x X K 1 \leq S_{1, y} - S_{1, x} \lt S_{1, x} \leq X \leq K
后面再推就是用二分了, 但我可以用枚举:核心思想:
每一次枚举的状态从上一次的状态开始就可以了
我也不知道为啥,蒟蒻求助~~~

代码

long long a[N],now1,now2;
int now,ans[N],n,k,j=1,z=2;
bool flag;
读入略。。。
if(k==1){printf("0");return 0;}
    for(int i=1;i<=k;++i){
        for(;j<=n;++j){
      
            now1=now2=flag=0;
            if(a[j]>i)break;
             
            else now1=a[j];
            for(;z<=n;++z){
                now2=a[z]-a[j];
                if(now2>i&&now1>0)break;
               
                if(now2>=now1){flag=1;break;}
           
            }
            if(!flag){ans[++now]=i;break;}
        }
    }
    printf("%d\n",now);
    for(int i=1;i<=now;++i)printf("%d ",ans[i]); 

D-出游 原题网址

题目描述

学校组织了一次暑期出游活动,报名将在第 T 天截止。
一共有 n 位同学,第 iii 位同学有 a i a_i ​ 位朋友。朋友关系是单向的,换句话说,小 Z 有一个朋友是小 Y,并不意味着小 Y 一定也有一个朋友是小 Z。另外,自己也可能是自己的朋友。
第 0 天时,每位同学会决定自己是否参加活动。第 i 位同学有 pi​ 的概率决定参加,1-pi 的概率决定不参加。
接下来的 T 天里,每位同学会重新决定自己是否参加活动。第 i 位同学这一天决定参加活动,当且仅当至少有一个他的朋友在前一天决定参加,否则便不参加。你需要求出参加活动的同学人数期望,答案对 998244353取模。

简要思路

先抛掉概率不看(因为我不会)就是问有一些朋友然后加上上面加粗字体的条件, 我们就可以转化成::给定n个点的有向图, 一开始选择一些点, 每次走一步, 问T步后可能在哪;
显然,可以用bitset加矩阵乘法优化
bitset 是啥? 看这儿!!!bitset简介
矩阵不用我说了吧。。。。
下面来构造一个矩阵;
矩阵 j a i 1 0 b 0 1 \begin{array}{c|l} \text{矩阵} & j & a\\ \hline i & 1 & 0\\ b & 0 & 1 \end{array}
说实话, 过于简单了。。
这个矩阵就是一个01矩阵, 下面讲解意义;
1.横纵坐标都是人, 共2 * n 个;
2.中间的数设为 a i j a_{ij} 表示如果选i, 那么j最终就会被走到;
一次转移就是
if (a.a[i][j]) c.a[i]|=b.a[j];
这就是矩阵乘法的设立:
用语言翻译就是:如果选 i 能走到 j, 那么选 j 能走到的点选 i 也能走到;
是不是清楚了很多呢;
用图来表示::

转移
J
1
2
3
I

所以转移就是建边啦。。。
矩阵乘上自己一次相当于过了一天, 那么T天后就是乘了T次自己呗;
用矩阵快速幂就能解决, 得到最终矩阵final;
下面来想如何算答案:
首先知道一件事, 答案 就是每个人去的概率之和
然后, 在模意义下计算;;
遍历每一个人, 算出他去的概率, 设为qi,那么可以算她不去的概率再用1减去;
对于他自己, 遍历矩阵中和他为朋友的人(以他为纵坐标为一的人), 算出他们不去的概率乘起来就是他不去的概率啦;
1 q i = f i n a l . a [ j ] [ i ] = 1 ( 1 p j ) 1 - q_i = \prod_{final.a[j][i] = 1} (1 - p_j)
pj是他朋友的概率, 最后答案等于
a n s = i = 1 n q [ i ] ans = \sum_{i=1} ^ n q[i]
然后别忘了取模
a n s = ( a n s m o d + m o d ) m o d ans = (ans取模mod + mod) 取模mod
不要问我为什么因为我也不知道, 大佬求助~~;

代码

#include <cstdio>
#include <bitset>
#include <cstring>
#include <algorithm>
using namespace std;

const int N=510,MOD=998244353;
int n,m,ans,p[N];

struct matrix
{
    bitset<N> a[N];
    
    friend matrix operator *(matrix &a,matrix &b)
    {
        matrix c;
        for (int i=1;i<=n;i++)
            for (int j=1;j<=n;j++)
                if (a.a[i][j]) c.a[i]|=b.a[j];
        return c;
    }
}a,f;

int main()
{
    scanf("%d%d",&n,&m);
    for (int i=1,x,y;i<=n;i++)
    {
        f.a[i][i]=1;
        scanf("%d%d",&p[i],&x);
        while (x--)
        {
            scanf("%d",&y);
            a.a[y][i]=1;
        }
    }
    for (;m;m>>=1,a=a*a)
        if (m&1) f=f*a;
    for (int i=1;i<=n;i++)
    {
        int x=1;
        for (int j=1;j<=n;j++)
            if (f.a[j][i])
                x=1LL*x*(1-p[j])%MOD;
        ans=(ans+1-x)%MOD;
    }
    printf("%d",(ans%MOD+MOD)%MOD);
    return 0;
}

小结

不知道写啥, 主要是记录一下思路吧, 写的不好谅解一下,对了,里面的问题请大佬一看;
1.最后为何要(ans % mod + mod)%mod
2. 状态为何不用重新枚举(C题)
谢谢!

发布了3 篇原创文章 · 获赞 3 · 访问量 214

猜你喜欢

转载自blog.csdn.net/chenboshuai/article/details/104117887
今日推荐