比赛传送门
作者: fn
目录
签到题
1004题 Link with Equilateral Triangle / Link和等边三角形
题目大意
给一个边长为
n
n
n 的大等边三角形。
在小三角形的每个顶点中填充数字,限制如下:填写0、1或2。大三角形的左侧不应填写0。大三角形的右侧不应填写1。大三角形的底侧不应填写2。对于边长为1的每个小三角形,三个顶点的总和不应是3的倍数。
是否可以填充三角形,使其满足上述所有条件?
考察内容
找规律
分析
画图找规律,发现不论
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.25∗0.8+8.75∗0.5+8∗0.5=¥208.375
真正的计费方法是,只有你花了足够的钱,你才能得到折扣,因此它将是
199
+
10
∗
0.8
+
8
∗
0.5
=
¥
211
199+10*0.8+8*0.5=¥211
199+10∗0.8+8∗0.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(1≤n≤105) 个元素的数组
a
a
a ,Link可以进行以下操作:
选择两个整数
l
,
r
(
1
≤
l
≤
r
≤
n
)
l,r (1≤l≤r≤n)
l,r(1≤l≤r≤n) ,使所有
a
i
=
x
o
r
(
l
,
r
)
a_{i}=xor(l,r)
ai=xor(l,r) ,其中
l
≤
i
≤
r
l\leq i\leq r
l≤i≤r ,并且
x
o
r
(
l
,
r
)
xor(l,r)
xor(l,r) 表示
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(n≤500) 的括号序列,序列中的一些括号丢失了。
计算有多少种方法来填补序列使其有效。
考察内容
区间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][i−1]=1
空序列算1种方案。
转移:
先枚举
l
,
r
l,r
l,r 位置上填写的内容,如果形成匹配的括号对,则把
g
[
l
+
1
]
[
r
−
1
]
g[l+1][r-1]
g[l+1][r−1] 乘上方案数,转移到
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][l−1]∗f[l][r]+g[l][l+1]∗f[l+2][r]+...+g[l][r−1]∗f[r−1][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;
}