2022杭电多校联赛第四场 题解

比赛传送门
作者: fn


签到题

1004题 Link with Equilateral Triangle / Link和等边三角形

题目大意
给一个边长为 n n n 的大等边三角形。
在小三角形的每个顶点中填充数字,限制如下:填写0、1或2。大三角形的左侧不应填写0。大三角形的右侧不应填写1。大三角形的底侧不应填写2。对于边长为1的每个小三角形,三个顶点的总和不应是3的倍数。

是否可以填充三角形,使其满足上述所有条件?
1004题

考察内容
找规律

分析
画图找规律,发现不论 n n n 为多少都是填不满的,直接输出No

#include<bits/stdc++.h>
#define ll long long
#define cer(x) cerr<<(#x)<<" = "<<(x)<<'\n'
#define endl '\n'
using namespace std;
const int N=1e6+10;
ll n,m,a[N];
string s;
int main(){ 
	ios::sync_with_stdio(0);cin.tie(0);cout.tie(0);
	int t; cin>>t;
	while(t--){
		cin>>n;
		cout<<"No"<<endl;
	}
	return 0;
}

1006题 BIT Subway / 北京国际交通地铁

题目大意
北京国际交通(BIT)地铁推出了以下促销活动:
如果您本月花费的总票价大于或等于100,以后的票8折
如果您本月花费的总票价大于等于200,以后的票5折

德利这个月花了199元买了票,他现在买了一张10元的票,然后买了一张8元的票:
德利认为他一次只能买一部分票,而不是整张票。也就是说,对于10元的车票,德利认为他可以先购买1.25元的车票,然后再购买8.75元的车票。在他的误解下,他需要花费
199 + 1.25 ∗ 0.8 + 8.75 ∗ 0.5 + 8 ∗ 0.5 = ¥ 208.375 199+1.25*0.8+8.75*0.5+8*0.5=¥208.375 199+1.250.8+8.750.5+80.5=¥208.375

真正的计费方法是,只有你花了足够的钱,你才能得到折扣,因此它将是 199 + 10 ∗ 0.8 + 8 ∗ 0.5 = ¥ 211 199+10*0.8+8*0.5=¥211 199+100.8+80.5=¥211
按顺序给出所有车票,输出德利以为的花费和实际花费。

考察内容
模拟

分析
按题意模拟即可,注意德利以为的情况有可能把一张票拆成三段。

#include<bits/stdc++.h>
#define ll long long
#define cer(x) cerr<<(#x)<<" = "<<(x)<<'\n'
#define endl '\n'
using namespace std;
const int N=1e5+10;
ll n;
double a[N];
int main(){ 
	int t; 
	scanf("%d",&t);
	while(t--){
		scanf("%lld",&n);
		for(int i=1;i<=n;i++){
			scanf("%lf",&a[i]); 
		}
		
		double sum1=0,sum2=0;
		
		for(int i=1;i<=n;i++){
			// 真实计价方式 
			if(sum2<100){ 
				sum2+=a[i]; // 原价购买 
			}
			else if(sum2<200){
				sum2+=a[i]*0.8; // 8折购买 
			}
			else{
				sum2+=a[i]*0.5; // 5折购买 
			}
			
			
			// DLee以为的计价方式 
			if(sum1<100){ 
				if(sum1+a[i]<=100){
					sum1+=a[i];
				}
				else{ 
					double d=100-sum1;
					sum1+=d;
					a[i]-=d;
					
					if(sum1+a[i]*0.8<=200){
						sum1+=a[i]*0.8;
					}
					else{ // sum1+a[i]*0.8>200 
						double d=200-sum1;
						sum1 += d;
						a[i] -= d*1.25; // /0.8等价于*1.25
						sum1 += a[i]*0.5;
					}
				}
			}
			else if(sum1<200){
				if(sum1+a[i]*0.8<=200){
					sum1+=a[i]*0.8;
				}
				else{ // sum1+a[i]*0.8>200 
					double d=200-sum1;
					sum1 += d;
					a[i] -= d*1.25; // /0.8等价于*1.25
					sum1 += a[i]*0.5;
				}
			}
			else{ // sum1>=200
				sum1+=a[i]*0.5;
			}
		}
		
		printf("%.3f %.3f\n",sum1,sum2);
	}
	return 0;
}
/*
1
2
99 200

*/ 

基本题

1007题 Climb Stairs / 爬楼梯

题目大意
有一座 n n n 层的高楼,每层楼梯上都有一个怪物,第二层有生命点 a i a_i ai
德利从地面(可以视为第0层)开始,有一个基本攻击点 a 0 a_0 a0

他可以选择往上跳 1 , 2 , . . . , k 1,2, ... ,k 1,2,...,k 层或往下走1层,但他不能去怪物生命值严格大于其攻击值的楼层,也不能去已经拜访过的楼层。一旦他来了并击败了一个怪物,他可以吸收怪物的生命值并将其添加到他的攻击点。

请问德利是否有可能打败所有的怪物并通过关卡。
打怪爬塔

考察内容
贪心,暴力,复杂度优化

分析
解法不唯一。

贪心策略,枚举每一个怪物,如果能不跳直接吃下就直接吃掉,
否则先跳过去,再往回吃掉这个怪物。
跳多远都不能往回吃掉的肯定无法通关,直接break。

可以证明,在可以吃掉下一个怪物的前提下,跳得尽可能近是最优的。

预处理前缀和,在暴力判断前先用前缀和判断剪枝一下,优化一下复杂度。

#include<bits/stdc++.h>
#define ll long long
#define cer(x) cerr<<(#x)<<" = "<<(x)<<'\n'
// #define endl '\n'
using namespace std;
int read(int &n){
	char ch=' '; int q=0,w=1;
	for(;(ch!='-')&&((ch<'0')||(ch>'9'));ch=getchar());
	if(ch=='-')w=-1,ch=getchar();
	for(;ch>='0'&&ch<='9';ch=getchar())q=q*10+ch-48;
	n=q*w; return n;
} 
ll read(ll &n){
	char ch=' '; ll q=0,w=1;
	for(;(ch!='-')&&((ch<'0')||(ch>'9'));ch=getchar());
	if(ch=='-')w=-1,ch=getchar();
	for(;ch>='0'&&ch<='9';ch=getchar())q=q*10+ch-48;
	n=q*w; return n;
}

const int N=1e5+10;
ll n,k,a[N];
ll f[N]; // a的前缀和 

int main(){ 
	ios::sync_with_stdio(0);cin.tie(0);cout.tie(0);
	int t; 
	read(t); 
	while(t--){
		read(n); read(a[0]); read(k); 
		
		f[0]=a[0]; 
		for(int i=1;i<=n;i++){
			read(a[i]);
			f[i]=f[i-1]+a[i];
		}
		
		int F=1; 
		
		ll sum=a[0];
		for(int i=1;i<=n;i++){ // 枚举a[i],看能否吃掉 
		
			// 先判断是否能直接吃上去吃光 
			if(sum>=a[i]){ // 可以吃掉 
				sum+=a[i];
			} 
			else{ // 吃不掉,需要跳 
				int F2=0;
				int len1=-1;
					
				if(k>=n-i+1){ 
					k=n-i+1; // 缩小k,防止跳出边界 
				}
					
				for(int j=2;j<=k;j++){ // 从近到远枚举跳的长度 
					ll cnt=sum;
					if(cnt+f[i+j-1]-f[i] < a[i]){ // a[i+1]到a[i+j-1]全部吃完也吃不掉a[i] 
						continue; // 剪枝掉 
					}
					
					F2=1; // 找到最近的跳的方案
					len1=j; // 跳j格 
					
					for(int k0=i+j-1;k0>=i;k0--){
						if(cnt>=a[k0]){
							cnt+=a[k0];
						}
						else{ // 吃不掉 
							F2=0;
							break;
						}
					}
					if(F2)break;
				} 
				
				if(F2){
					sum += f[i+len1-1]-f[i-1]; 
					i += len1-1; 
				} 
				else{
					F=0;
					break;
				}
			}
		}
		
		if(F)cout<<"YES"<<endl;
		else cout<<"NO"<<endl;
	}
	return 0;
}
/*
1
9 1 6
2 1 8 4 2 1 40 20 1
YES

1
6 1 1
1 2 4 8 16 32 
YES
*/ 

进阶题

1011题 Link is as bear / Link像熊一样

题目大意
给定一个包含 n ( 1 ≤ n ≤ 1 0 5 ) n (1 \leq n \leq 10^5) n(1n105) 个元素的数组 a a a ​,Link可以进行以下操作:
选择两个整数 l , r ( 1 ≤ l ≤ r ≤ n ) l,r (1≤l≤r≤n) l,r(1lrn) ,使所有 a i = x o r ( l , r ) a_{i}=xor(l,r) ai=xor(lr) ,其中 l ≤ i ≤ r l\leq i\leq r lir ,并且 x o r ( l , r ) xor(l,r) xor(lr) 表示 a [ l ] , a [ l + 1 ] , . . . , a [ r ] a[l], a[l+1], ... ,a[r] a[l],a[l+1],...,a[r] 按位异或后的值。

Link可以操作任意次(可能为零次),并可以任意选择 l , r l,r l,r ,最终把所有元素变成一样的值。他想知道这个一样的值的最大是多少。

给定的数组保证存在至少一对相同的数字

考察内容
线性基

分析
问题等价于给定 n n n 个数,从中选一些数,使得这些数的异或和最大。这是线性基的模板题。

线性基传送门

#include<bits/stdc++.h>
using namespace std;
#define mem(a, b) memset(a, b, sizeof(a))
#define rep(i,a,b) for(int i=(a),__##i##__=(b);i<=__##i##__;i++)
#define dwn(i,a,b) for(int i=(a),__##i##__=(b);i>=__##i##__;i--)

typedef long long ll;
typedef unsigned long long ull;
template <typename T>
T read(){
    T x=0;int f=1;char ch=getchar();
    while(ch!=EOF&&!isdigit(ch)) f=(ch=='-'?-1:1),ch=getchar();
    while (ch!=EOF&&isdigit(ch)) x=x*10+ch-'0',ch=getchar();
    return x*f;
}
#define rdi read<int>()
#define rdl read<ll>()
size_t rds(char *__s, bool _getspace = false){
    size_t len=0;char ch=getchar();
    while(ch!=EOF&&(ch=='\r'||ch=='\n'||(!_getspace&&isspace(ch))))ch=getchar();
    while(ch!=EOF&&(ch!='\r'&&ch!='\n'&&(_getspace||!isspace(ch))))__s[len++]=ch,ch=getchar();
    __s[len]='\0';
    return len;
}

const int N=100005;
int n;
ll a[N],d[80];
int main(){ // 线性基。问题等价于给n个数,从中选一些数,使得这些数的异或和最大。
    rep(_,1,rdi){
        mem(d,0);
        n=rdi;
        rep(i,1,n) a[i]=rdl;
        
        rep(i,1,n){
            ll x=a[i];
            dwn(j,62,0){
                if(x&(1ll<<j)) if(d[j]) x^=d[j];
                else {d[j]=x;break;}
            }
        }
        ll ans=0;
        dwn(i,62,0)
            ans=max(ans,ans^d[i]);
            
        printf("%lld\n",ans);
    }
    return 0;
}

1001题 Link with Bracket Sequence II / Link和括号序列II

题目大意
给定 m m m 个类型的括号和一个长度为 n ( n ≤ 500 ) n (n \leq 500) n(n500) 的括号序列,序列中的一些括号丢失了。
计算有多少种方法来填补序列使其有效。

考察内容
区间dp

分析
区间dp。

状态:
f [ i ] [ j ] f[i][j] f[i][j] 表示 [ i , j ] [i,j] [i,j] 为合法括号序列且 i , j i,j i,j 上括号相互匹配的方案数。
g [ i ] [ j ] g[i][j] g[i][j] 表示 [ i , j ] [i,j] [i,j] 区间形成一个合法括号序列的方案数。

边界:
g [ i ] [ i − 1 ] = 1 g[i][i-1]=1 g[i][i1]=1
空序列算1种方案。

转移:
先枚举 l , r l,r l,r 位置上填写的内容,如果形成匹配的括号对,则把 g [ l + 1 ] [ r − 1 ] g[l+1][r-1] g[l+1][r1] 乘上方案数,转移到 f [ l ] [ r ] f[l][r] f[l][r]

再从 f f f 转移回 g g g
g [ l ] [ r ] = g [ l ] [ r ] + g [ l ] [ l − 1 ] ∗ f [ l ] [ r ] + g [ l ] [ l + 1 ] ∗ f [ l + 2 ] [ r ] + . . . + g [ l ] [ r − 1 ] ∗ f [ r − 1 ] [ r ] g[l][r]=g[l][r]+g[l][l-1]*f[l][r]+g[l][l+1]*f[l+2][r]+...+g[l][r-1]*f[r-1][r] g[l][r]=g[l][r]+g[l][l1]f[l][r]+g[l][l+1]f[l+2][r]+...+g[l][r1]f[r1][r]

复杂度 O ( n 3 ) O(n^3) O(n3)

#include<bits/stdc++.h>
#define ll long long
#define cer(x) cerr<<(#x)<<" = "<<(x)<<'\n'
#define endl '\n'
using namespace std;
const int N=505;
const ll mod=1e9+7;
ll n,m,a[N];
ll f[N][N];
ll g[N][N];

int main(){ // 区间dp 
	ios::sync_with_stdio(0);cin.tie(0);cout.tie(0);
	int t; cin>>t;
	while(t--){
    	memset(f,0,sizeof(f));
    	memset(g,0,sizeof(g));
    
		cin>>n>>m;
		for(int i=1;i<=n;i++){
			cin>>a[i];
		}
		
		for(int i=1;i<=n;i++){
			g[i][i-1]=1; // 空序列算1个 
		}
		
		for(int len=2;len<=n;len+=2){ // 枚举区间长度 
			for(int l=1;l+len-1<=n;l++){
				int r=l+len-1;
				
				if(a[l]>=0 && a[r]<=0){
					int e=0;
					if(a[l]==0 && a[r]==0){
						e=m;
					}
					else if(a[l]==0 || a[r]==0){
						e=1;
					}
					else if(a[l]+a[r]==0){
						e=1;
					}
					f[l][r]=g[l+1][r-1]*e%mod;
				} 
				
				for(int k=l;k<=r-1;k+=2){
					g[l][r]+=g[l][k-1]*f[k][r]%mod;
					g[l][r]%=mod;
				}
			}
		} 
		
		cout<<g[1][n]<<endl;
	}
	return 0;
}
  • 2
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值