2020.05.05日常总结——NOI ONLINE 2019 ROUND 2

T1 \color{green}{\texttt{T1}} T1

【题意】: \color{blue}{\texttt{【题意】:}} 【题意】:

2020.05.05日常总结——NOI ONLINE 2019 ROUND 2_第1张图片

2020.05.05日常总结——NOI ONLINE 2019 ROUND 2_第2张图片
2020.05.05日常总结——NOI ONLINE 2019 ROUND 2_第3张图片

【思路】: \color{blue}{\texttt{【思路】:}} 【思路】: 一个简单的贪心,回忆一下现实:当选择任何的一个东西的代价都相同时,我们肯定会优先地选择效益(价值)最好的一个(相信大家都是这样的吧)。

回到本题,因为选择任何一个魔法的代价都是一样的(都是 1 1 1),我们可以贪心地优先选择那些数值大的魔法。为什么呢?

假设当前魔法值为 t t t,则 Sisyphus 爬到 t t t 位置时会掉落到原点,然后他会用爬 n n n 的距离到达山顶。则他爬的路程就是 n + t n+t n+t。因为他的速度不变,则时间和路程成正比。所以我们需要让 n + t n+t n+t 尽可能的大,又因为 n n n 不变,所以我们只需让 t t t 尽可能大即可。

故,我们只需贪心地优先选择位置值大的魔法即可解决本题。

但是,每次枚举会 TLE,我们可以用前缀和的方法预处理出在使用 k k k 个魔法的前提下,可以让 Sisyphus 走多远的 距离 \color{red}{\texttt{距离}} 距离

为什么是距离而不是时间?因为时间可能是小数,但距离一定是整数。注意需要随时把输入的时间转化为路程,否则会 WA

【代码】: \color{blue}{\texttt{【代码】:}} 【代码】:

#define ll long long
const int N=2e5+100;
ll a[N],s[N],n,q,L,v;
bool cmp(ll t1,ll t2){
	return t1>t2;
}
int main(){
//	freopen("endless.in","r",stdin);
//	freopen("endless.out","w",stdout);
	n=read();L=read();v=read();
	for(int i=1;i<=n;i++)
		a[i]=read();
	sort(a+1,a+n+1,cmp);
	s[0]=L;//注意初始化! 
	for(int i=1;i<=n;i++)
		s[i]=s[i-1]+a[i];
	q=read();//输入询问的数量 
	for(int i=1;i<=q;i++){
		register ll t=read();
		if (t*v<L) printf("0\n");
		else if (t*v>=s[n]) printf("-1\n");
		else{
			int l=upper_bound(s+1,s+n+1,t*v)-s;
			printf("%d\n",l);
		}
	}
	return 0;
}

T2 \color{green}{\texttt{T2}} T2

【题意】: \color{blue}{\texttt{【题意】:}} 【题意】:

2020.05.05日常总结——NOI ONLINE 2019 ROUND 2_第4张图片

2020.05.05日常总结——NOI ONLINE 2019 ROUND 2_第5张图片

2020.05.05日常总结——NOI ONLINE 2019 ROUND 2_第6张图片

2020.05.05日常总结——NOI ONLINE 2019 ROUND 2_第7张图片

【思路】: \color{blue}{\texttt{【思路】:}} 【思路】: 本思路可能只能通过洛谷的民间数据……

我们一看题目,就知道这题肯定要用到搜索算法。数据提示我们,我们要用 bfs 算法。

我们把隐身和瞬移的 剩余 \color{red}{\texttt{剩余}} 剩余次数也写入状态中,则我们可以一遍 bfs 求出这两个量。

每次判断一个点是否是士兵的观察点太慢,于是我们可以提前把所有的点是否可以被士兵观察到这么一个状态计算出来,存入数组中, O ( 1 ) O(1) O(1) 判断。

代码可以通过洛谷的民间数据(不能通过的时候告诉我)。建议使用标程的方法。

【代码】: \color{blue}{\texttt{【代码】:}} 【代码】:

struct node{
	int x,y,step,c1,c2;
};bool b[360][360][17][17];
int a[360][360],n,m,c1,c2,d;
inline bool Check(int x,int y){
	return !(x<1||x>n||y<1||y>m);
}
inline bool check(int x,int y){
	if (!Check(x,y)) return false;
	if (a[x][y]>0) return false;
	return true;
}
const int dx[]={1,0,-1,0};int bx;
const int dy[]={0,1,0,-1};int by;
int ans_step,ans_c1,ans_c2,ex,ey;
const int Dx[]={0,0,1,1,1,-1,-1,-1};
const int Dy[]={1,-1,0,1,-1,0,1,-1};
inline void bfs(int bx,int by){
	node nxt,now=(node){bx,by,0,c1,c2};
	queue<node> q;q.push(now);
	b[bx][by][c1][c2]=true;
	while (!q.empty()){
		now=q.front();q.pop();
		if (now.step>ans_step) break;
		if (now.x==ex&&now.y==ey){
			ans_step=now.step;//记录步数 
			int u1=c1-now.c1,u2=c2-now.c2;
			if (u1+u2<ans_c1+ans_c2){
				ans_c1=u1;ans_c2=u2;
			}
			else if (u1<ans_c1){
				ans_c1=u1;
				ans_c2=u2;
			}
		}
		for(int i=0;i<8;i++){
			nxt.x=now.x+Dx[i];
			nxt.y=now.y+Dy[i];
			nxt.step=now.step+1;
			if (!check(nxt.x,nxt.y))
				continue;//越界错误 
			if (a[nxt.x][nxt.y]==-1)
				nxt.c1=now.c1-1;
			else nxt.c1=now.c1;
			nxt.c2=now.c2;//计算新状态 
			if (nxt.c1<0) continue;//不符题意 
			if (b[nxt.x][nxt.y][nxt.c1][nxt.c2])
				continue;//已经走过,不再走另一次 
			b[nxt.x][nxt.y][nxt.c1][nxt.c2]=1;
			q.push(nxt);
		}
		for(int i=0;i<4;i++){
			nxt.x=now.x+dx[i]*d;
			nxt.y=now.y+dy[i]*d;
			nxt.step=now.step+1;
			if (!check(nxt.x,nxt.y))
				continue;//越界错误 
			if (a[nxt.x][nxt.y]==-1)
				nxt.c1=now.c1-1;
			else nxt.c1=now.c1;
			nxt.c2=now.c2-1;//计算新状态 
			if (nxt.c2<0||nxt.c1<0) continue;
			if (b[nxt.x][nxt.y][nxt.c1][nxt.c2])
				continue;//已经走过,不再走另一次 
			b[nxt.x][nxt.y][nxt.c1][nxt.c2]=1;
			q.push(nxt);
		}
	}
}
const int inf=0x3f3f3f3f;
int main(){
//	freopen("bandit.in","r",stdin);
//	freopen("bandit.out","w",stdout);
	memset(b,false,sizeof(b));
	cin>>n>>m>>c1>>c2>>d;
	for(int i=1;i<=n;i++)
		for(int j=1;j<=m;j++){
			char c;cin>>c;
			if (isdigit(c)){
				register int t=c-48;
				while (c=getchar()){
					if (!isdigit(c)) break;
					t=t*10+c-48;
				}
				a[i][j]=t;//不能走 
			}
			else{
				if (c=='S'){bx=i,by=j;a[i][j]=0;}
				else if (c=='T'){ex=i,ey=j;a[i][j]=0;}
				else a[i][j]=0;
			}
		}
	for(int i=1;i<=n;i++)
		for(int j=1;j<=m;j++)
			if (a[i][j]>0)
				for(int k=0;k<a[i][j];k++)
					for(int l=0;k+l<a[i][j];l++){
						if (check(i-k,j-l))
							a[i-k][j-l]=-1;
						if (check(i+k,j-l))
							a[i+k][j-l]=-1;
						if (check(i-k,j+l))
							a[i-k][j+l]=-1;
						if (check(i+k,j+l))
							a[i+k][j+l]=-1;
					}
	ans_step=inf;ans_c1=inf;
	ans_c2=inf;bfs(bx,by);
	if (ans_step==inf) printf("-1");
	else{
		cout<<ans_step<<" ";
		cout<<ans_c1<<" ";
		cout<<ans_c2;
	}
	return 0;
}

T3 \color{green}{\texttt{T3}} T3

【题意】: \color{blue}{\texttt{【题意】:}} 【题意】:

2020.05.05日常总结——NOI ONLINE 2019 ROUND 2_第8张图片

2020.05.05日常总结——NOI ONLINE 2019 ROUND 2_第9张图片

2020.05.05日常总结——NOI ONLINE 2019 ROUND 2_第10张图片

【思路】: \color{blue}{\texttt{【思路】:}} 【思路】: 先考虑 n n n 栋楼,高度限制为 m m m,且 h i ≥ h i − 1 ( 1 < i ≤ n ) h_i \geq h_{i-1}(1hihi1(1<in) 时的方案数。

这个东西我们可以看做是把 n n n 个相同的小球放入 m m m 个小盒中,方案数为 ( n + m − 1 m − 1 ) \binom {n+m-1} {m-1} (m1n+m1)

因为单调不增和单调不减本质一样(把其中一个反回来就是另外一个),所以该公式一样使用于单调不增中。

题目未说 x , y x,y x,y 是否在 n n n 的两侧,分类讨论:

  • x , y x,y x,y n n n 的同侧时,把 [ x , y ] [x,y] [x,y] 看做一栋楼,则该册总方案数为 ( n + x − y + m − 1 m − 1 ) \binom{n+x-y+m-1}{m-1} (m1n+xy+m1)。另一侧的方案数为 ( n + m − 1 m − 1 ) \binom{n+m-1}{m-1} (m1n+m1),二者相乘即为答案。

  • x , y x,y x,y n n n 的两侧时,枚举 h x h_x hx(即 h y h_y hy)。

    x < y xx<y,把整个图形分成四块:

    • 1 → x 1\to x 1x 栋楼
    • x + 1 → n x+1\to n x+1n 栋楼
    • n + 1 → y n+1\to y n+1y 栋楼
    • y + 1 → 2 n y+1 \to 2n y+12n 栋楼

    分开求其方案数相乘即为 h x h_x hx 固定时的结果。把每种情况中编号最小的楼看做 1 1 1 号楼即可方便的求出答案。

【代码】: \color{blue}{\texttt{【代码】:}} 【代码】:

const int N=2e5+100;
const int mod=998244353;
int inv[N],fac[N],n,m,x,y,ans;
inline int ksm(int a,int b){
	register int ret=1;//注意初始化 
	while (b){//b还没有被分完,继续 
		if (b&1) ret=1ll*ret*a%mod;
		a=1ll*a*a%mod;b>>=1;
	}//x>>=a:二进制运算,等于x/=2^a 
	return ret;//ret:a的b次幂%mod 
}//快速幂的模板,注意实时取模和1ll 
inline void init_fac_and_inv(){
	fac[0]=1;//求i(1<=i<=n+m)的阶乘 
	for(int i=1;i<=n+m;i++)//正序 
		fac[i]=1ll*fac[i-1]*i%mod;
	inv[n+m]=ksm(fac[n+m],mod-2);
	for(int i=n+m-1;i>=0;i--)//倒序 
		inv[i]=1ll*inv[i+1]*(i+1)%mod;
//	后三行是求i(1<=i<=n+m)的阶乘的逆元 
//	注意第二个for的循环顺序必须是倒序! 
}//O(n+m)预处理出i(1<=i<=n+m)的阶乘和逆元 
inline int f(int n,int m){//求C(n+m-1,m-1) 
	return 1ll*fac[n+m-1]*inv[m-1]%mod*inv[n]%mod;
}//把n栋楼分m种不同高度的方案数,相当于n个球m个楼 
int main(){
	scanf("%d%d%d%d",&m,&n,&x,&y);
	init_fac_and_inv();//别忘了调用 
	if (x>y) swap(x,y);//保证y>=x 
	if (!((x<=n)^(y<=n))){//同侧 
		ans=1ll*f(n,m)*f(n+x-y,m)%mod;
	}
	else{//否则就是在异侧,需要枚举高度i 
		for(int i=1;i<=m;i++){//hx=hy=i
			int tmp=1ll*f(x-1,i)*f(n-x,m-i+1)%mod;
			tmp=1ll*tmp*f(y-n-1,m-i+1)%mod*f(2*n-y,i)%mod;
			ans=(1ll*ans+tmp)%mod;//累加方案数(即答案) 
		}
	}
	printf("%d",ans);
	return 0;
}

你可能感兴趣的:(NOI,真题)