10-3国庆节第六场模拟赛题解

T1 炮 (cannon)

Description

Makik 曾经沉迷于打麻将,热衷于点炮的他近日终于开始爱上了中国象棋。面对一个n×m的棋盘,他不禁陷入了思考:在这张棋盘上摆“炮”,并且任意两个“炮”之间不会互相攻击的方案数究竟有多少呢?

说明:两枚炮可以互相攻击,当且仅当它们处在同一行或同一列上且恰好间隔一枚棋子,即“炮打隔山”。

由于 Makik 记不住太大的数字,所以请告诉他答案对 999983 取模的结果。

Input

输入文件包含一行两个整数 n,m,分别表示棋盘的长度和宽度。

Output

输出一行一个整数,表示方案数对 999983999983 取模的结果。

XJBDP

依稀记得是洛谷月赛的一道原题,不过并没有做当时。

刚开始以为是个数学题,想了想发现是DP。但是状态不会设啊。。。

感谢题解:

设\(f[i][j][k]\)表示前i行有j列放置1个炮,有k列放置2个炮。

根据分析问题可以知道,在同一列或者同一列都是不可以放超过两个炮的,因为三个炮就会开始互相攻击了。

所以每一行每一列只有三种可能,要么不放,要么放一个,要么放两个。

这是一个非常有用的信息,可以让我们开始写状态转移。

使用推表法。

很容易想出如果i+1可以通过什么都不放直接有i推出,这是第i+1行不放炮的情况。

f[i+1][j][k]=(f[i+1][j][k]+f[i][j][k])%mod;

可以由\(f[i][j][k]\)推出\(f[i+1][j+1][k]\)的取值,意思就是前i行中如果存在一列什么都没有放,那么就可以在这一列上放一个,这样就会使j加1,这是i+1行放一个炮的情况。

if(m-j-k>=1)f[i+1][j+1][k]=(f[i+1][j+1][k]+f[i][j][k]*(m-j-k))%mod;

还可以由\(f[i][j][k]\)推出\(f[i+1][j-1][k+1]\)的情况,意思就是如果前i行中存在只放1个炮的列,我们就可以在这一列上面再放一个炮让它变成放两个棋子的,这也是i+1行放一个炮的情况。

if(j>=1)f[i+1][j-1][k+1]=(f[i+1][j-1][k+1]+f[i][j][k]*j)%mod;

上面的三种情况针对的是第i+1行放0个或者1个炮的情况。

下面还有三种情况是第i+1行放2个炮的情况。

那么首先,可以从\(f[i][j][k]\)推出\(f[i+1][j-2][k+2]\),意思是我们可以找到两个已经放了一个炮的列,再在这两列上各自放上一个炮,那么就会使有一个炮的列数减2,有两个炮的列数加2.

if(j>=2)f[i+1][j-2][k+2]=(f[i+1][j-2][k+2]+f[i][j][k]*C(j))%mod;C(j)=C(j,2)

还可以从\(f[i][j][k]\)推出\(f[i+1][j+2][k]\)意思是可以找到两个一个炮都没放的列,然后再在这两列再放上一个炮

if((m-j-k)>=2)f[i+1][j+2][k]=(f[i+1][j+2][k]+f[i][j][k]*C(m - j - k)) % mod;

还可以综合上面两种,第i+1行上新放的两个炮一种一个

if((m-k-j)>=1&&j>=1)f[i+1][j][k+1]=(f[i+1][j][k+1]+f[i][j][k]*(m-k-j)*j)%mod;

哦对了,最后答案就是\(\sum_{j=0;j<=n}\sum_{k=0,k+j<=n}f[n][j][k]\)。

至此,问题得以解决。

code

#include<iostream>
#include<cstdio>
using namespace std;
const int mod=9999973;
inline int read(){
    int sum=0,f=1;
    char ch=getchar();
    while(ch<'0'||ch>'9'){
        if(ch=='-')f=-1;
        ch=getchar();
    }
    while(ch>='0'&&ch<='9'){
        sum=(sum<<1)+(sum<<3)+ch-'0';
        ch=getchar();
    }
    return sum*f;
}
int C(int _){
    return _*(_-1)/2;
}
int n,m;
long long ans;
long long f[111][111][111];
int main(){
    n=read();
    m=read();
    f[0][0][0]=1;//什么东西都不放有1种方案
    for(int i=0;i<=n;i++){
        for(int j=0;j<=m;j++){
            for(int k=0;k+j<=m;k++){
                if(f[i][j][k]){
                    f[i+1][j][k]=(f[i+1][j][k]+f[i][j][k])%mod;
                    if(j>=1)f[i+1][j-1][k+1]=(f[i+1][j-1][k+1]+f[i][j][k]*j)%mod;
                    if(m-j-k>=1)f[i+1][j+1][k]=(f[i+1][j+1][k]+f[i][j][k]*(m-j-k))%mod;
                    if(j>=2)f[i+1][j-2][k+2]=(f[i+1][j-2][k+2]+f[i][j][k]*C(j))%mod;
                    if((m-j-k)>=2)f[i+1][j+2][k]=(f[i+1][j+2][k]+f[i][j][k]*C(m - j - k)) % mod;
                    if((m-k-j)>=1&&j>=1)f[i+1][j][k+1]=(f[i+1][j][k+1]+f[i][j][k] * (m - k -  j) * j) % mod;
                }
            }
        }
    }
    for(int i=0;i<=m;i++){
        for(int j=0;j+i<=m;j++){
            (ans+=f[n][i][j])%=mod;
        }
    }
    printf("%lld\n",ans);
    return 0;
}

T2滚 (roll)

Description

玩腻了象棋之后,Makik 想出去看看风景。这次,他来到了 MS 山脉。MS 山脉共包含 n 座山峰,山峰从 11 到 nn 编号,有些山峰间还连接有道路。被这里的景色深深吸引的 Makik 花重金请人把他抬到了1号山峰的顶端,他要从这里开始旅程。

Makik 不喜欢爬山,但是喜欢下山,因为下山可以滚。当 Makik 在 i号山峰上时,如果 i 号山峰与 j 号山峰间有一条道路,并且 j 号山峰的高度不大于 i 号山峰的高度,那么他就可以顺势沿这条路从 i 号山峰滚到 j 号山峰上。Makik 还有一个神奇的能力,可以沿着滚来的道路走回(而不是滚回)到曾经游览过的山峰上。

Makik 想要游览尽可能多的山峰,还想在此前提下使滚的距离尽可能短。请你说出,他最多能游览到多少山峰,此时最短需要滚多少路?

Input

输入文件第一行包含两个整数 n,m分别表述山峰的道路的数量。

接下来一行包含 \(n\) 个数,依次表示每一座山峰的高度。

之后 m 行,每行三个整数 x_i,y_i,z_i表示 xi 号和 yi 号两座山峰间有一条长度为 zi 的道路。

Output

输出一行两个整数,分别表示 Makik 最多能游览到的山峰数目和他最短需要滚的距离。

原题链接:洛谷P2573 [SCOI2012]滑雪 https://www.luogu.org/problemnew/show/P2573

要注意一个问题,就是滚和走是不一样的,题目中求得是滚的路程,所以向回走是对答案不产生贡献的。

所以尝试画一下图,可以发现,我们从一开始向外扩展,因为要保证到达的点最多,所以需要先扩展出所有可以从一到达的点,一个搜索就可以解决问题。

在这个基础上,我们要求出最短的路程,这里的路程就是我们扩展过程中的每一条边的边长。

试想一下,当我们扩展出这条边之后,就一定是滚到那里的,所以一定会对答案产生贡献。

感觉上述太杂乱了,我需要重新说一下。

题目大意:从一开始,对于所有可以扩展到的点的最小生成树。

不能直接求最小生成树,因为有些点因为高度的限制无法到达,所以上一句说的是可以扩展到的所有的点。

保证从高到低,所以根据两点之间的高度比较来建边。(高点向低点建单向边,同高度建无向边)

做法步骤:输入时按照高度进行建边,先从1开始宽搜,标记每个可以到达的点,这个时候就可以统计出第一问的答案,之后可以求一下建出的边的MST,又因为要保证到达的点最多,所以在对边排序的时候要改变一下优先级。

在类似这种情况下,这三个点都是我们可以走到也应该走到的,但是如果对边长进行排序的话,很显然有两条边都连向了同一个点,这就不符合我们要求的最小树形图,如果按照高度排序,就可以很好的先把点都遍历到,再去求最小的边权

[在类似这种情况下,这三个点都是我们可以走到也应该走到的,但是如果对边长进行排序的话,很显然有两条边都连向了同一个点,这就不符合我们要求的最小树形图,如果按照高度排序,就可以很好的先把点都遍历到,再去求最小的边权](

然后就很简单了,难点就出在排序。

#include<iostream>
#include<cstdio>
#include<queue>
#include<algorithm>
using namespace std;
const int wx=2000017;
inline int read(){
    int sum=0,f=1;
    char ch=getchar();
    while(ch<'0'||ch>'9'){
        if(ch=='-')f=-1;
        ch=getchar();
    }
    while(ch>='0'&&ch<='9'){
        sum=(sum<<1)+(sum<<3)+ch-'0';
        ch=getchar();
    }
    return sum*f;
}
int h[wx];
int n,m,num,tot,x,y,z,cnt;
int head[wx];
int f[wx];
long long ans;
int vis[wx];
struct e{
    int nxt,to,dis;
}edge[wx*2];
struct node{
    int l,r,d;
    friend bool operator < (const node& a,const node& b){
        if(h[a.r]==h[b.r])return a.d < b.d;
        return h[a.r] > h[b.r];
    }
}a[wx];
void add(int from,int to,int dis){
    edge[++num].nxt=head[from];
    edge[num].to=to;
    edge[num].dis=dis;
    head[from]=num;
}
queue<int > q;

void bfs(int s){
    q.push(s); vis[1] = 1;
    while(!q.empty()){
        int u=q.front();q.pop();
        for(int i=head[u];i;i=edge[i].nxt){
            int v=edge[i].to;
            a[++ tot].l = u; a[tot].r = v; a[tot].d = edge[i].dis;
            if(!vis[v]){
                vis[v]=1;
                 cnt++;q.push(v);
            }
        }
    }
}
int find(int x){
    if(x==f[x])return x;
    return f[x]=find(f[x]);
}
signed main(){
    freopen("roll.in" ,"r",stdin);
    freopen("roll.out","w",stdout);
    n=read();m=read();
    for(int i=1;i<=n;i++)h[i]=read();
    for(int j=1;j<=m;j++){
        x=read();y=read();z=read();
        if(h[x]==h[y]){
            add(x,y,z);add(y,x,z);
        }
        else if(h[x]>h[y])add(x,y,z);
        else add(y,x,z);
    }
    bfs(1);
    for(int i=1;i<=n;i++)f[i]=i;
    sort(a+1,a+1+tot);
    for(int i=1;i<=tot;i++){
        if(find(a[i].l)!=find(a[i].r)){
            ans+=a[i].d;
            f[find(a[i].l)]=find(a[i].r);
        }
    }
    printf("%d %lld\n",cnt + 1,ans);
    return 0;
}

T3分裂 (split)

Description

Makik 滚得头昏脑胀,一下子分裂成了 n 只小 makik。小 makik 们从左到右排成一排,看起来十分有趣。

你想调戏一下小 makik 们,于是准备了这样的一个游戏:先为每只小 makik 分配一个编号,之后进行许多次询问。每一次询问时,先指定一个区间,对于区间内的任意两只小 makik,如果它们编号相同,则称其中左侧的小 makik 是一只坏 makik。之后,你要对区间内所有的坏 makik 中最靠右的一个施加惩罚。如果区间内没有坏 makik,那么你会感到有些无聊。

你想提前知道,对于每一次询问,你将要惩罚第几只小 makik。

Input

输入文件第一行包含一个正整数 n,表示一共有 n 只小 makik。

接下来一行包含 n 个数 a_1, a_2, ..., a_n依次表示每只小 makik 被分配到的编号。

之后一行给出一个整数 q,表示询问的数量。

下面 q 行,每行两个整数 l,r表示这次询问的区间为左起第 l 只到第 r 只小 makik。

Output

输出 q 行,对应 q 次询问。每次询问输出最靠右的坏 makik 排在第几个位置。如果没有坏 makik,输出 “Boring”。

这道题的暴力应该是离线的,但是好气,这道题的数据实在是太水了,各种暴力碾压标算,eolv用生日悖论给我们证明了这道题的数据有多么难处,然而我这个辣鸡并没有get到

感谢教练教育我们要通读题目,让我这场考试发现了最水的第三题,但是,就是因为它太水了,搞得我没有像往常一样看一看数据范围再做题,直接敲了一个线段树完事。本来美滋滋地以为自己T3绝对稳了,但是最后却得20分。

忽然发现,\(a_i\)的范围是1e9的,这心情。。。石乐志石乐志。。。身败名裂身败名裂。。。

这次教训告诉我们,写区间操作用类似线段树的数据结构时一定要注意输入数据的范围,因为再记录前驱时很容易想到开一个桶,然后就傻傻的爆空间。。。RE。。。所以一定要写离散化。

忽然发现自己的离散化有点不透彻,复习一下:
离散化:

    for(int i=1;i<=n;i++){
        aa[i]=read();
        b[i] = aa[i];
    }
    sort(b+1,b+1+n);
    for(int i = 1; i <= n; ++ i) a[i] = lower_bound(b + 1, b + 1 + n, aa[i]) - b;

去重:

    for(int i=1;i<=n;i++)scanf("%d",&a[i]);
    int size=unique(a+1,a+1+n)-a-1;
    //debug()
    //for(int i=1;i<=size;i++)printf("%d ",a[i]);

再来看这道题:对于当前询问的区间,如果一个位置他的前驱也在这个区间,那么他的前驱的位置就会对答案产生影响,所以直接离散化,然后记录一下每个位置的前驱,再把这个前驱放到线段树中维护。每一次询问直接输出区间最大值即可。

code:

#include<iostream>
#include<cstdio>
#include<cstring>
#include<algorithm>
#define ls(o) o<<1
#define rs(o) o<<1|1
using namespace std;
const int wx=2000017;
inline int read(){
    int sum=0,f=1;
    char ch=getchar();
    while(ch<'0'||ch>'9'){
        if(ch=='-')f=-1;
        ch=getchar();
    }
    while(ch>='0'&&ch<='9'){
        sum=(sum<<1)+(sum<<3)+ch-'0';
        ch=getchar();
    }
    return sum*f;
}
struct val_tree{
    int l,r,ma;
    #define ma(o) t[o].ma
}t[wx*4];
int pre[wx],a[wx],last[wx],b[wx];
int n,m,x,y;
void up(int o){
    ma(o)=max(ma(ls(o)),ma(rs(o)));
}
void build(int o,int l,int r){
    t[o].l=l;t[o].r=r;
    if(l==r){ma(o)=pre[l];return;}
    int mid=t[o].l+t[o].r>>1;
    if(l<=mid)build(ls(o),l,mid);
    if(r>mid)build(rs(o),mid+1,r);
    up(o);
}
int query(int o,int l,int r){
    if(l<=t[o].l&&t[o].r<=r){
        return ma(o);
    }
    int mid=t[o].l+t[o].r>>1;
    int maxn=0;
    if(l<=mid)maxn=max(maxn,query(ls(o),l,r));
    if(r>mid)maxn=max(maxn,query(rs(o),l,r));
    return maxn;
}
int aa[wx];
int main(){
    freopen("split.in","r",stdin);
    freopen("split.out","w",stdout);
    n=read();
    for(int i=1;i<=n;i++){
        aa[i]=read();
        b[i] = aa[i];
    }
    sort(b+1,b+1+n);
    for(int i = 1; i <= n; ++ i) a[i] = lower_bound(b + 1, b + 1 + n, aa[i]) - b;
    for(int i=1;i<=n;i++){
        pre[i]=last[a[i]];
        last[a[i]]=i;
    }
    build(1,1,n);
    m=read();
    for(int i=1;i<=m;i++){
        x=read();y=read();
        int ans=query(1,x,y);
        if(ans<x)printf("Boring\n");
        else printf("%lld\n",ans);
    }
    fclose(stdin);
    fclose(stdout);
    return 0;
} 

原文地址:https://www.cnblogs.com/wangxiaodai/p/9740969.html

时间: 2024-08-01 04:21:02

10-3国庆节第六场模拟赛题解的相关文章

10-4国庆节第七场模拟赛题解

10-4 国庆节第七场模拟赛题解 T1工厂 (factory) 水 #include<iostream> #include<cstdio> #define int long long using namespace std; inline int read(){ int sum=0,f=1; char ch=getchar(); while(ch<'0'||ch>'9'){ if(ch=='-')f=-1; ch=getchar(); } while(ch>='0

10-2国庆节第五场模拟赛题解

T1 seq: 序列2 (seq) Description 给定个长度为 n 的数列 {a},初始时数列中每个元素 a_i 都不大于 40.你可以在其上进行若干次操作.在一次操作中,你会选出相邻且相等的两个元素,并把他们合并成一个元素,新的元素值为 \((旧元素值+1)\). 请你找出,怎样的一系列操作可以让数列中的最大值变得尽可能地大?这个最大值是多少? Input 输入文件第一行一个正整数 n,表示数列的长度. 接下来一行 n 个不大于 40 的正整数,表示这个数列 {a }. Output

10月15日模拟赛题解

10月15日模拟赛题解 A 树 Description 给定一棵 \(n\) 个节点的树,每个节点有两个参数 \(a,~b\),对于每个节点,求子树中参数为 \(b\) 的所有节点的 \(a\) 之和 Limitations \(100\%\) \(1 \leq b \leq n \leq 10^5,~a \leq 1000\) \(60\%\) \(1 \leq b,n\leq 1000\) \(30\%\) \(1 \leq b, n \leq 10\) Solution 对于 \(30\%

lzoi模拟赛题解

A题:签到题原题:mtoi 联赛 的A题定位:真.签到题(普及B题或者提高d1A题)考点:选手的基本数学能力思维难度:提高-代码难度:普及A题 题解:80%:暴力枚举100%:注意到(a xor b)<=(a+b),于是把所有的数异或起来即可. B题:送分题原题:[多省省队联测]d2A:皮配定位:一道联赛d1B题,考察了选手的基本功.送了选手70分.把70%的2种做法扩展可以得到正解考点:多种背包dp,计数思维难度:提高代码难度:提高+ 前面的几个数据可以暴力枚举解决.50%的数据:考虑dp.设

计蒜课 八月模拟赛题解

看见机房有大佬上周写了上面的普及信心赛 于是我康了康 8月的提高组模拟赛 9月的还没开始qwq 真的 有点难 主要是我先打开了T2 我再次 对自己的数学产生了怀疑 我现在还是不会写T2 T1 又又又又都错题了 下次重建图 尽量写vector 都写 邻接表 变量差不多的容易搞混 我这个同学变又写错了 T1 :https://nanti.jisuanke.com/t/41086 题目大意就是 一个有向图 删一个点 把与他直接和间接 相连的点 删掉 然后 求删掉所有点的最小最大代价 : 为了避免这个环

10.06 国庆节第九场模拟赛

密钥(key) Description 在这个问题中,一个密钥是指一个长度为\(3n\)的二进制序列,其中\(n\)是正整数. 序列的每一位从左往右依次被编号为\(1\)到\(3n\) ,一个密钥的权值是指数字不同的相邻位的个数再加上\(1\) .比如: \(000\) 的权值是 \(1\), \(011010100\) 的权值是 \(7\). 密钥可以被修改.确切地说,你可以不断地进行下面的操作:任选两个相邻的位,然后同时将它们取反.例如,可以通过一次操作把 \(000\) 修改为 110 .

【一场模拟赛?】

JSH师兄来讲课,于是也弄了场比赛给我们做...被虐... A.简单的图论题 嗯求一个拓扑图中每个点的可达点数量,n=10000 嗯记忆化搜索,但是有可能搜到重复的点,于是我们不搜索可达点数量,转成搜索可达点集,这个可以用一个bool数组记录. 于是STL有某种东西叫做bitset(长知识 // Problem#: 14938 // Submission#: 3794800 // The source code is licensed under Creative Commons Attribu

2017.07.10【NOIP提高组】模拟赛B组

Summary 今天题目总体不是难,但是分数很低,只有100+10+30,其中第二题还是以前做过的,第一题设计数论,而且以前做过同一个类型的题目,比赛推了很长时间.第三题时以前做过的原题,是贪心没学好啊!方法也不够周到,数据看得不仔细. Problem T1 可见点数 题目大意 我更改了一下,但是求的东西是一样的.已知有n*n个人在一个n*n网络的格点上,有个人在(1,1)的位置,问他能看到多少个人的脸,不包括自己. 想法 已知一个定理,说得笼统一点,(x,y)和(x+a,y+b)点连一条边,其

CPPU程序设计训练营清明天梯模拟赛题解

感谢大家今天来做题 比赛地址:http://202.206.177.79/contest/8 由于博主比较菜,没做完所有题目,这里暂时仅提供前两部分的题解. 为了节约篇幅,题目及数据描述不再赘述,如有需求,请移步OJ查看. 感谢大家的辛苦付出,但是从这次比赛的结果来看,前行之路还非常非常漫长呐. 我寂寞的时候,会害怕踏出第一步.不会想到要去做什么事,所以,可能没有发觉很多很多的东西吧.--<夏目友人帐> 第一阶段 L1-1 天梯赛座位分配 (20分) 通过率:2.56% \(\;\;\) 通过