2024/12/15 模拟赛 普及组(B)

总体而言还算是比较简单的一场模拟赛(但我是废物,被小孩哥直接拉爆了)。

T1 坦克

Describe

众所周知,甜所妹妹很可爱。 甜所妹妹有 \(n\) 辆相同的坦克,你有 \(m\) 辆相同的坦克,但你们两人的坦克是不同的。甜所妹妹的坦克打爆你的一辆坦克需要 \(a\) 炮,你的坦克打爆甜所妹妹的一辆坦克需要 \(b\) 炮。

甜所妹妹和你在一个空旷的地图上对战,每个回合每辆坦克可以打出 1 炮。甜所妹妹和你的坦克都会同时以最优策略。每个回合中,首先甜所妹妹和你都会向自己的每一辆坦克分别下达命令,确定该坦克本回合攻击对方的哪一辆坦克;然后双方所有坦克同时开炮,所有炮弹的飞行时间都相同,即本回合被命中的坦克都是同时被命中的。如果某辆坦克被打爆了,那么它在以后的回合中将无法再进行攻击。你们会一直让坦克互相开炮,直到某一方所有坦克被全部打爆为止。

甜所妹妹想知道把你的坦克打光后,自己还剩多少坦克。如果她打不过你,输出 " 0 "(不含引号) 。你和甜所妹妹玩了 \(T\) 轮游戏,也就是说本题有 \(T\) 组测试数据。

Input

第一行输入一个正整数 \(T\) ,表示数据组数。 对于每一组测试数据,一共输入一行四个正整数,分别为 \(n,m,a,b\),表示双方的坦克数量和每辆坦克的生命值。

输入样例:

5
10 2 15 1
2 1 2 1
2 1 3 1
10 8 4 4
10 8 1145141919 1145141919

Output

对于每一组数据,输出一个非负整数,表示答案。

输出样例:

3
1
0
5
6

Solution

首先,两方最优解肯定都是优先集火对方血量最低的坦克。

由于要记录坦克数量和某一个的血量太过复杂,所以我们选择记录两方的总血量。然后通过总血量整出,取模等操作,计算可以对对方造成的伤害。

然后可以写为优化一下,就是一次打多轮。因为一方的单个血量可能太大,一轮一轮太慢了,所以我们可以一次打多轮,直接把这个残血的打掉。

Code

#include<bits/stdc++.h>
#define int long long
using namespace std;
int T,n,m,a,b;
void work(){
	cin>>n>>m>>a>>b;
	if(m>=n&&a>=b){cout<<"0\n";return;}
	int x=m*a,y=n*b;
	while(x>0&&y>0){
		n=ceil(y*1.0/b),m=ceil(x*1.0/a);
		int t=max(min(ceil((x%a)*1.0/n),ceil((y%b)*1.0/m)),1.0);
		x-=n*t;
		y-=m*t;
	}
	cout<<max(0ll,(y+b-1)/b)<<"\n";
}
signed main(){
	ios::sync_with_stdio(0);
	cin.tie(0),cout.tie(0);
	cin>>T;
	while(T--)work();
	return 0;
}

T2 火柴棍

Describe

你有 \(n\) 个火柴棍,问用完所有的火柴棍可以摆出的最小的数(不含前导零,但可以为 0 )是多少?

由于你能摆出的数字可能非常大,请输出对 998244853 取模的结果。

提示:0~9 所使用的火柴棍数量分别为:6,2,5,5,4,5,6,3,7,6

Input

第一行一个整数 \(T\) 表示数据组数。

对于每组数据输入一行一个整数 \(n\)

输入样例:

2
7
8

Output

对于每组数据输出一行一个整数,表示答案。

输出样例:

8
10

Solution

首先我们考虑,想要让一个数最小,首先要让它的位数最小,所以我们选择先将所有的位置都填上 \(8\)(因为 \(8\) 所需的火柴棍最多),然后在根据剩下的火柴棍数量安排最开始那几位的数字即可。

但是他要让我们对 998244853 取模,一位一位边加数字边取模太慢了。我们想到后面的很多位都是 8,相当于 \((8\times 11···1)\mod 998244853\),前面只有几位是别的数字,相当于 \((x\times 10^n)\mod 998244853\),所以我们考虑将 \(11···1\mod 998244853\)\(10^n\mod 998244853\) 都预处理出来,最后统一乘上即可。

Code

#include<bits/stdc++.h>
#define int long long
#define N 15005
#define mod 998244853
using namespace std;
int T,n,a[N],b[N];
signed main(){
	ios::sync_with_stdio(0);
	cin.tie(0),cout.tie(0);
	b[0]=1;
	for(int i=1;i<=15000;i++)a[i]=(a[i-1]*10+1)%mod,b[i]=b[i-1]*10%mod;
	cin>>T;
	while(T--){
		cin>>n;
		if(n<7){
			if(n==2)cout<<"1\n";
			if(n==5)cout<<"2\n";
			if(n==4)cout<<"4\n";
			if(n==6)cout<<"0\n";
			if(n==3)cout<<"7\n";
			continue;
		}
		int num=n/7,sum=n%7,x=0;
		if(sum==1)--num,x=10;
		if(sum==2)x=1;
		if(sum==3){
			if(num>1)num-=2,x=200;
			else --num,x=22;
		}
		if(sum==4)--num,x=20;
		if(sum==5)x=2;
		if(sum==6)x=6;
		cout<<(x*b[num]%mod+8*a[num]%mod)%mod<<"\n";
	}
	return 0;
}

T3 子集计数

Describe

给定一个长度为 \(n\) 的非负整数序列 \((a_1,a_2,⋯,a_n)\),对每个 \(i\in [1,n]\) 求有多少个子序列 \((a_{j_1},a_{j_2},⋯,a_{j_k})\),满足 \(k≥1,1≤j_1<j_2<⋯<j_k=i\),且将序列中的数视为二进制表示的集合后 \(a_{j_1}\subseteq a_{j_2}\subseteq ⋯\subseteq a_{j_k}\),答案对 \(998244353\) 取模。

Input

第一行一个整数 \(n\)
接下来 \(n\) 行,每行一个非负整数,依次是 \(a_1,a_2,⋯,a_n\)

输入样例:

5
1
2
3
4
5

Output

输出 \(n\) 行,每行 \(1\) 个整数,依次是 \(i=1,2,⋯,n\) 的答案\(\mod 998244353\) 后的结果。

输出样例;

1
1
3
1
3

Solution

20pts

\(O(n^2)\) 暴力 DP 即可。

\(f_i\) 表示以 \(a_i\) 结尾的序列数量,若 \(j<i\)\(a_j\subseteq a_i\),则 \(f_j\to f_i\),初始值全部为 \(1\)

有个小技巧是 \(x\vee y=x\)\(y\subseteq x\) 是等价的。

#include<bits/stdc++.h>
#define mod 998244353
#define N 100005
using namespace std;
int n,a[N],dp[N];
int main(){
	cin>>n;
	for(int i=1;i<=n;i++)cin>>a[i];
	for(int i=1;i<=n;i++){
		dp[i]=1;
		for(int j=1;j<i;j++)if((a[i]|a[j])==a[i])(dp[i]+=dp[j])%=mod;
		cout<<dp[i]<<"\n";
	}
	return 0;
}

30pts

法1:

\(g_s\) 是所有 \(a_j=s\)\(f_s\) 之和,转移时枚举 \(s\subseteq a_i\)\(g_s\to f_i\),转以后 \(f_i\to g_{a_i}\)

#include<bits/stdc++.h>
#define int long long
#define N 100005
#define mod 998244353
using namespace std;
int n,x,a[N],dp[N],g[N];
signed main(){
	ios::sync_with_stdio(0);
	cin.tie(0),cout.tie(0);
	cin>>n;
	for(int i=1;i<=n;i++){
		cin>>a[i];
		dp[i]=1;
		for(int j=0;j<=a[i];j++){
			if((j|a[i])==a[i])(dp[i]+=g[j])%=mod;
		}
		(g[a[i]]+=dp[i])%=mod;
		cout<<dp[i]<<"\n";
	}
	return 0;
}

法2:

\(g_s\) 是所有 \(a_j\subseteq s\)\(f_j\) 之和,转移时 \(g_{a_j}\to f_i\),转以后枚举 \(s\) 满足 \(a_i\subseteq s\)\(f_i\to g_s\)

#include<bits/stdc++.h>
#define int long long
#define N 100005
#define mod 998244353
using namespace std;
int n,x,a[N],dp[N],g[N],mx;
signed main(){
	ios::sync_with_stdio(0);
	cin.tie(0),cout.tie(0);
	cin>>n;
	for(int i=1;i<=n;i++)cin>>a[i],mx=max(mx,a[i]);
	for(int i=1;i<=n;i++){
		(dp[i]=g[a[i]]+1)%=mod;
		cout<<dp[i]<<"\n";
		for(int j=a[i];j<=mx;j++){
			if((a[i]|j)==j)(g[j]+=dp[i])%=mod;
		}
	}
	return 0;
}

时间复杂度都为 \(O(2^m))\)\(m\) 表示 \(\max(\log a_i))\)

100pts

将 30pts 的两种做法结合一下,将 \(m\le 16\) 拆分成低8位和高8位。低8位采用法1,高8位采用法2。

\(g_{s,t}\) 表示 \(a_i>>8\subseteq s\)\(a_i\wedge 255=t\) 的所有 \(f_i\) 之和。

转移时枚举 \(t\subseteq a_i\wedge 255\),令 \(s=a_i>>8\)\(g_{s,t}\to f_i\)

转移后枚举 \(s\) 满足 \(a_i>>8\subseteq s\),令 \(t=a_i\wedge 255\)\(f_i\to g_{s,t}\)

(这种高 \(m/2\) 位和低 \(m/2\) 位的小技巧值得记录一下)

#include<bits/stdc++.h>
#define int long long
#define N 100005
#define mod 998244353
using namespace std;
int n,x,a[N],dp[N],g[300][300],mx;
signed main(){
	ios::sync_with_stdio(0);
	cin.tie(0),cout.tie(0);
	cin>>n;
	for(int i=1;i<=n;i++){
		cin>>a[i],mx=max(mx,(a[i]>>8));
	}
	for(int i=1;i<=n;i++){
		dp[i]=1;
		int x=a[i]&255,y=a[i]>>8;
		for(int j=0;j<=x;j++){
			if((j|x)==x)(dp[i]+=g[y][j])%=mod;
		}
		cout<<dp[i]<<"\n";
		for(int j=y;j<=mx;j++){
			if((j|y)==j)(g[j][x]+=dp[i])%=mod;
		}
	}
	return 0;
}

T4 排列

Describe

对于序列长度为 \(n\) 的自然数序列 \({a}\),若序列中的每个数都为 \(1\)\(n\) 的自然数且互不相同,则称序列 \({a}\) 为好的序列。

给定自然数 \(n\),我们称所有长度为 \(n\) 的好的序列 \((p1,p2,…,pn)\) 中满足序列中任意相邻两数以及尾首两数产生的 \(n\) 对有序二元对中,前面的数字模后面的数字的结果不超过 \(2\),为完美的序列。

现在需要你统计所有长度为 \(n\) 的好的序列中,有多少个是完美的序列。

输出满足条件的序列个数对 \(10^9+7\) 取模的结果。

Input

一行一个整数 \(n\) 代表序列的长度。

输入样例:

4

Output

一行一个整数代表完美的序列的数量。

输出样例:

16

Solution

打表可以发现(也不一定必须要),将任意一条完美的序列首尾相连后再重新断开,一定会有一种是两个递减的序列(当然序列可以为空)拼在一起,而且它们的最后一个一定是 \(1\)\(2\)

因为在 \(x\)\(y\) 前面的情况下,\(x<y\),那么 \(x\) 就必须要小于等于 \(2\),这样才能保证取模符合要求。也就是说,只有 \(1\)\(2\) 才能在比它们大的数的前面,其他必须是递减。

所以题目也就被转化成从前往后依次把数放进前后两个集合中,集合内部的顺序不用管,一定是递减;两个集合的前后顺序也不用管,因为谁前谁后都可以,只不过是断环的位置不同。

最后统计答案的时候,要将这个方案数乘 \(n\),因为我们只管了前后两个集合,把他们拼在一起一共会有 \(n\) 种断环的方式。

我们设 \(dp_{i,j}\) 表示的是后面的集合已经选到了 \(i\)(其实也就是最大值为 \(i\)),前面的集合选到了 \(j\)。保证 \(i>j\),且 \(1~n\) 都选完了。

dp转移:

  • \(dp_{i,j}\to dp_{i+1,j}\) 这个必然满足,因为 \(i+1\mod i=1\)
  • \(dp_{i,j}\to dp_{i+1,i}(满足 i+1\mod j\le 2)\) 这个有些难以理解。可以认为是将 \(i+1\) 加到了前面,因为要保证 \(i>j\),所以将前后两个集合顺序调换了一下。

复杂度 \(O(n^2)\)

因为所有 \(dp_i\) 都只与 \(dp_{i-1}\) 有关,所以可以将第一维滚掉。

这样第一种转移就可以不管了。再看第二种转移,要求 \(i+1\mod j\le 2\),所以一共有三种状态,取模后等于 \(-1\)(就是 \(2\)),等于 \(0\),等于 \(1\),也就是 \(j\) 整除 \(i-1\)\(i\),或 \(i+1\),便利的时候从这三个开始,每次加 \(j\)

Code

#include<bits/stdc++.h>
#define int long long
#define N 1000005
#define mod 1000000007
using namespace std;
int n,dp[N],ans;
signed main() {
	cin>>n;
	dp[1]=1;
	for(int j=1;j<n;j++){
		if(j<=2){
			for(int i=j+1;i<n;i++)(dp[i]+=dp[j])%=mod;
		}
		else{
			for(int k=-1;k<=1;k++){
				for(int i=j+k;i<n;i+=j){
					if(j<i)(dp[i]+=dp[j])%=mod;
				}
			}
		}
	}
	for(int i=1;i<n;i++){
		(ans+=dp[i])%=mod;
	}
	cout<<ans*n%mod;
	return 0;
}
posted @ 2024-12-22 15:27  WDY_Hodur  阅读(35)  评论(0)    收藏  举报