UTPC Contest 01-26-24 Div. 2 (Beginner) (D,E,F,G,H)

说是Div 2,实际上没有特别难的题,水平可能也就是 cf round div 3 ~ 4,不过后面的题挺有意思的(就是题意不是很严谨,容易读假题理解错),有时间可以做一做。

比赛链接


D. The World Turned Upside Down

题意:

世界天翻地覆!

在这个角色转换的过程中,我们需要你的帮助来写一个问题!

问题是这样的我从数字 1 开始。然后,我可以进行一系列运算,其中的运算就是用我当前的数字乘以任意正整数 k k k 。例如,我可以选择 k = 3 k = 3 k=3 ,得到数字 1 × 3 = 3 1 \times 3 = 3 1×3=3 。然后选择新的 k = 4 k = 4 k=4 ,得到数字 3 × 4 = 12 3 \times 4 = 12 3×4=12 。然后选择 k = 2 k = 2 k=2 ,得到数字 12 × 2 = 24 12 \times 2 = 24 12×2=24 。这样就得到了数字 [ 1 , 3 , 12 , 24 ] [1, 3, 12, 24] [1,3,12,24] 序列。

我的老板(班尼特)向我提供了一份他最喜欢的数字 N N N 的列表;在我的问题中,我需要找到包含尽可能多的他最喜欢的数字的序列。

如果我创建了一个完美的序列,我可以包含多少个班尼特最喜欢的数字?

思路:

注意老板给的是列表,而不是序列,是没有顺序的,你构造的序列只要有这个数就可以了,不需要顺序都一样。构造的序列一定是递增的,而且每个数和前一个数是倍数关系。所以我们对列表排一下序,然后跑最长上升子序列即可(这里的上升得是每个数和前一个数是倍数关系的序列)。

code:

#include <iostream>
#include <cstdio>
#include <algorithm>
using namespace std;
typedef long long ll;
const int maxn=1e3+5;

int n;
ll a[maxn];
int dp[maxn];

int main(){
	cin>>n;
	for(int i=1;i<=n;i++)
		cin>>a[i],dp[i]=1;
	sort(a+1,a+n+1);
	for(int i=1;i<=n;i++)
		for(int j=1;j<i;j++)
			if(a[i]%a[j]==0)
				dp[i]=max(dp[i],dp[j]+1);
	
	int ans=0;
	for(int i=1;i<=n;i++)
		ans=max(ans,dp[i]);
	cout<<ans;
	
	return 0;
} 

E. Up Down Matching

题意:

上城区将举办一年一度的 “下城上” 舞会,邀请下城的邻居参加,共同庆祝上下城和谐共处。舞会开始时间如下:

上城人和下城人按一定顺序排成一排。接下来,连续选出一部分人参加舞蹈。本着相互交融的精神,每个人都必须能够与另一个城市的人配对。也就是说,被选中的部分必须包含相同数量的上城人和下城人。

舞蹈组织者希望今年的舞蹈规模越大越好。考虑到上城人和下城人排队的顺序,你能帮他们确定每个人可以选择参加舞会的最长人数段吗?

思路:

算是比较经典的一道题了。如果我们把上城区人看作 1 1 1,下城区人看作 − 1 -1 1,那么我们其实就是要找区间和为 0 0 0 的所有区间中最大的长度。我们用前缀和维护这个 1 1 1 − 1 -1 1 的序列,枚举右端点 r r r,其实就是找前缀和与它相等的最前面的满足条件的 l l l 端点。

找东西是搜索树的强项,找前缀和 看它的最靠前的位置,用map。

code:

#include <iostream>
#include <cstdio>
#include <map>
using namespace std;
const int maxn=2e5+5;

int T,n,a[maxn];

int main(){
	cin>>T;
	while(T--){
		cin>>n;
		char tmp;
		map<int,int> s;//前缀和,下标 
		int ans=0;
		s[0]=0;
		for(int i=1;i<=n;i++){
			cin>>tmp;
			a[i]=a[i-1]+((tmp=='U')?1:-1);
			if(s.find(a[i])!=s.end())
				ans=max(ans,i-s[a[i]]);
			else s[a[i]]=i;
		}
		cout<<ans<<endl;
	}
	return 0;
}

F. Down Up Disco

题意:

多玛斯在达斯廷市中心拥有一家迪斯科俱乐部。他的舞池是一个由发光瓷砖组成的 N × M N \times M N×M 网格,每块瓷砖上都站着一个人。霍马斯设计的舞池有一个独特的怪癖。多马斯可以在 ( row = R , column = C ) (\text{row} = R, \text{column} = C) (row=R,column=C) 处选择一块瓷砖,并把 ( row = i , column = j ) (\text{row} = i, \text{column} = j) (row=i,column=j) i ≤ R i \leq R iR j ≤ C j \leq C jC 处扭转所有瓷砖的重力方向。多玛斯将整个过程定义为舞池中的一次 “逆转”。如果在这样一块瓷砖上的人一开始是 right-side up 站立,那么他们就会被颠倒过来。同样,如果他们是 upside down 的,他们也会被翻转成 right-side up。

由于迪斯科俱乐部即将结束营业,所以多马斯需要在舞池中的每个人离开之前将他们翻转到 right-side up 的位置。Dhomas 希望尽快完成这项任务,因此他要求您找出将舞池中的每个人都翻到 right-side up 所需的最少翻转次数。

思路:

不知道咋翻译,right-side up就叫上,upside down就叫下吧。稍微手玩一下很快就能发现一个规律:就是对一个点 ( x , y ) (x,y) (x,y)

  1. i ≥ x , j ≥ y i \ge x,j\ge y ix,jy 的位置,如果有一次操作,这个点就会被翻转一次。
  2. 对其他的位置,无论有几次操作,都影响不到这个点。

如果 i ≥ x , j ≥ y i \ge x,j\ge y ix,jy 的位置所有点经过一定次数操作已经被全部归位,但是这个点 ( x , y ) (x,y) (x,y) 现在是朝下的话,那么这个点就一定得使用一次操作,因为其他位置都无法影响到它。

所以我们可以维护一个“前”缀和,对前缀和数组 c n t [ i ] [ j ] cnt[i][j] cnt[i][j],它表示 x ≤ i ≤ n , y ≤ j ≤ m x\le i \le n,y\le j\le m xin,yjm 区域使用的操作总数。一边计算前缀和,一边判断某个位置是否要进行一次操作。最后得到的 c n t [ 1 ] [ 1 ] cnt[1][1] cnt[1][1] 即为所求。

code:

#include <iostream>
#include <cstdio>
using namespace std;
const int maxn=3005;

int n,m;
char mp[maxn][maxn];
int cnt[maxn][maxn];

int main(){
	scanf("%d%d",&n,&m);
	for(int i=1;i<=n;i++)
		scanf("%s",mp[i]+1);//不用字符读入会超时
	
	for(int i=n;i>=1;i--)
		for(int j=m;j>=1;j--){
			cnt[i][j]=cnt[i+1][j]+cnt[i][j+1]-cnt[i+1][j+1];
			cnt[i][j]+=cnt[i][j]&1^(mp[i][j]=='1');
		}
	cout<<cnt[1][1];
	return 0;
}

G. Dinnerbone and Array

题意:

今天是 Dinnerbone 的生日!作为生日礼物,Dinnerbone 的好朋友 Grumm 决定送给 Dinnerbone 一个长度为 N   ( 1 ≤ N ≤ 15 ) N\ (1\leq N \leq 15) N (1N15) 的数组 A A A 。这个数组对 Dinnerbone 来说非常特别,因为每个数组都有 ∣ A i ∣ ≤ 1 0 4 \lvert{A_i}\rvert\leq 10^{4} Ai104 。考虑到 Dinnerbone 可以选择数组的任意一个严格子集 S S S ,请计算 S S S 的最大值和最小值。

( ∣ S ∣ ⋅ ( − 1 ) ∣ S ∣ ⋅ ∑ x ∈ S x 3 ) \left(|S|\cdot (-1)^{|S|}\cdot \sum_{x \in S} x^3\right) (S(1)SxSx3)

思路:

啊?G题是个爆搜?这下汗流浃背了,因为 N N N 就只有 15,所以搜就完了,都不需要剪枝。注意上面说了是严格子集(也就是真子集),被这东西绊了半个小时。

code:

#include <iostream>
#include <cstdio>
#include <algorithm>
#include <set>
using namespace std;
typedef long long ll;
const int maxn=5e5+5;
const ll inf=1e18;

int T,n;
ll a[maxn];
ll maxx=-1e18,minn=1e18;

void dfs(int i,ll x,int k){//选第i个,当前数为x,选了k个 
	if(i>n){
		if(k<n){
			ll t=k*((k&1)?-1:1)*x;
			maxx=max(maxx,t);
			minn=min(minn,t);
		}
		return;
	}
	dfs(i+1,x+a[i],k+1);
	dfs(i+1,x,k);
}

int main(){
	cin>>T;
	while(T--){
		cin>>n;
		for(int i=1,t;i<=n;i++){
			cin>>t;
			a[i]=1ll*t*t*t;
		}
		maxx=-inf;
		minn=inf;
		dfs(1,0,0);
		
		cout<<minn<<" "<<maxx<<endl;
	}
	return 0;
}

H. Australian Solitaire

题意:

亚历克斯喜欢玩一种纸牌游戏–澳大利亚接龙。这种游戏使用一副特殊的扑克牌,其中有 4 张 A 和 100 张其他面值的扑克牌 (king、queen…2)。这副扑克牌没有花色。在澳大利亚接龙游戏中,玩家将 N N N ( 1 ≤ N ≤ 20 ) (1 \leq N \leq 20) (1N20) 张牌排成一行,不得替换。

如果一串牌符合以下标准,则视为 u p s i d e − d o w n upside-down upsidedown

  1. 第一张牌可以是任何等级。
  2. 后面任何一张牌的等级必须严格大于前一张牌的等级,或者是一张A。

牌的等级从小到大依次是 A、2、3、…K。有多少个由 N N N 张牌组成的 u p s i d e − d o w n upside-down upsidedown 序列?如果序列中的每一张牌的面值/等级都相同,那么这两个序列就被认为是相同的。

输出您的答案,模数为 1 0 9 + 7 10^9 + 7 109+7

思路:

容易想到除了最开头的那个序列。后面可以拆成一块一块的区间,区间内是开头是一张A,之后在 [ 2 , 13 ] [2,13] [2,13] 上递增的序列,还能算出长度为 l e n len len 的序列有 C 12 l e n − 1 C_{12}^{len-1} C12len1 种可能(第一个位置放A,然后在剩下的12张牌中选 l e n − 1 len-1 len1 个按顺序放好)可以预处理出 C 12 x C_{12}^{x} C12x (或者直接打表)。

由于一共只有4张A,所以设置 d p [ i ] [ j ] dp[i][j] dp[i][j] 表示长为 i i i 个序列,使用了 j j j A A A 的可能数。那么对于一个长为 i i i 的序列,我们可以在后面接上一个长为 l e n len len 的序列,长为 i + l e n i+len i+len 的序列可以加上 d p [ i ] [ j ] ∗ C 12 l e n − 1 dp[i][j]*C^{len-1}_{12} dp[i][j]C12len1 的可能性,即 c n t [ i + l e n ] [ j + 1 ] + = c n t [ i ] [ j ] ∗ C [ l e n − 1 ] cnt[i+len][j+1]+=cnt[i][j]*C[len-1] cnt[i+len][j+1]+=cnt[i][j]C[len1]递推即可。

code:

#include <iostream>
#include <cstdio>
using namespace std;
typedef long long ll;
const int mod=1e9+7;

int n;
//C 12 n
int C[20]={1,12,66,220,495,792,924,792,495,220,66,12,1};

ll ans;
void dfs(int i,int lst){//暴力程序,用来验证答案 
	if(i>n){
		ans++;
		return;
	}
	dfs(i+1,1);
	for(int t=lst+1;t<=13;t++)
		dfs(i+1,t);
	return;
}

ll cnt[30][5];//放好前i个位置,用掉j个Ace 

int main(){
	cin>>n;
	cnt[0][0]=1;
	for(int i=1;i<=12;i++)//一开始的序列不需要A开头 
		cnt[i][0]=C[i];
	
	for(int i=0;i<=20;i++){
		for(int len=1;len<=min(13,20-i);len++){//长为len的以A起手的递增序列 
			for(int j=0;j<=3;j++){
				cnt[i+len][j+1]+=cnt[i][j]*C[len-1];
				cnt[i+len][j+1]%=mod;
			}
		}
	}
	cout<<(cnt[n][0]+cnt[n][1]+cnt[n][2]+cnt[n][3]+cnt[n][4])%mod;
	return 0;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值