牛客竞赛第三届越杯程序设计团体赛题解
比赛链接:第三届超越杯程序设计团体赛重现赛_ACM/NOI/CSP/CCPC/ICPC算法编程高难度练习赛_牛客竞赛OJhttps://www.nowcoder.com/acm/contest/31374
A.红红找蓝蓝.
题目描述
信大校园很大很大很大,红红和蓝蓝最近玩上了捉迷藏。具体来说,信大可以抽象成一个 n×n的方格矩阵,`x` 表示不能行走的楼房(可能是教学楼、实验楼、食堂等地方),`.` 表示可以行走的道路。红红的初始位置在 A 点,而蓝蓝的初始位置在 B 点。红红想从 A 点出发去寻找在 B 点的蓝蓝。红红和蓝蓝都是训练有素的军人,所以他们不会斜着走,只会沿着前、后、左、右四个方向行走。学过广度优先搜索的你,一定很容易求得 A 到 B 的最少步数。红红突发奇想,想知道从 A 到 B 的所有路径之中,请问,最少需要拐弯几次。
开始时与结束时的方向任意,不做要求。
输入描述:
共T(1≤T≤10)组数据。
每组第一行一个整数:n(1≤n≤100),下面 nnn 行,每行n个字符,只出现字符:`.`,`x`,`A`,`B` (保证 `A,B` 出现)
表示上面所说的矩阵道路。每行的字符中除了题中描述的字符和末尾的换行符之外,没有多余字符。
输出描述:
一个整数:红红所需的最少转弯次数。如果不能到达,输出 -1 。
示例1
输入:
1
3
.xA
...
Bx.
输出:
2
_____________________________________________________________________________
思路:
本题用bfs,因为要求最少转弯,搜索的时候应该按照转弯次数逐层搜索。注意本题数据会卡掉dfs。
代码:
-
-
using namespace std;
-
const int N=110;
-
char s[N][N];
-
int n,sx,sy,dir[4][2]={1,0,-1,0,0,1,0,-1},st[N][N]; /*st为选择数组*/
-
struct node{
-
int x,y; //坐标
-
int step; //转弯数
-
int state; //状态,分别用1,2,3,4对应左右上下
-
};
-
bool isopp(int a,int b) //判断两个方向是不是反向的
-
{
-
if(a>b) swap(a,b);
-
if(a==1&&b==2) return true;
-
if(a==3&&b==4) return true;
-
return false;
-
}
-
void bfs()
-
{
-
memset(st,0,sizeof(st));
-
queue<node>q;
-
node p;
-
p.x=sx,p.y=sy,p.state=0,p.step=0;
-
q.push(p);
-
st[sx][sy]=1;
-
while(!q.empty()){
-
node t=q.front();
-
q.pop();
-
if(s[t.x][t.y]=='B'){ //找到了
-
cout<<t.step<<endl;
-
return;
-
}
-
for(int i=0;i<4;i ){
-
int nx=t.x dir[i][0],ny=t.y dir[i][1];
-
if(nx<0||nx>=n||ny<0||ny>=n||s[nx][ny]=='x') continue; //出界了,或者为障碍
-
if(t.state==0){ //开始状态
-
while(1){ /*把一个方向都搜到底*/
-
if(nx<0||nx>=n||ny<0||ny>=n||s[nx][ny]=='x') break;
-
node nw;
-
nw.x=nx,nw.y=ny,nw.step=0,nw.state=i 1;
-
if(!st[nx][ny]) q.push(nw),st[nx][ny]=1;
-
nx =dir[i][0],ny =dir[i][1];
-
}
-
}
-
else{
-
while(1){ //垂直方向入队
-
if(nx<0||nx>=n||ny<0||ny>=n||s[nx][ny]=='x'||isopp(i 1,t.state)) break; //如果在同一直线的方向不需要搜
-
node nw;
-
nw.x=nx,nw.y=ny,nw.step=t.step 1,nw.state=i 1;
-
if(!st[nx][ny]) q.push(nw),st[nx][ny]=1;
-
nx =dir[i][0],ny =dir[i][1];
-
}
-
}
-
}
-
}
-
cout<<-1<<endl; //没找到
-
}
-
int main()
-
{
-
int t;
-
cin>>t;
-
while(t--){
-
cin>>n;
-
for(int i=0;i<n;i )
-
for(int j=0;j<n;j ){
-
cin>>s[i][j];
-
if(s[i][j]=='A') sx=i,sy=j;
-
}
-
bfs();
-
}
-
return 0;
-
}
B. 红红蓝蓝在争论.
题目描述
红红和蓝蓝都是信大的学生,他们最近对二进制 0 和 1 字符串特别感兴趣。红红总喜欢把 1 变成 0 ,而蓝蓝总喜欢把 0 变成 1 。他们在争吵之中,定义一个 01 字符串的最大有效值为该字符串中最长连续的 0 的个数与最长连续的 1 的个数的较大值。
现在给定了一个长度为 n 的 01 串 s ,红红和蓝蓝合计可以执行最多 m 次操作。
对于每次操作,蓝蓝把某一位的 0 改成 1 ,或者红红把 1 改成 0 。
求字符串 S 在最多 m 次操作的最大有效值。
输入描述:
共 T(1≤T≤20)组数据。
每组数据包括 2个整数n(1≤n≤100000)和m(1≤m≤1000)。n 为字符串长度,m 为操作次数。
以下一行,为长度为 n 的字符串 S 。
输出描述:
一个整数,表示题面描述的最大有效值。
示例1
输入:
2
5 1
00101
2 1
01
输出:
4
2
_____________________________________________________________________________
思路:
本来想用背包模型动态规划dp[n][m]来刻画,滚动数组空间优化到n*2赌一赌,结果还是超时了qwq.
这道题方法很多,很多大牛用前缀和 二分,本菜鸡介绍一下用双指针的做法。
以求最长连续1为例,首先可以用一个数组记录一下0出现的位置(可以被修改的),然后考虑某个位置i,判断修改次数<=m,修改本位置,更新最大值,i指针后移。否则考虑j指针的位置不修改,i指针位置修改,更新最大值,两个指针后移。
上述方法分别对1和0求最长连续序列即可。
代码:
-
-
using namespace std;
-
char s[100010];
-
int main()
-
{
-
int t,n,m;
-
cin>>t;
-
while(t--){
-
int ans=0;
-
vector<int>z,o; //z代表0出现的位置,o代表1出现的位置
-
cin>>n>>m>>s;
-
for(int i=0;i<n;i ){
-
if(s[i]=='0') z.push_back(i);
-
else o.push_back(i);
-
}
-
int j=0;
-
for(int i=0;i<z.size();i ){
-
if(i 1<=m){ //修改位置
-
if(i 1==z.size()) ans=n; //如果是最后一个,直接和尾部连接成最长连续序列。
-
else ans=max(ans,z[i 1]); //从0到下一个断点的前一位
-
continue;
-
}
-
/*考虑i修改,j不修改*/
-
if(i 1==z.size()) ans=max(ans,n-1-z[j ]-1);
-
else ans=max(ans,z[i 1]-z[j ]-1);
-
}
-
j=0;
-
for(int i=0;i<o.size();i ){
-
if(i 1<=m){
-
if(i 1==o.size()) ans=n;
-
else ans=max(ans,o[i 1]);
-
continue;
-
}
-
if(i 1==o.size()) ans=max(ans,n-1-o[j ]-1);
-
else ans=max(ans,o[i 1]-o[j ]-1);
-
}
-
cout<<ans<<endl;
-
}
-
return 0;
-
}
C.合法的IP地址.
题目描述
红红和蓝蓝是一个合格的本科生了。这一天,他们在学习计算机网络中的 IP 地址。他们对 IP 地址的使用场景很感兴趣,但是,由于刚接触 IP 地址,他们无法判断一个给定的字符串,是否为一个合法的 IP 地址。
你能帮帮他们吗?
一个合法的 IP 地址,需要满足以下条件:
(1)该 IP 地址,为 A.B.C.D的形式
(2)A,B,C,D均为十进制数值,即字符范围为 0∼9,不会出现其他字符
(3)0<A≤255
(4)0≤B,C,D≤255
输入描述:
该输入文件提供 T(1≤T≤100)组数据,每组数据为一个长度不超过15的字符串。
输出描述:
若该字符串为一个合法的 IP 地址,则输出 `YES`(不带引号);否则,输出 `NO` 。末尾带一个换行符。
示例1
输入:
3
1.1.1.1
255.255.255.0
256.255.255.0
输出:
YES
YES
NO
_____________________________________________________________________________
思路:
模拟题,不多解释,注意数据有多个'.'连续出现的情况。
代码:
-
-
using namespace std;
-
int main()
-
{
-
int t;
-
cin>>t;
-
while(t--){
-
string s,a;
-
cin>>s;
-
int cnt=0,flag=1;
-
for(int i=0;i<s.size();i ){
-
if(i==0&&s[i]=='.'){
-
flag=0;
-
break;
-
}
-
else if(i==s.size()-1&&s[i]=='.'){
-
flag=0;
-
break;
-
}
-
else if(s[i]=='.'){
-
if(a==""){
-
flag=0;
-
break;
-
}
-
int sum=0;
-
cnt ;
-
if(cnt>3){
-
flag=0;
-
break;
-
}
-
for(int j=0;j<a.size();j ){
-
sum=sum*10 a[j]-'0';
-
if(sum>255){
-
flag=0;
-
break;
-
}
-
}
-
if(flag==0) break;
-
if(cnt==1&&sum==0){
-
flag=0;
-
break;
-
}
-
a="";
-
}
-
else if(!isdigit(s[i])){
-
flag=0;
-
break;
-
}
-
else if(s[i]!='.') a.push_back(s[i]);
-
}
-
if(cnt!=3) flag=0;
-
if(flag){
-
int sum=0;
-
for(int j=0;j<a.size();j ){
-
sum=sum*10 a[j]-'0';
-
if(sum>255){
-
flag=0;
-
break;
-
}
-
}
-
}
-
if(flag) puts("YES");
-
else puts("NO");
-
}
-
return 0;
-
}
D.石头剪刀布.
题目描述
又到了每周扫大环的时间,这周的安排是小红。可到了正要下去扫的时候,小红给班长说她胃疼,扫不了地了。班长只好让小蓝去扫。小蓝不愿意,小红就说咱俩玩剪刀石头布,谁输了谁去。小蓝知道第一次小红会出石头,第二次小红会出剪刀,第三次小红会出布,之后小红会按照:石头、剪刀、布、石头、剪刀、布的顺序一直循环。蓝蓝,想请问你,你需要以怎么样的应对策略来面对小红,才能达到 100% 的胜率呢?
输入描述:
共T(1≤T≤100)组数据。
每组数据为一个整数 n(1≤n≤1000),表示蓝蓝正在与小红进行第n次游戏。
输出描述:
对每组数据,输出一行字符串。
石头输出 `Rock` ,剪刀输出 `Scissors`,布输出 `Paper` 。
末尾只带有一个换行符。
示例1
输入:
1
1
输出:
Paper
_____________________________________________________________________________
思路:
本场签到题。
代码:
-
#include<bits/stdc .h>
-
using namespace std;
-
int main()
-
{
-
int t,n;
-
cin>>t;
-
while(t--){
-
cin>>n;
-
if(n%3==1) puts("Paper");
-
else if(n%3==2) puts("Rock");
-
else puts("Scissors");
-
}
-
return 0;
-
}
E.填充游戏
题目描述
小红和小蓝在玩一个游戏。有一个边长为m的正方形,两人依次在正方形中放下圆。规则为:在正方形中放下的圆不能与正方形的边框有任何交点,也不能与已经放入正方形中的其他圆的边框有任何交点。不能再放入圆的选手失败。红红先行动,蓝蓝后行动。两人的博弈论都学得非常好,都是100分,且小红知道小蓝博弈水平很好,小蓝也知道小红水平很好。请问,在两人都是采用最优操作的条件下,红红和蓝蓝谁能获胜呢?
输入描述:
共T(1≤T≤100)组数据。
每组数据为在一行之中,用空格隔开的两个正整数,第一个正整数为正方形的边长m,第二个正整数为圆的半径n(10≤n≤100,10≤m≤400)。
输出描述:
如果红红获胜,输出 `honghong yyds`,末尾带一个换行符。
如果蓝蓝获胜,输出`lanlan yyds`,末尾带一个换行符。
示例1
输入:
2
2 10
3 1
输出:
lanlan yyds
honghong yyds
_____________________________________________________________________________
思路:
思想头脑风暴,代码简单粗暴。
如果第一个圆都放不下,后手胜。否则先手有必胜策略:第一次放中间,根据图形对称性,先手可以放和后手中心对称的位置。所以最后放不下一定是后手遇到。
代码:
-
-
using namespace std;
-
int main()
-
{
-
int t,n,m;
-
cin>>t;
-
while(t--){
-
cin>>m>>n;
-
if(m<=2*n) puts("lanlan yyds");
-
else puts("honghong yyds");
-
}
-
return 0;
-
}
F.RickLee And 540
题目描述
RickLee 和 540 报名参加了网络知识技能竞赛。他们以为模拟考试和真实考试是一样的,只要记下答案就可以乱杀考试,于是他们决定先做一套模拟题。出乎意料的是两个人的模拟题题目并不相同,这下他们意识到原来每套题目都是随机从题库里抽几道题组成的。这可难不倒优秀的 xd 学生,他们立马想到多找几个人一起参加模拟考试(因为每个人只有一次模拟的机会),记录下每个人的题目及答案,只要人数够多就一定可以还原出题库来。
假设题库有 n 道题,每套题是随机选出其中的 m 道(可能重复), RickLee 和 540 一共找来了 k个人,他们可以还原出题库的概率是多少?
输入描述:
第一行一个整数 T代表询问组数
之后每行为三个正整数 n(1≤n≤1e5),m(1≤m≤n),k(1≤k≤1e9)
数据保证 ∑n≤1e6
输出描述:
T 行每行一个整数,表示还原出的概率,答案对 998244353 取模。(假设答案为 p/q,那么就是 p 乘上 q 的逆元再模 998244353 )。
示例1
输入:
1
2 1 2
输出:
499122177
Hint: 找来的两个人选出的有如下四种情况:11 ,12,21,22。有两种是可行的,概率为 1/2 。2 在模 998244353 下的逆元为 499122177 。
_____________________________________________________________________________
思路:
本题难点在于求出概率表达式,取模数为素数,求逆元可以用费马定理。
先看总的方案个数,注意每套题m可以重复选择,相当于n个不同的球,每一次有n种选法,总共选m*k次。
分母:
合法的情况,目的是要从n种球种选m*k个,且方案有n种球。从i种球选m*k个的方案数为,容斥原理可得:
有n种颜色的球,用集合Si表示方案颜色为i色的方案,以n=3为,如图
蓝色合法区域可以表示为:S1 S2 S3-S1*S2-S1*S3-S2*S3 S1*S2*S3
最后,在计算时,mk比较大,需要用欧拉降幂公式。(mod为素数,指数降为mk%(mod-1))
代码:
-
#include<bits/stdc .h>
-
using namespace std;
-
const int mod=998244353,N=100010;
-
int fact[N],infact[N]; //分别表示n!%mod和n!在%mod下的逆元
-
inline int qp(int a,int b,int m) //快速幂
-
{
-
int res=1;
-
while(b){
-
if(b&1) res=1ll*res*a%m;
-
b>>=1;
-
a=1ll*a*a%m;
-
}
-
return res%m;
-
}
-
inline int C(int a,int b) //计算组合数
-
{
-
if(a<0||b<0||a<b) return 0;
-
return 1ll*fact[a]*infact[b]%mod*infact[a-b]%mod;
-
}
-
inline void init() //预处理出fact和infact,保障后面算组合数复杂度为O(1),避免超时
-
{
-
fact[0]=infact[0]=1;
-
for(int i=1;i<N;i ){
-
fact[i]=1ll*fact[i-1]*i%mod;
-
infact[i]=1ll*infact[i-1]*qp(i,mod-2,mod)%mod;
-
}
-
}
-
int main()
-
{
-
init();
-
int t,n,m,k;
-
cin>>t;
-
while(t--){
-
cin>>n>>m>>k;
-
int ans=0,sig=1,inv_n=qp(n,mod-2,mod); //inv_n表示n的逆元
-
for(int i=n;i>=1;i--){
-
ans=(1ll*sig*C(n,i)*qp(1ll*i*inv_n%mod,1ll*m*k%(mod-1),mod)%mod ans mod)%mod;
-
sig*=-1;
-
}
-
cout<<ans%mod<<endl;
-
}
-
return 0;
-
}
G.Jump.
题目描述
Ruri 正在玩一个游戏。现在 Ruri 正站在一个数轴的 1 号位置,他的目标是走到 n号位置。当 Ruri 走到位置 i 时,会得到 ai 的经验值。在位置i时,下一步可以跳到 [li,Ri] 这些位置(位置 n 就不能跳了)。请问 Ruri 可以获得的最大经验是多少?
输入描述:
第一行为一个正整数 n(1≤n≤2e5)。
第二行有 n 个数表示 a1,a2,⋯ ,an(1≤ai≤1e4)
后面的 n−1 行表示 [Li,Ri](i<Li≤Ri≤n)
输出描述:
一个整数,表示可获得的最大经验值。
示例1
输入:
6
7 9 1 1 3 7
2 3
5 6
4 6
6 6
6 6
输出:
26
Hint:
开始位于 1 号位置,此时经验值为 7 ,下一步能跳到 [2,3]
之后跳到 2 号位置,此时经验值为 7 9=16 ,下一步能跳到 [5,6]
之后跳到 5 号位置,此时经验值为 7 9 3=19,下一步能跳到 [6,6]
最后跳到 6 号位置,此时经验值为 7 9 3 7=26,结束。
显然找不到更优的方案。
_____________________________________________________________________________
思路:
用线段树维护区间最小值,初始化为0.之后查询[i,i]的值t,若不为0,用a[i] t更新区间[li,ri]的所有值(维护当前能跳到的点的最大值)
以样例为例,如图:
i | a[i] | 区间 | 查询值 | 更新后的值,从1-6 | |||||
1 | 7 | 2 3 | 0 | 0 | 7 | 7 | 0 | 0 | 0 |
2 | 9 | 5 6 | 7 | 0 | 7 | 7 | 0 | 16 | 16 |
3 | 1 | 4 6 | 7 | 0 | 7 | 7 | 8 | 16 | 16 |
4 | 1 | 6 6 | 8 | 0 | 7 | 7 | 8 | 16 | 16 |
5 | 3 | 6 6 | 16 | 0 | 7 | 7 | 8 | 16 | 19 |
6 | 7 |
最后的答案即为query(1,n) a[n](最后一步会得到a[n]的值)
Code:
-
-
using namespace std;
-
const int N=2e5 10;
-
int a[N];
-
struct tree{
-
int l,r,s,tag; //s为最小值,tag为更新标记
-
}T[N*4];
-
inline void up(int p)
-
{
-
T[p].s=min(T[2*p].s,T[2*p 1].s);
-
}
-
inline void down(int p) //标记下移
-
{
-
if(T[p].tag){
-
T[2*p].tag=max(T[2*p].tag,T[p].tag);
-
T[2*p 1].tag=max(T[2*p 1].tag,T[p].tag);
-
T[2*p].s=T[2*p].tag;
-
T[2*p 1].s=T[2*p 1].tag;
-
T[p].tag=0;
-
}
-
}
-
inline void build_tree(int p,int l,int r) //建树
-
{
-
T[p].l=l,T[p].r=r,T[p].tag=0;
-
if(l==r){
-
T[p].s=0;
-
return;
-
}
-
int m=l r>>1;
-
build_tree(2*p,l,m),build_tree(2*p 1,m 1,r);
-
up(p);
-
}
-
inline void update(int p,int l,int r,int c) //用c更新l,r(维护每个可跳跃点的最大值)
-
{
-
if(T[p].l>r||T[p].r<l) return; //不相关
-
if(c<T[p].s) return; //c比最小值都小,不用更新
-
if(T[p].l>=l&&T[p].r<=r){ //打上懒惰标记
-
T[p].tag=max(T[p].tag,c);
-
T[p].s=T[p].tag;
-
return;
-
}
-
down(p);
-
if(T[2*p].r>=l) update(2*p,l,r,c);
-
if(T[2*p 1].l<=r) update(2*p 1,l,r,c);
-
up(p);
-
}
-
int query(int p,int k) //查询k的值
-
{
-
if(T[p].l==T[p].r) return T[p].s;
-
down(p); //必须做一次标记下移操作
-
if(T[2*p].r>=k) return query(2*p,k);
-
else if(T[2*p 1].l<=k) return query(2*p 1,k);
-
else return 0;
-
}
-
int main()
-
{
-
int n;
-
scanf("%d",&n);
-
build_tree(1,1,n);
-
for(int i=1;i<=n; i) scanf("%d",&a[i]);
-
for(int i=1;i<n; i){
-
int l,r,t;
-
scanf("%d%d",&l,&r);
-
t=query(1,i);
-
if(i!=1&&t==0) continue; //第一次要直接更新
-
update(1,l,r,a[i] t);
-
}
-
printf("%d\n",query(1,n) a[n]);
-
return 0;
-
}
H.信大学子要过河
题目描述
在这一天,阳光明媚,是一个适合春游的好日子。信大学子共 n个人一齐出发,走啊走,走到一条河的岸边,想要过河到另一边岸上继续游玩,但是却没有桥。岸边有一个只能坐两个人的船。
就像大家过题的手速不一样,大家摆渡渡河的时间也各不相同,船划到对岸的时间等于船上渡河时间较长的人所用的时间。
现在已知每一个信大学子的渡河时间 t ,信大队干部想知道,最少要花费多少时间,才能使所有人都过河。
输入描述:
共 T(1≤T≤40)组数据。
每组数据第一行为人数 n(1≤n≤100000)。以下有 n 行,每行 1 个数。第 i 1行的数为第 i个人渡河时间。
输出描述:
每组数据输出一行。
表示所有人渡过河最少渡河时间。
示例1
输入:
1
4
6
7
10
15
输出:
42
Hint:
初始:A岸:1,2,3,4;B岸:空
第一次:A岸:3,4;B岸:1,2;时间7
第二次:A岸:1,3,4;B岸:2;时间6
第三次:A岸:1;B岸:2,3,4;时间15
第四次:A岸:1,2;B岸:3,4;时间7
第五次:对岸1,2,3,4;时间7
总时间为 7 6 15 7 7=42
_____________________________________________________________________________
思路:
贪心算法。贪心策略:要尽量让送船的人在路上花费时间最短,当然尽量选择时间短的把船送回来。先把时间按从小到大排序,有两种局部最优策略:对于两个耗时最大的人,可以用最小的送两次,消耗的时间是a[1] a[n] a[n-1] a[1];或者是和样例的策略一样,先1,2过河,1送回船,n和n-1过河,2送回船,消耗时间是a[2] a[1] a[n] a[2]。比较一下选最小值即可。
代码:
-
-
using namespace std;
-
int a[100010];
-
int main()
-
{
-
int t,n;
-
cin>>t;
-
while(t--){
-
cin>>n;
-
for(int i=1;i<=n;i ) cin>>a[i];
-
sort(a 1,a 1 n);
-
if(n==1) cout<<a[1];
-
else if(n==2) cout<<a[2];
-
else{
-
int sum=0;
-
for(int i=n;i-1>2;i-=2){
-
if(a[i-1] a[1]<2*a[2]) sum =a[i] a[1] a[i-1] a[1];
-
else sum =a[1] a[2] a[i] a[2];
-
}
-
if(n%2) sum =a[3] a[1] a[2];
-
else sum =a[2];
-
cout<<sum;
-
}
-
cout<<endl;
-
}
-
return 0;
-
}
I.中国国粹
题目描述
众所周知,中国国粹是我们伟大的京剧。但本题,我们需要解决的问题是民间国粹:麻将。麻将是一项易学难精的游戏。
下面简要介绍一下麻将的游戏规则。请注意,一定要按照本题设置的麻将游戏规则进行游戏,不要带入自己了解的规则。
(1)牌型:
牌型分为四种:万;条;饼;字。
一万到九万分别用 1m 到 9m 表示;
一条到九条用 1s 到 9s 表示;
一饼到九饼用 1p 到 9p 表示;
字牌为东西南北中发白,分别用 1c 到 7c 表示。
共34 种牌型,每种牌型 4 个,共 136 张牌。
(2)当手里的 13 张牌,与摸上来的牌或别人打出的牌,组成如下的牌型,即为胡牌。
**牌型组合介绍**
对子:34种牌型,任意一种有两个,可以算作对子。如两个一万,可算作一对一万;两个发财,可算作一对白板。即AA结构。
搭子:34种牌型,任意一种有三个,可以算作搭子。如三个东风,算作东风搭子;三个二条,算作二条搭子。即AAA结构。除字牌外,三个连续的万牌、条牌、饼牌,也可以算作搭子。如123万,678条等。即ABC结构。
**胡牌方式**
a)七小对:14 张牌组成了七个对子。如:13 张牌为 1 万 ×1 2万×2 3万×2 4万×2 5万×2 7万×2 8万×2,再摸一张 1 万或者别人打出 1 万,可胡牌。
b)十三幺:1 万 9 万 1 饼 9 饼 1 条 9 条东西南北中发白,各一张。剩下那一张是上述 13 张中任意一张。如 13 张牌为 1 万 9 万 1 饼 9 饼 1 条 9 条东西南北中发白,各一张。再拿到这十三张牌的任意一张就胡。
c)将将胡:14 张牌,都是 2 万 5 万 8 万 2 条 5 条 8 条 2 饼 5 饼 8 饼的某一张。
d)屁胡:除一对牌做对子之外,其余每种牌型都是搭子结构。
现在,给定你手里的 13 张牌,你能迅速算出,你能胡 34 种牌型的哪些牌么?
输入描述:
共 T(1≤T≤100)组数据。
每组数据为 13 张牌。每张牌为 1m 到 9m、1s 到 9s、1p 到 9p、1c 到 7c的某一张,且不会出现其他不符合麻将牌型的输入,也不会出现牌多或者牌少或每种牌型数量超过 4 张的情况。(保证输入合法)每张牌之间有一个空格,行末尾除换行符外无多余字符。
输出描述:
每组数据输出一行。如果34种牌型中无牌可胡,则输出 `Ohno` 。否则,先输出总共可胡多少种牌型的数目,然后一个空格与后面的牌型隔开。再按照 1m 到 9m、1s 到 9s、1p 到 9p、1c 到 7c 的顺序,把能胡的牌输出出来。每张牌之间有一个空格,行末尾除换行符外无多余字符。
示例1
输入:
2
1s 2s 3s 2c 2c 2c 2p 3p 5m 6m 7m 1p 1p
1p 1p 2p 3p 4s 5s 6s 7c 7c 3s 3s 2m 2m
输出:
2 1p 4p
Ohno
_____________________________________________________________________________
思路:
模拟题,比较繁琐,注意细节问题要考虑全面,不太容易AC.
预处理:把1m-7c牌型映射成1-34,还需要一个反函数。用b数组存放可以胡掉的牌(能胡掉设为1),最后扫一遍b数组,通过反函数转换即可得到答案。
1.7小对最简单,13张排只能有一个奇数牌,可以胡掉那张牌。
2.将将胡:先统计所有合法牌型个数,如果<13,说明有其他牌,不和要求,否则可以胡掉个数<4的合法牌。
3.13幺:易错。注意14张牌中有一种牌要出现两次,拿到的13张牌不一定是13幺的牌各一个。比如拿两个1万,缺一个9万,就可以胡掉9万。用一个集合表示13幺的牌出现的个数,不能出现13幺以外的牌,且每张不能大于2.
4.屁胡:这个最麻烦,搭子的类型多。出了字牌以外,其他牌还有ABC结构。每个牌都可能作为对子,其他为搭子结构。首先枚举可能胡掉的牌,再枚举对子结构,除去对子结构,14张牌除去对子,还有12张牌,dfs搜索4次,判断合法的搭子。
代码:
-
-
using namespace std;
-
int a[40],b[40],S[20]={1,9,10,18,19,27,28,29,30,31,32,33,34}; //a[i]i牌出现的个数,b[i]表示i能否被胡,能被胡值为1 S集合为13幺
-
unordered_map<string,int>mp; //牌型对应到数字
-
unordered_map<int,string>unmp; //数字映射到牌型
-
inline void init() //初始化
-
{
-
mp["1m"]=1,mp["2m"]=2,mp["3m"]=3,mp["4m"]=4,mp["5m"]=5,mp["6m"]=6,mp["7m"]=7,mp["8m"]=8,mp["9m"]=9;
-
unmp[1]="1m",unmp[2]="2m",unmp[3]="3m",unmp[4]="4m",unmp[5]="5m",unmp[6]="6m",unmp[7]="7m",unmp[8]="8m",unmp[9]="9m";
-
mp["1s"]=10,mp["2s"]=11,mp["3s"]=12,mp["4s"]=13,mp["5s"]=14,mp["6s"]=15,mp["7s"]=16,mp["8s"]=17,mp["9s"]=18;
-
unmp[10]="1s",unmp[11]="2s",unmp[12]="3s",unmp[13]="4s",unmp[14]="5s",unmp[15]="6s",unmp[16]="7s",unmp[17]="8s",unmp[18]="9s";
-
mp["1p"]=19,mp["2p"]=20,mp["3p"]=21,mp["4p"]=22,mp["5p"]=23,mp["6p"]=24,mp["7p"]=25,mp["8p"]=26,mp["9p"]=27;
-
unmp[19]="1p",unmp[20]="2p",unmp[21]="3p",unmp[22]="4p",unmp[23]="5p",unmp[24]="6p",unmp[25]="7p",unmp[26]="8p",unmp[27]="9p";
-
mp["1c"]=28,mp["2c"]=29,mp["3c"]=30,mp["4c"]=31,mp["5c"]=32,mp["6c"]=33,mp["7c"]=34;
-
unmp[28]="1c",unmp[29]="2c",unmp[30]="3c",unmp[31]="4c",unmp[32]="5c",unmp[33]="6c",unmp[34]="7c";
-
}
-
inline bool find(int n) //S(13幺)中找到n
-
{
-
for(int i=0;i<13; i) if(n==S[i]) return true;
-
return false;
-
}
-
inline bool dfs(int s) //判断合法的搭子,搜索4次,每次除去3个搭子牌
-
{
-
if(s==4){ //边界,如果a都为0就合法了
-
for(int i=1;i<=34; i) if(a[i]) return false;
-
return true;
-
}
-
for(int i=1;i<=34; i)
-
if(a[i]){
-
if(a[i]>=3){
-
a[i]-=3; //拿掉搭子AAA
-
if(dfs(s 1)){
-
a[i] =3; //复原
-
return true;
-
}
-
a[i] =3;
-
}
-
if(i<28){
-
if(a[i]&&a[i 1]&&a[i 2]&&unmp[i][1]==unmp[i 1][1]&&unmp[i 1][1]==unmp[i 2][1]){
-
a[i]--,a[i 1]--,a[i 2]--; //拿掉ABC搭子
-
if(dfs(s 1)){
-
a[i] ,a[i 1] ,a[i 2] ; //复原
-
return true;
-
}
-
a[i] ,a[i 1] ,a[i 2] ;
-
}
-
}
-
return false;
-
}
-
return false;
-
}
-
inline void seven_pair(int &f) //七小对
-
{
-
int cnt=0,t; //cnt记录奇数个数,只能有一个奇数
-
for(int i=1;i<=34; i){
-
if(a[i]&1) cnt ,t=i;
-
if(cnt>1) return;
-
}
-
b[t]=1,f=1;
-
}
-
inline void thirteen_yao(int &f) //13幺
-
{
-
for(int i=1;i<=34; i){
-
if(!a[i]) continue;
-
if(!find(i)) return; //i不在13幺中不合法
-
}
-
int cnt=0,t;
-
for(int i=0;i<13; i){
-
if(a[S[i]]>2||cnt>1) return; //同一个牌数等于2的个数不大于1,不能有大于2的
-
if(a[S[i]]==2) cnt ;
-
if(a[S[i]]==0) t=S[i];
-
}
-
f=1;
-
if(cnt==0) for(int i=0;i<13; i) b[S[i]]=1; //如果拿到的13幺都出现了,都可以胡
-
else b[t]=1; //否则胡掉没出现的那个牌
-
}
-
inline void jjh(int &f) //将将胡
-
{
-
if(a[2] a[5] a[8] a[11] a[14] a[17] a[20] a[23] a[26]<13) return;
-
for(int i=2;i<=8;i =3){ //胡之前判断个数
-
if(a[i]<4) b[i]=1;
-
if(a[i 9]<4) b[i 9]=1;
-
if(a[i 18]<4) b[i 18]=1;
-
}
-
f=1;
-
}
-
inline void pih(int &f) //屁胡
-
{
-
for(int i=1;i<=34; i){ //枚举可能胡的牌
-
if(a[i]==4||b[i]) continue;
-
int flg=0;
-
a[i] ;
-
for(int j=1;j<=34; j)
-
if(a[j]>=2){ //a[j]选做对子
-
a[j]-=2;
-
if(dfs(0)){ //判断有没有搭子结构
-
flg=1;
-
a[j] =2;
-
break;
-
}
-
a[j] =2; //还原
-
}
-
if(flg) f=1,b[i]=1;
-
a[i]--; //还原
-
}
-
}
-
int main()
-
{
-
int t;
-
char ch[5];
-
init();
-
scanf("%d",&t);
-
while(t--){
-
int flag=0;
-
memset(a,0,sizeof(a));
-
memset(b,0,sizeof(b));
-
for(int i=0;i<13; i) scanf("%s",ch),a[mp[ch]] ;
-
seven_pair(flag);
-
thirteen_yao(flag);
-
jjh(flag);
-
pih(flag);
-
if(flag){
-
int cnt=0;
-
for(int i=1;i<=34; i) if(b[i]) cnt ;
-
printf("%d",cnt);
-
for(int i=1;i<=34; i) if(b[i]) printf(" %s",unmp[i].c_str());
-
printf("\n");
-
}
-
else puts("Ohno");
-
}
-
return 0;
-
}
J.Kabayashi And Three consciousness
Ruri 发现对于任意一个有理数 ,都可以不断按以下形式拆分。
现在给定 p,q ,求 a1,a2,⋯ ,an,其中 a1,a2,⋯ ,an都是整数。
输入描述:
第一行为两个正整数p(1≤p≤1e18),q(1≤q≤1e18) 。
保证 p,q互质。
输出描述:
第一行一个整数 n 。第二行n个数,表示拆分结果。
示例1
输入:
17 3
输出:
3
5 1 2
Hint
_____________________________________________________________________________
思路:
把整数部分和小数部分拆开来看。比如a1是p/q的整数部分,即a1=p/q,后面为小数部分,记为b1,可知b1=(p%q)/q,取倒数可以得到递推式q/(p%q)=a2 b2。重复上面步骤,直到小数部分为0即可。
Code:
-
-
using namespace std;
-
typedef long long ll;
-
int main()
-
{
-
ll p,q;
-
vector<ll>v;
-
cin>>p>>q;
-
while(1){
-
v.push_back(p/q);
-
if(p%q==0) break;
-
ll t=p;
-
p=q;
-
q=t%q;
-
}
-
cout<<v.size()<<endl;
-
for(int i=0;i<v.size();i )
-
printf("%lld ",v[i]);
-
return 0;
-
}
K.最小整数问题
题目描述
红红最近对超大整数非常感兴趣,可是超大整数已经超过了C语言中整数的可表示范围。而蓝蓝觉得这个问题很简单,他甚至提出了一个更难的问题:给定一个超大整数,在其各位置上的数字可以打乱重新排序的情况下,能够生成许多个不同大小的超大整数。那么请问,在这些超大整数之中,最小的那个是多少?请告诉红红,这个最小的超大整数。(不可出现前导零)
输入描述:
共 T(1≤1≤100)组数据。
每组数据为一个超大整数,用十进制表示,该数的数位不超过 200 位。输入一定合法。
输出描述:
每组数据输出一行,为题意中的最小的超大整数。末尾带一个换行符。
示例1
输入:
2
13425
9876543210
输出:
12345
1023456789
_____________________________________________________________________________
思路:
签到题,sort一遍即可。如果有前导0,找到第一个非零元素,交换即可。
-
-
using namespace std;
-
int main()
-
{
-
int t;
-
cin>>t;
-
string s;
-
while(t--){
-
cin>>s;
-
sort(s.begin(),s.end());
-
int i=0;
-
while(i<s.size()&&s[i]=='0') i ;
-
if(s[0]=='0') swap(s[0],s[i]);
-
cout<<s<<endl;
-
}
-
return 0;
-
}
L.Find And Cal
题目描述
Ruri 遇到了两个函数:f(x)表示大于 x的最小的质数。g(x,y) 表示x!包含质因子 y的个数。Ruri 想知道对于一个给定 (x,y)对, g(f(x),y)的大小。
输入描述:
第一行一个整数 T(1≤103),表示询问组数。
后面 T 行为两个正整数 x,y(1≤x≤1e16,2≤y≤1e9)
保证y是素数。
输出描述:
T行,每行一个整数,g(f(x),y)的大小。答案对 998244343 取模。
示例1
输入:
1
7 2
输出:
8
Hint: 大于 7 的最小质数为 11,11!=1×2×3×⋯×11 ,其中 2,6,10有 3 个 2的因子,4 有 2 个2 的因子,8有 3个 2的因子。
_____________________________________________________________________________
思路:
题目后来做了改动,保证y是素数。求出x后用阶乘分解即可。
本题考察大数判素,用robin判素求出f(x)。
代码:
-
#include<bits/stdc .h>
-
using namespace std;
-
typedef long long ll;
-
const int mod=998244353;
-
ll mul(ll a,ll b,ll m) //大数相乘取模,防止溢出
-
{
-
ll res=0;
-
a%=m,b%=m;
-
for(;b;a=(a a)%m,b>>=1) if(b&1) res=(res a)%m;
-
return res;
-
}
-
ll qp(ll x,ll n,ll m) //快速幂
-
{
-
ll res=1;x%=m;
-
for(;n;x=mul(x,x,m),n>>=1) if(n&1) res=mul(res,x,m);
-
return res%m;
-
}
-
bool Miller_Robin(ll a,ll n) //robin判素
-
{
-
ll x=n-1,y;
-
int t=0;
-
while((x&1)==0) x>>=1,t ;
-
x=qp(a,x,n);
-
for(int i=1;i<=t; i){
-
y=mul(x,x,n);
-
if(y==1&&x!=1&&x!=n-1) return true;
-
x=y;
-
}
-
return y!=1?true:false;
-
}
-
bool isprime(ll x)
-
{
-
if(x==2||x==3||x==7||x==61||x==24251) return true;
-
if(!(x&1)||!(x%3)||!(x%61)||!(x%24251)) return false;
-
if(x%6!=1&&x%6!=5) return false;
-
int a[5]={2,3,7,61,24251};
-
for(int i=0;i<5; i){
-
if(a[i]>=x) break;
-
if(Miller_Robin(a[i],x)) return false;
-
}
-
return true;
-
}
-
int main()
-
{
-
int t,y;
-
ll x;
-
scanf("%d",&t);
-
while(t--)
-
{
-
scanf("%lld%d",&x,&y);
-
x;
-
while(!isprime(x)) x;
-
ll ans=0;
-
x=11;
-
while(x) ans=(ans x/y)%mod,x/=y;
-
printf("%lld\n",ans);
-
}
-
return 0;
-
}
这道题卡常数,上面代码交了10多次才AC。。。
可以加快读快写优化。
这篇好文章是转载于:学新通技术网
- 版权申明: 本站部分内容来自互联网,仅供学习及演示用,请勿用于商业和其他非法用途。如果侵犯了您的权益请与我们联系,请提供相关证据及您的身份证明,我们将在收到邮件后48小时内删除。
- 本站站名: 学新通技术网
- 本文地址: /boutique/detail/tanhgehgkf
-
photoshop保存的图片太大微信发不了怎么办
PHP中文网 06-15 -
Android 11 保存文件到外部存储,并分享文件
Luke 10-12 -
word里面弄一个表格后上面的标题会跑到下面怎么办
PHP中文网 06-20 -
《学习通》视频自动暂停处理方法
HelloWorld317 07-05 -
photoshop扩展功能面板显示灰色怎么办
PHP中文网 06-14 -
微信公众号没有声音提示怎么办
PHP中文网 03-31 -
excel下划线不显示怎么办
PHP中文网 06-23 -
excel打印预览压线压字怎么办
PHP中文网 06-22 -
怎样阻止微信小程序自动打开
PHP中文网 06-13 -
TikTok加速器哪个好免费的TK加速器推荐
TK小达人 10-01