练习赛41

Posted tieechal

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了练习赛41相关的知识,希望对你有一定的参考价值。

练习赛41

Begin:
2019-03-27 18:35

PDD:310rlb

A

递增N元组 HihoCoder-1733

题目大意:

给定N个数组,每个数组都包含M个整数。
现在你被要求从每个数组中选出一个数,总共N个数。
在 M^N 种选法中,有多少种选法满足选出的N个数恰好是严格递增的?

CODE:

#include<bits/stdc++.h>
using namespace std;
#define oo 1000000007
#define M 10009
int a[110][M],pos[110][M],n,m;
int dp[110][M],res[M],ans;
int main()
    scanf("%d%d",&n,&m);
    for(int i=1;i<=n;i++)
        for(int j=1;j<=m;j++)scanf("%d",&a[i][j]);
        sort(a[i]+1,a[i]+m+1);
    
    for(int i=2;i<=n;i++)
        int x=m;
        for(int j=m;j>=1;j--)
            while(a[i-1][x]>=a[i][j]&&x>=1)x--;
            pos[i][j]=x;
        
    
    for(int i=1;i<=m;i++)dp[1][i]=1;
    for(int i=2;i<=n;i++)
        res[1]=dp[i-1][1];
        for(int j=2;j<=m;j++)res[j]=(res[j-1]+dp[i-1][j])%oo;
        for(int j=1;j<=m;j++)if(pos[i][j]>=0)dp[i][j]=res[pos[i][j]];
    
    for(int i=1;i<=m;i++)ans=(ans+dp[n][i])%oo;
    printf("%d",ans);

B

逃离迷宫5 HihoCoder - 1734

题目大意:

给定一张地图,‘.‘代表可以走,‘#‘代表不能走,但可以通过施膜法通过一个‘#‘,膜法只能用一次,问从(1,1)走到(n,n)的最短长度。

思路:

这道题暴力dfsT了,转变一下思路,枚举使用膜法的块(假设对于一个点也可以施膜法,即什么也不改变):从(1,1)spfa一次,只将‘.‘的块加入队列中;再从(n,n)spfa一次,同样也只将‘.‘的块加入队列中。那么ans=max(ans,dis1[i]+dis2[i])跑了74ms,竟然是VJ上最快的

CODE:

#include<bits/stdc++.h>
#define oo 10000000
const int N=1010;
using namespace std;
struct node
    int x,y;
q[N*N];
bool vis[N][N];
int n,dis[N][N],hd,tl,dis2[N][N];
const int dx[4]=0,0,1,-1;
const int dy[4]=1,-1,0,0;
char c[N][N];
void spfa(int x,int y,bool f)
    for(int i=1;i<=n;i++)
        for(int j=1;j<=n;j++)dis[i][j]=oo,vis[i][j]=0;
    hd=tl=1;
    q[1]=(node)x,y,dis[x][y]=0,vis[x][y]=1;
    while(hd<=tl)
        node t=q[hd];
        int d=dis[t.x][t.y];
        for(int i=0;i<=3;i++)
            int xx=t.x+dx[i],yy=t.y+dy[i];
            if(xx<=0||xx>n||yy<=0||yy>n)continue;
            if(dis[xx][yy]>d+1)
                dis[xx][yy]=d+1;
                if(!vis[xx][yy]&&c[xx][yy]=='.')
                    q[++tl]=(node)xx,yy;
                    vis[xx][yy]=1;
                
            
        
        hd++;
    
    if(f==0)
        for(int i=1;i<=n;i++)
        for(int j=1;j<=n;j++)dis2[i][j]=dis[i][j];  
    

int main()
    scanf("%d",&n);
    for(int i=1;i<=n;i++)scanf("%s",c[i]+1);
    spfa(1,1,0),spfa(n,n,1);
    int ans=oo;
    for(int i=1;i<=n;i++)
        for(int j=1;j<=n;j++)ans=min(ans,dis[i][j]+dis2[i][j]);
    printf("%d",ans==oo?-1:ans);

C

Subsequence HDU - 3530

题目大意:

给出一个长度为n的数列,求一个最长的区间,使得区间中最小值和最大值的差在[m,k]之间。

思路:

第一眼,woc单调队列的既视感........好的,,,,不会写QAQ

way1:单调队列O(N)

1.来两个单调队列,一个维护最大值,一个维护最小值。

2.如果此时Ma-Mi>R,那么我们要么把Mi搞大,要么把Ma弄小,然后我们惊奇的发现,对应的操作都是将其中一个单调队列head++,即把队首元素pop出去。

3.那么到底要pop哪个呢,我们画个图就知道了,当然是pop掉位置靠前的那一个,但为什么呢:

我们来看:

choose1

如果我们pop掉Ma,并且假设接下来的Ma-Mi<=R(符合条件),但我们也不能取[i,r]这个长度作为ans,因为[i,r]中还包含着那个已经pop掉的最大值,所以我们只能取[pos[lastma]+1,r]作为答案,就是把当前区间彻底挪开上一个最大值的位置。

choose2

同理,假如我们pop的是Mi,并且假设接下来的Ma-Mi<=R(符合条件),那么我们得到的最优解

是[pos[lastmi]+1,r]。

综上,假设这次pop操作后能使当前区间符合条件,答案是pos[lastma]+1,r]或[pos[lastmi]+1,r],

要取得最优解,当然是pos[Mi]小popMi,反之popMa。

假如现在你已经使Ma-Mi<=R,如果当前Ma-mi还能满足>=L,那么就可以更新答案了。至于是r-pos[lastmi]还是r-pos[lastma]得取决你刚才最后pop的是哪个,可以用个简单的判断(见代码pos_mi,pos_ma);大概就这样了,虽然讲起来有点麻烦,但代码其实很短,需要注意一些细节。

CODE:

#include<bits/stdc++.h>
using namespace std;
const int N=100000+5;
int a[N],q1[N],q2[N];
int head1,head2,tail1,tail2,pos_ma,pos_mi,ans;
int main()
    int n,L,R;
    while(~scanf("%d%d%d",&n,&L,&R))
        for(int i=1;i<=n;i++)scanf("%d",&a[i]);
        ans=head1=head2=tail1=tail2=0;
        pos_mi=pos_ma=0;
        for(int i=1;i<=n;i++)
            while(head1<tail1&&a[q1[tail1-1]]<=a[i])tail1--;
            q1[tail1++]=i;
            while(head2<tail2&&a[q2[tail2-1]]>=a[i])tail2--;
            q2[tail2++]=i;
            while(a[q1[head1]]-a[q2[head2]]>R)
                if(q1[head1]<q2[head2])pos_ma=q1[head1++];
                else pos_mi=q2[head2++];
            
            if(a[q1[head1]]-a[q2[head2]]>=L)
                ans=max(ans,i-max(pos_ma,pos_mi));
            
        
        printf("%d\n",ans);     
    

way2:Multiset O(NlogN)

数据有点水,能过。而且代码比较短。

D

一人麻将 HihoCoder - 1503

题目大意:

小Hi起手拥有14张牌,之后小Hi每摸一张牌后,如果没有胡牌,就出一张牌,直至胡牌或牌被摸光。
胡牌的规则如下:
手中14张牌最多只能属于两个花色。
手中14张牌的牌型须满足下列两个条件之一:
1)3 + 3 + 3 + 3 + 2,其中的3代表一坎(指同花色且同数字、或同花色且数字相连的三张牌),其中的2代表一个对子(指两张同花色且同数字的牌)。
2)2 × 7,即14张牌构成7个对子,特别的,四张花色数字全相同的牌可以看作两个对子。

现给出小Hi的起手牌,并按顺序给出场上其余小Hi将要摸的牌。

请计算小Hi最早在摸第几张牌之后就可能胡牌,如果起始牌就满足条件输出0。无法胡牌输出-1。

思路:

首先要理解题意:

1.明确一点:题目说每摸张牌如果没法胡,都要弃掉一张手牌。其实我们不必考虑每轮丢哪张牌,我们可以采取上帝视角,把每张牌都保留下来,等到什么时候能胡牌了,再把那些多余的牌扔掉,这样没有任何影响吧。超级骚操作

2.胡牌有两种大方案:

A :四坎一对 B: 七对

先来看怎么判B方案:

bool ok2()
    int tmp[5];
    for(int i=0;i<=2;i++)
        tmp[i]=0;
        for(int j=1;j<=9;j++)tmp[i]+=cnt[i][j]/2;
    
    if(tmp[0]+tmp[1]>=7)return 1;
    if(tmp[0]+tmp[2]>=7)return 1;
    if(tmp[1]+tmp[2]>=7)return 1;
    return 0;

由代码可以看出——...什么都看不出,这一步很好想,不解释了

再来看怎么判A方案,这里有点复杂:

bool dfs(int now)//接下来要凑第几个 
    if(now>=5)//现在要凑最后一个————对子
        for(int i=1;i<=9;i++)
            if(cnt[co1][i]>=2||cnt[co2][i]>=2)return 1;
        
        return 0;
    
    //现在要凑————坎(smg名字????)
    //坎又有两种:同花色且同数字/同花色且数字相连.
    //same_num:co1
    for(int i=1;i<=9;i++)
        if(cnt[co1][i]>=3)
            cnt[co1][i]-=3;
            if(dfs(now+1))return 1;
            cnt[co1][i]+=3;
        
    //same_num:co2   同上操作,只要把co1改成co2即可
    //link_num:co1
    for(int i=1;i<=7;i++)
        if(cnt[co1][i]&&cnt[co1][i+1]&&cnt[co1][i+2])
            cnt[co1][i]--,cnt[co1][i+1]--,cnt[co1][i+2]--;
            if(dfs(now+1))return 1;
            cnt[co1][i]++,cnt[co1][i+1]++,cnt[co1][i+2]++;
        
    
    //link_num:co2 同上操作,只要把co1改成co2即可
    return 0;

3.就是这样。那么co1,co2又是什么呢,题目中还说胡牌有一个前提条件:“手中14张牌最多只能属于两个花色”(一定要好好好好看题),所以我们需要枚举还握在手中的牌的花色:最多只能有两种。

bool choosecolor()
    for(co1=0;co1<=2;co1++)
    for(co2=co1;co2<=2;co2++)if(dfs(1))return 1;
    return 0;

4.完整代码如下,部分重要函数已于上面出示过了,这里减短码量,给一个大致框架

Code:

#include<bits/stdc++.h>
using namespace std;
int cnt[5][12];
int co1,co2,n;
char s[5];
void read()//存牌 
    scanf("%s",s);
    cnt[s[0]-'a'][s[1]-'0']++;

bool dfs(int now)
bool choosecolor()
bool ok2()
int main()
    scanf("%d",&n);
    for(int i=1;i<=14;i++)read();
    if(choosecolor())puts("0");return 0;
    if(ok2())puts("0");return 0;
    for(int i=1;i<=n;i++)
        read();
        if(ok2())printf("%d",i);return 0;
        if(choosecolor())printf("%d",i);return 0;
    
    return puts("-1"),0;

以上是关于练习赛41的主要内容,如果未能解决你的问题,请参考以下文章

HihoCoder1672 : 区间问题([Offer收割]编程练习赛41)(贪心)

HihoCoder1673 : 01间隔矩阵([Offer收割]编程练习赛41)(单调队列)

HiHoCoder1671 : 反转子串([Offer收割]编程练习赛41)(占位)

艰难地学习 Python,练习 41 头痛

HihoCoder1670 : 比赛日程安排([Offer收割]编程练习赛41)(模拟)

C++ 入门书第 4 卷中的练习 15.41 [关闭]