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;
} 
posted @ 2018-10-03 21:37  _王小呆  阅读(232)  评论(0编辑  收藏  举报