A. Long Comparison
题意:给出两个数字,都表示为
x
+
后
缀
有
p
个
0
x+后缀有p个0
x+后缀有p个0.比较两个数字的大小
思路:一开始是傻傻地去模拟,暴力地把p个0加在x后面,然而
p
p
p的有
1
e
6
1e6
1e6,会超时.我的做法是这样的,首先把
x
1
x1
x1变为string,令其长度为
m
1
m1
m1,
首先直接比较
m
1
+
p
1
与
m
2
+
p
2
m1+p1与m2+p2
m1+p1与m2+p2,谁长谁大.如果相等,把
x
1
与
x
2
补
成
位
数
相
等
的
s
t
r
i
n
g
,
然
后
直
接
比
较
x1与x2补成位数相等的string,然后直接比较
x1与x2补成位数相等的string,然后直接比较
/*
给出两个整数 都是x + p个0
比较该两个整数大小.
2 000
1900
*/
#include<bits/stdc++.h>
using namespace std;
const int maxn = 3e5+5;
const int INF = 1e9+7;
int main(){
int T;
cin>>T;
while(T--){
int x1,p1,x2,p2;
scanf("%d %d %d %d",&x1,&p1,&x2,&p2);
string s1,s2;
s1 = to_string(x1);s2 = to_string(x2);
int m1 = s1.length();int m2 = s2.length();
if(m1+p1!=m2+p2){
if(s1.length()+p1>s2.length()+p2) cout<<">\n";
else cout<<"<\n";
continue;
}
if(m2>m1){
for(int i=0;i<(m2-m1);i++) s1+="0";
if(s1==s2) cout<<"=\n";
else if(s1>s2) cout<<">\n";
else cout<<"<\n";
}
else if(m1==m2){
if(x1>x2) cout<<">\n";
if(x1==x2) cout<<"=\n";
if(x1<x2) cout<<"<\n";
}
else if(m1>m2){
for(int i=0;i<(m1-m2);i++) s2+="0";
if(s1==s2) cout<<"=\n";
else if(s1>s2) cout<<">\n";
else cout<<"<\n";
}
}
return 0;}
B. Absent Remainder
题意:给你一个长度为
n
n
n的数组
a
i
ai
ai,然后要你找
n
/
2
n/2
n/2对的
x
,
y
x,y
x,y,满足
x
%
y
不
在
数
组
a
中
x\%y不在数组a中
x%y不在数组a中.
思路:考虑
x
%
y
x\%y
x%y的值域,落在
[
0
,
y
−
1
]
[0,y-1]
[0,y−1],如果我们选取y是最小值,那么任意的
x
!
=
y
x!=y
x!=y都一定满足该条件.代码中还排序了一下,选后
n
/
2
n/2
n/2个数字.
/*
2 3 4 5 7 8
8 7 4 3 5 2
*/
#include<bits/stdc++.h>
using namespace std;
const int maxn = 2e5+5;
const int INF = 1e9+7;
int a[maxn];
int main(){
int T;cin>>T;
while(T--){
int n;scanf("%d",&n);
for(int i=1;i<=n;i++) scanf("%d",&a[i]);
int m = n / 2;
sort(a+1,a+1+n);
for(int i=n-m+1;i<=n;i++) {
cout<<a[i]<<" "<<a[1]<<"\n";
}
}
}
C. Poisoned Dagger
题意:可以简化为以下问题: 给出
n
n
n个数轴上的点
a
i
ai
ai
寻找一个最小的k,使得总区间长度
[
a
i
,
a
i
+
k
]
,
[
a
i
+
1
,
a
i
+
1
+
k
]
.
.
.
[
a
n
,
a
n
+
k
]
[ai,ai+k],[ai+1,ai+1+k]...[an,an+k]
[ai,ai+k],[ai+1,ai+1+k]...[an,an+k]长度大于H.
分析:显然,对于k来说,满足单调递增性.如果k是一个可行解,那么k+1也一定是一个可行解.二分k即可.
那么问题转化为计算出总区间长度,记为
s
u
m
sum
sum,我们只需要解决两个区间重复的问题.
s
u
m
=
∑
i
=
1
n
k
−
o
v
e
r
l
a
p
(
a
i
,
a
i
−
1
)
sum=\sum_{i=1}^n{k-overlap(a_i,a_{i-1})}
sum=∑i=1nk−overlap(ai,ai−1).
o
v
e
r
l
a
p
(
a
i
,
a
i
−
1
)
是
指
区
间
前
后
两
个
区
间
的
重
复
部
分
.
不
难
得
o
v
e
r
l
a
p
(
a
i
,
a
i
−
1
)
=
a
i
−
1
+
k
−
a
i
overlap(a_i,a_{i-1})是指区间前后两个区间的重复部分.不难得overlap(a_i,a_{i-1})=a_{i-1}+k-a_i
overlap(ai,ai−1)是指区间前后两个区间的重复部分.不难得overlap(ai,ai−1)=ai−1+k−ai
计算出
s
u
m
sum
sum,只需判断sum和H的大小就能写出二分的check.
代码:
#include<bits/stdc++.h>
using namespace std;
const int maxn = 100+5;
const int INF = 1e9+7;
typedef long long ll;
int a[maxn];
int n;ll h;
bool check(ll k){
ll sum = 0;
for(int i=1;i<=n;i++){
if(i==1) sum+=k;
else {
ll overlap = a[i-1] + k - a[i];
if(overlap>0) sum-=overlap;
sum+=k;
}
}
return sum >= h;
}
int main(){
int T;cin>>T;
while(T--){
scanf("%d %lld",&n,&h);
for(int i=1;i<=n;i++) scanf("%d",&a[i]);
ll L = 1,R = h;ll ans;
while(L<=R){
ll mid = (L+R)>>1;
if(check(mid)){
ans = mid;R = mid-1;
}
else L = mid + 1;
}
cout<<ans<<"\n";
}
}
D.MEX Sequences
题意:定义一个好序列ak.
对于
1
<
=
i
<
=
k
,
都
满
足
∣
x
i
−
M
e
x
(
x
1..
x
i
)
∣
<
=
1
1<=i<=k,都满足|xi-Mex(x1..xi)|<=1
1<=i<=k,都满足∣xi−Mex(x1..xi)∣<=1
现在给你一个长度为n的数组,计算出满足上述条件的子序列个数.
分析:先分析这样合法的序列大概长什么样.通过分析几个样例尤其是最后一个样例与5个0的样例,不难发现合法的序列大概长这两个个样子.
[
1
,
2
,
2
,
2
,
3
,
3
,
3
]
[1,2,2,2,3,3,3]
[1,2,2,2,3,3,3]
[
1
,
2
,
3
,
4
,
6
,
4
,
6
,
6
,
4
]
[1,2,3,4,6,4,6,6,4]
[1,2,3,4,6,4,6,6,4]
计算出子序列的模型我个人认为是这样的:扫描一遍该数组,每扫到一个元素,考虑如何把该元素放到原有的合法子序列仍然合法,然后组成新的子序列.
考虑元素
x
x
x如何插入到上述两种子序列中.
n
u
m
1
[
i
]
为
扫
到
当
前
x
元
素
为
止
,
[
0
,
1
,
2...
i
]
的
子
序
列
数
目
num1[i]为扫到当前x元素为止,[0,1,2...i]的子序列数目
num1[i]为扫到当前x元素为止,[0,1,2...i]的子序列数目
n
u
m
2
[
i
]
为
扫
到
当
前
x
元
素
为
止
,
[
0
,
1
,
2..
i
−
2
,
i
]
子
序
列
数
目
.
num2[i]为扫到当前x元素为止,[0,1,2..i-2,i]子序列数目.
num2[i]为扫到当前x元素为止,[0,1,2..i−2,i]子序列数目.
对于
n
u
m
1
[
x
]
num1[x]
num1[x],x都可以与其子序列组成新的合法序列,故
n
u
m
1
[
x
]
∗
=
2
num1[x]*=2
num1[x]∗=2.
同理
n
u
m
1
[
x
−
1
]
也
是
,
x
能
插
入
到
[
1
,
2
,
3...
x
−
1
]
中
,
但
这
部
分
贡
献
应
当
算
到
n
u
m
1
[
x
]
中
有
:
n
u
m
1
[
x
]
+
=
n
u
m
[
x
−
1
]
.
num1[x-1]也是,x能插入到[1,2,3...x-1]中,但这部分贡献应当算到num1[x]中有:num1[x]+=num[x-1].
num1[x−1]也是,x能插入到[1,2,3...x−1]中,但这部分贡献应当算到num1[x]中有:num1[x]+=num[x−1].
类似地,对于
n
u
m
2
num2
num2.
x
要
么
是
插
到
[
1
,
2
,
3
,
x
,
x
+
2
]
中
,
要
么
是
插
到
[
1
,
2
,
3
,
4..
,
x
−
2
,
x
]
中
x要么是插到[1,2,3,x,x+2]中,要么是插到[1,2,3,4..,x-2,x]中
x要么是插到[1,2,3,x,x+2]中,要么是插到[1,2,3,4..,x−2,x]中
n
u
m
2
[
x
+
2
]
∗
=
2
,
n
u
m
[
x
]
∗
=
2
num2[x+2]*=2,num[x]*=2
num2[x+2]∗=2,num[x]∗=2
但
是
要
注
意
n
u
m
2
的
起
始
条
件
是
[
1
,
2
,
3...
i
−
2
]
这
恰
好
是
n
u
m
1
[
i
−
2
]
的
数
目
,
所
以
实
际
上
还
有
n
u
m
2
[
x
]
+
=
n
u
m
1
[
x
−
2
]
但是要注意num2的起始条件是[1,2,3...i-2]这恰好是num1[i-2]的数目,所以实际上还有num2[x]+=num1[x-2]
但是要注意num2的起始条件是[1,2,3...i−2]这恰好是num1[i−2]的数目,所以实际上还有num2[x]+=num1[x−2]
最后答案就是把
n
u
m
1
,
n
u
m
2
num1,num2
num1,num2全部加起来,代表所有子序列的数目.
然而,上述的转移方程在实际编程却遇到了麻烦,请看如下测试样例
[
1
,
1
]
[1,1]
[1,1],没错在单独一个[1]的开头子序列,我们计算出现了麻烦,它是属于num2情况中的特殊情况,它不能被继续续上2,也不能续上0,只能续上它自己,但在编程中却忽略了它,应当计算上,存放在num2[1]中.同理也有
[
0
,
0
,
0
,
0
]
这
样
的
计
数
[0,0,0,0]这样的计数
[0,0,0,0]这样的计数
所以当
x
=
1
时
,
应
当
还
有
n
u
m
2
[
1
]
=
n
u
m
2
[
1
]
+
n
u
m
2
[
1
]
x=1时,应当还有num2[1]=num2[1]+num2[1]
x=1时,应当还有num2[1]=num2[1]+num2[1],当
x
=
0
时
,
应
当
还
有
n
u
m
1
[
0
]
+
=
1
;
x=0时,应当还有num1[0]+=1;
x=0时,应当还有num1[0]+=1;
注意注意注意:上述的过程是有先后顺序的,在计算过程中,
n
u
m
1
[
x
]
∗
=
2
,
n
u
m
2
[
x
]
∗
=
2
,
是
最
先
执
行
的
num1[x]*=2,num2[x]*=2,是最先执行的
num1[x]∗=2,num2[x]∗=2,是最先执行的,如果你放在后边进行,就会进行错误地翻倍,因为
∗
=
2
*=2
∗=2实际上代表的是加上上一次自己的子序列数目是多少,如果先进行了其他操作,就会改变了
n
u
m
1
[
x
]
,
n
u
m
2
[
x
]
的
值
,
进
而
错
误
地
∗
=
2
了
num1[x],num2[x]的值,进而错误地*=2了
num1[x],num2[x]的值,进而错误地∗=2了
代码:
/*
*/
#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
const int maxn = 5e5+2;
const int INF = 1e9+7;
const int mod = 998244353;
int a[maxn];
int main(){
int T;
cin>>T;
while(T--){
int n;scanf("%d",&n);
vector<int> num1(n+5);
vector<int> num2(n+5);
for(int i=1;i<=n;i++){
int x;
scanf("%d",&x);
num1[x] = (num1[x] + num1[x]) %mod;
if(x>0) num1[x] = (num1[x] + num1[x-1])%mod;
else if(num1[0]==0) num1[0] = 1;
else num1[0]++;
num2[x] = (num2[x]+num2[x])%mod;
if(x>=2) num2[x] = (num2[x]+num1[x-2]) % mod;
if(x==1) {
if(num2[1]==0) num2[1] =1;
else num2[1]++;
}
num2[x+2] = (num2[x+2] + num2[x+2] )%mod;
}
int ans = 0;
for(int i=0;i<=n;i++) ans = ((ans+num1[i])%mod+num2[i])%mod;
cout<<ans<<"\n";
}
return 0;}
一开始用的数组每次进行初始化让卡时间了,后来改用vector过了
E. Crazy Robot
给一个机器人与一张地图,这个机器人对输入的指令一定不服从,也就是说,只能控制它不去往一个方向.然后给你一个’L’,要求用指令把这个机器人抓回去’L’.然后机器人可能在任意的
′
.
′
'.'
′.′上,所以要求你输出是否可以由’.‘绝对可以到达’L’点,把这样的’.‘点改为’+’,输出整一张地图.
思路:显然是一个搜索.怎么让机器人前往’L’点呢,假设机器人在’L’点附近的一个点,如果机器人被两面’墙’堵住,我们再控制它不去往第三面,那么机器人一定会前往’L’点.所谓的"墙’就是机器人不能前往的点/前往了就一定会被抓住的点,也就是’+’,‘L’,’#'和地图边界.
代码实现:采用bfs实现,但要注意,在进行墙的计数时,不能把该点的前驱也视为墙,事实上往外扩展的节点是需要去到绝对可以前往’L’的这一集合中,也就是往外走的节点被两个墙堵住,然后再被指令堵一个方向,只能前往前驱被访问过的集合中。
注意,不要用数组存地图,因为n,m规模很大,但是
n
∗
m
<
=
1
e
6
n*m<=1e6
n∗m<=1e6,开vector存,
vis数组同理,省空间,防止爆空间.
#include<bits/stdc++.h>
using namespace std;
const int maxn = 3e5+5;
const int INF = 1e9+7;
typedef pair<int,int> pii;
vector<vector<char> > mp;
vector<vector<bool> > vis;
int dx[]={0,0,1,-1};int dy[]={1,-1,0,0};
int n,m;int sx,sy;;
int block(int x,int y,int fx,int fy){
int B = 0;
for(int i=0;i<4;i++){
int nx = x+dx[i];int ny = y+dy[i];
if(nx==fx&&ny==fy) continue;
if(nx<1||nx>n||ny<1||ny>m||mp[nx][ny]=='#'||mp[nx][ny]=='L'||mp[nx][ny]=='+') B++;
}
return B;
}
bool check(int nx,int ny){
if(nx<1||nx>n||ny<1||ny>m||mp[nx][ny]=='#'||vis[nx][ny]==1) return false;
return true;
}
void bfs(){
queue<pii> q;
q.push(make_pair(sx,sy));
vis[sx][sy]=1;
while(!q.empty()){
int x = q.front().first;int y=q.front().second;
q.pop();
bool L=false;
if(x==sx&&y==sy) L = true;
for(int i=0;i<4;i++){
int nx = x+dx[i];int ny = y+dy[i];
if(!check(nx,ny)) continue;
int blocks = block(nx,ny,x,y);
if(blocks>=2) {
vis[nx][ny]=1;
q.push(make_pair(nx,ny));
mp[nx][ny] = '+';
}
}
}
}
int main(){
ios_base::sync_with_stdio(0);
cin.tie(0);
int T;cin>>T;
while(T--){
cin>>n>>m;
mp.clear();vis.clear();
mp.resize(n+2);vis.resize(n+2);
for(int i=1;i<=n;i++){
mp[i].resize(m+1);vis[i].resize(m+2);
for(int j=1;j<=m;j++){
cin>>mp[i][j];
if(mp[i][j]=='L') sx=i,sy=j;
}
}
bfs();
for(int i=1;i<=n;i++){
for(int j=1;j<=m;j++) cout<<mp[i][j];
cout<<"\n";
}
}
return 0;}