CUSTACM Summer Camp 2022 Training 8
无AI题解,以前写过
B. The Robot
题意
给你一个字符串表示的路径,其中LRUD分别表示x–,x++,y++,y–,要你在地图上一个位置放一个障碍物,使机器人从(0,0)按路径走后会返回起点。若无法实现输出(0,0);
数据大小: 1 ≤ 字符串长度 ≤ 5000 1 \leq 字符串长度 \leq 5000 1≤字符串长度≤5000
tags:暴力
思路
数据范围很小,可以直接使用暴力,对路径上每一个点放一个障碍物看看它会不会返回起点
不要总觉得暴力不行直接pass,看好数据范围估计暴力复杂度
代码
#include<bits/stdc++.h>
using namespace std;
int main(){
ios::sync_with_stdio(false);
cin.tie(0);cout.tie(0);
int t;
for(cin>>t;t;t--){
string s;
cin>>s;
vector<pair<int,int>>v;
int x=0,y=0;
for(int i=0;i<s.length();i++){
if(s[i]=='D')y--;
else if(s[i]=='U')y++;
else if(s[i]=='L')x--;
else if(s[i]=='R')x++;
v.push_back(make_pair(x,y));
}
bool judge=false;
for(int i=0;i<v.size();i++){
int x=0,y=0;
for(int j=0;j<s.length();j++){
if(s[j]=='D')y--;
else if(s[j]=='U')y++;
else if(s[j]=='L')x--;
else if(s[j]=='R')x++;
if(x==v[i].first&&y==v[i].second){
if(s[j]=='D')y++;
else if(s[j]=='U')y--;
else if(s[j]=='L')x++;
else if(s[j]=='R')x--;
}
}
if(x==0&&y==0){
cout<<v[i].first<<' '<<v[i].second<<endl;
judge=true;
break;
}
}
if(!judge)cout<<0<<' '<<0<<endl;
}
}
复杂度
O ( n 2 ) O(n^2) O(n2)
C. Divide and Summarize线段树好题
题意
一个长度为n的数组,可以进行以下操作:令mid=(max+min)/2,让<=mid的数在数组左侧,让>mid的数在数组右侧,可以选择丢弃一侧数组,将另一侧数组代替原数组
有q次询问,是否可以进行上述操作得到一个和为s的数组
数据大小: 1 ≤ n , q ≤ 1 0 5 , 1 ≤ a i ≤ 1 0 6 1 \leq n ,q \leq 10^5,1 \leq a_i \leq 10^6 1≤n,q≤105,1≤ai≤106
tags:思维,二分,线段树
思路
其实上述过程就是建立线段树的过程,只不过有几处不同
- 对于普通线段树建立
int mid=(l+r)/2
,但是对于本题,以(max+min)/2
的位置作为mid,可以用二分找 - 对于普通线段树的return条件
if(l==r)
,但是对于本题,如果有l到r的值都相等就return,a[k]的值为该区间和,可以先维护以下前缀和 - 在建树的过程中把a[k].sum用map标记一下,之和q次询问都可以在O(1)时间内求出
细节:
-
开longlong
-
不用去for循环判断是否整个区间都相等,因为区间是有序的,只需判断a[l]是否等于a[r]就行
-
普通线段树大小为 4 ∗ n 4*n 4∗n即可,但是这题的线段树不是每次一半一半分的,我开到100倍大小才过。
唉,比赛中就要自闭了 -
其实因为本题不需要区间查找、修改等操作,把线段树存起来没用作用还占空间,所以可以只对建树过程中的区间和用map标记一下就行,这样就不用怕上面溢出问题了
代码
最初代码
#include<bits/stdc++.h>
#define ll long long
using namespace std;
const int maxn=1e5+5;
int n,q;
ll num[maxn],b[maxn];
struct node{
ll l,r,sum;
node(){
l=r=sum=0;
}
}a[100*maxn];
map<ll,int>m;
void update(int k){
a[k].sum=a[k*2].sum+a[k*2+1].sum;
}
void build(int k,int l,int r){
a[k].l=l,a[k].r=r;
if(l==r||num[l]==num[r]){
a[k].sum=b[r]-b[l-1];
m[a[k].sum]++;
return;
}
// int mid=(l+r)/2;
int x=(num[l]+num[r])/2;
int mid=upper_bound(num+1,num+n+1,x)-num-1;
build(2*k,l,mid);
build(2*k+1,mid+1,r);
update(k);
m[a[k].sum]++;
}
int main(){
ios::sync_with_stdio(false);cin.tie(0);cout.tie(0);
int t;
for(cin>>t;t;t--){
cin>>n>>q;
// for(int i=0;i<10*n;i++)a[i].l=0,a[i].r=0,a[i].sum=0;
for(int i=0;i<=n;i++)b[i]=0;
m.clear();
for(int i=1;i<=n;i++)cin>>num[i];
sort(num+1,num+n+1);
for(int i=1;i<=n;i++)b[i]=num[i]+b[i-1];
build(1,1,n);
while(q--){
int x;
cin>>x;
if(m[x])cout<<"YES"<<endl;
else cout<<"NO"<<endl;
}
}
}
改进代码
#include<bits/stdc++.h>
#define ll long long
using namespace std;
const int maxn=1e5+5;
int n,q;
ll num[maxn],b[maxn];
map<ll,int>m;
void build(int l,int r){
m[b[r]-b[l-1]]++;
if(l==r||num[l]==num[r])return;
int x=(num[l]+num[r])/2;
int mid=upper_bound(num+1,num+1+n,x)-num-1;
build(l,mid);
build(mid+1,r);
}
int main(){
ios::sync_with_stdio(false);cin.tie(0);cout.tie(0);
int t;
for(cin>>t;t;t--){
cin>>n>>q;
for(int i=0;i<=n;i++)b[i]=0;
m.clear();
for(int i=1;i<=n;i++)cin>>num[i];
sort(num+1,num+n+1);
for(int i=1;i<=n;i++)b[i]=num[i]+b[i-1];
build(1,n);
while(q--){
int x;
cin>>x;
if(m[x])cout<<"YES"<<endl;
else cout<<"NO"<<endl;
}
}
}
D. Row GCD
题意
给定一个长度为n的序列a[],和长度为m的序列b[],对于每个j,求每个a[i],加上b[j]后的gcd(a[0]…a[n])
tsgs:辗转相除法,更相减损法
思路
对于每个a[0…n]加上b[j]后的gcd,可以对后面的a[1…n]项分别减去b[j](更具更相减损法此处gcd不变),这样a[1…n]的gcd就是原来没有加上b[j]的gcd了,先用辗转相除法预先求出该部分gcd,然后于a[0]+b[j]求gcd就行
代码
#include<bits/stdc++.h>
#define ll long long
using namespace std;
const int maxn=2e5+5;
ll n,m;
ll a[maxn],b[maxn];
ll gcd(ll x,ll y){
return y==0?x:gcd(y,x%y);
}
int main(){
ios::sync_with_stdio(false);
cin.tie(0);cout.tie(0);
cin>>n>>m;
for(int i=0;i<n;i++)cin>>a[i];
for(int j=0;j<m;j++)cin>>b[j];
for(int i=1;i<n;i++)a[i]=abs(a[i]-a[0]);
ll x=0;
for(int i=1;i<n;i++)x=gcd(a[i],x);
for(int j=0;j<m;j++){
cout<<gcd(b[j]+a[0],x)<<' ';
}
}
E. Rating Compression思维好题
题意
给你长度为n的数组,对于每个k从1到n,有n-k+1个数组的连续子集,其中判断取每个子集的最小值是否可以构成长度为n-k+1的排列
数据大小: 1 ≤ n ≤ 3 ∗ 1 0 5 , 1 ≤ a i ≤ n 1 \leq n \leq 3*10^5,1 \leq a_i \leq n 1≤n≤3∗105,1≤ai≤n
tags:思维
思路
-
对于一个较小的数,它只能处在数组两端,不然会出现多个子集的最小值为它
以1为例,当k=n-1时,要把数组分成两个子集,那么1只能在数组最左或最右段,不然的话两个子集的最小值都为1
承接上面,以2为例,当k=n-2时,要把数组分成三个子集,那么2只能在中间那个子集的两端出现,不然的话有两个子集的最小值都为2
以此类推
于是逆向遍历,一次划分的子集个数为2个、3个…n-1个,每次把最小值放在队列两端,直到有不满足题目要求的就break,因为后面肯定也会不满足要求
-
对于k=n或k=1要单独判断,因为它们不用符合上面最小值在队列两边的要求
代码
#include<bits/stdc++.h>
using namespace std;
const int maxn=3e5+5;
int n;
int a[maxn];
int out[maxn];
int main(){
ios::sync_with_stdio(false);cin.tie(0);cout.tie(0);
int t;
for(cin>>t;t;t--){
cin>>n;
map<int,int>m;
for(int i=0;i<=n;i++)out[i]=0;
for(int i=0;i<n;i++){
cin>>a[i];
m[a[i]]++;
}
bool judge=true;
for(int i=1;i<=n;i++)if(m[i]!=1)judge=false;
if(judge)out[1]=1;//特判k=1
if(m[1])out[n]=1;//特判k=n
int l=0,r=n-1,index=1;//左右指针为0,n-1。index表示当前应该在队列端的最小值
for(int i=n-1;i>=1;i--){
if(m[index+1]&&m[index]==1&&(a[l]==index||a[r]==index)){//若满足index只有一个(如果有多个那么它会在中间出现,肯定不满足),并且有index+1出现,而且index在队列两端,那么符合要求
out[i]=1;//k=i就符合要求,注意时逆向遍历,子集个数越来越大,这样才能把最小值依次放两边
if(a[l]==index)l++;//更新队列两端
else r--;
index++;
}
else break;//有不符合条件的,后面的肯定也不会符合条件
}
for(int i=1;i<=n;i++)cout<<out[i];
cout<<endl;
}
}
F. Zigzags
题意
给你一个数组,找出四元向量的数量(i,j,k,l)
- 1 ≤ i < j < k < l ≤ n 1\leq i<j<k<l\leq n 1≤i<j<k<l≤n
- a i = a k 并且 a j = a l a_i=a_k并且a_j=a_l ai=ak并且aj=al
数据大小: 4 ≤ n ≤ 3000 4 \leq n \leq 3000 4≤n≤3000
tags:思维
思路
数据没有很大,n2是每问题的,可以暴力(但不是无脑枚举四个元素)
- 确定其中一个元素k,枚举k
- 对于出现在k后面的数字,维护其出现的次数
- 在k之前寻找与之匹配的数,即寻找i
- 在寻找i的过程中维护一下方案数,即sum+在第二步中统计的次数(ik直接的数)
- 注意开longlong
代码
#include<bits/stdc++.h>
#define ll long long
using namespace std;
const int maxn=3e3+5;
int n;
int a[maxn];
int main(){
ios::sync_with_stdio(false);
cin.tie(0);cout.tie(0);
int t;
for(cin>>t;t;t--){
cin>>n;
for(int i=0;i<n;i++)cin>>a[i];
map<int,int>m;
ll ans=0;
for(int i=2;i<n;i++){
m.clear();
for(int j=i+1;j<n;j++)m[a[j]]++;
ll sum=0;
for(int k=i-1;k>=0;k--){
if(a[k]==a[i])ans+=sum;
sum+=m[a[k]];
}
}
cout<<ans<<endl;
}
}
复杂度
O ( n 2 ) O(n^2) O(n2)
G. Array Walk思维好题
题意
给你n个数,你可以走k步,最多可以向左走z步,并且不能连续向左走大于1步。每走到一个位置就加上该位置的值,问值最大可以为多少。
数据范围: 2 ≤ n ≤ 1 0 5 , 1 ≤ k ≤ n − 1 , 0 ≤ z ≤ m i n ( 5 , k ) 2 \leq n \leq 10^5,1 \leq k \leq n-1,0 \leq z \leq min(5,k) 2≤n≤105,1≤k≤n−1,0≤z≤min(5,k)
tags:思维
思路
- 因为不能连续向左走大于1步,所以除了一直向左走外,就只能多次左右左右横跳走,于是记录下移走过的位置中相邻两数的最大值mx
- 不断向前左走,不断更新mx,以及已走步数的价值sum
- 当然每走一步,更新剩余的步数,剩余的步数可以实现**min(z,(剩余步数)/2)**次横条,不断更新最大值``sum+min(z,(剩余步数)/2)*mx`
代码
#include<bits/stdc++.h>
using namespace std;
const int maxn=3e5+5;
int n,k,z;
int a[maxn];
int main(){
ios::sync_with_stdio(false);
cin.tie(0);cout.tie(0);
int t;
for(cin>>t;t;t--){
cin>>n>>k>>z;
for(int i=1;i<=n;i++)cin>>a[i];
int ans=0,mx=0,sum=0;
for(int i=1;i<=k+1;i++){
sum+=a[i];//sum时已经向前走了i-1步的score
if(i!=n)mx=max(mx,a[i]+a[i+1]);//相邻两数之和最大值为nx,目前正位于第i个位置,已经走了i-1步
ans=max(ans,sum+min(z,(k-i+1)/2)*mx);//剩下有(k-i+1)步,可以供反复横条(k-i+1)/2次
}
cout<<ans<<endl;
}
}
复杂度
O ( n ) O(n) O(n)
H. Pinkie Pie Eats Patty-cakes
题意
给你n个数,输出相同的数的最小间距的最大值
数据范围: 1 ≤ n ≤ 1 0 5 1 \leq n \leq 10^5 1≤n≤105
tags:思维、贪心
思路
- 最大间距取决于出现次数最多(假设为cnt次)的数字,该数字可能不止一种,设有x种
- 那么上述x种数字可以形成长度为x的cnt个隔板
- 将剩下的n-cnt*x个数均匀分配到每个隔板之中(如果是多次出现数应该放到不同隔板之中,其间隔一定会>=隔板间隔)
- 于是每个隔板的最大间隔为(n-cnt*x)/(cnt-1),在假设隔板本身长度提提供的间隔x-1,答案就是二者之和
代码
#include<bits/stdc++.h>
using namespace std;
const int maxn=1e5+5;
int n;
int cnt[maxn];
int main(){
ios::sync_with_stdio(false);
cin.tie(0);cout.tie(0);
int t;
for(cin>>t;t;t--){
cin>>n;
for(int i=0;i<=n;i++)cnt[i]=0;
for(int i=0;i<n;i++){
int x;cin>>x;
cnt[x]++;
}
sort(cnt,cnt+n+1,greater<int>());
int x=0;
for(int i=0;i<n;i++)if(cnt[i]==cnt[0])x++;else break;
cout<<(n-x*cnt[0])/(cnt[0]-1)+x-1<<endl;
}
}
复杂度
O ( n ) O(n) O(n)
J. Flood Fill区间dp好题
题意
有一行长度为n的方格,每个格子有颜色
对于连续的一块相同颜色的格子称为一个连通块
一开始你可以选择一个起点,每次操作你可以将起点所在位置的整个连通块改变为任意一个颜色
问最少进行多少次操作可以把n个方格都变为相同颜色数据范围: 1 ≤ n ≤ 5000 1 \leq n \leq 5000 1≤n≤5000
tags:区间dp
思路
- 因为一开始选定了一个点之后就固定了,只能将连通块逐个向左右扩展,若将区间[l,r]扩展为同色,最终颜色一定位a[l]或a[r]
- 对于把一个长度为n的区间变成连通块,其实把长度为len<n的区间变成连通块是其子问题,可以用区间dp来做
- 长度为len的区间可以由长度为len-1的区间扩展而来:
dp[l][r][0]可以由dp[l+1][r][0/1]而来,dp[l][r][1]可以由dp[l][r-1][0/1]而来
看题解还有最长公共子序列、最长回文子序列的方法
代码
区间DP
#include<bits/stdc++.h>
#define inf 0x3f3f3f3f
using namespace std;
const int maxn=5e3+5;
int n;
int a[maxn];
int dp[maxn][maxn][2];
int main(){
cin>>n;
for(int i=0;i<n;i++)cin>>a[i];
for(int i=0;i<n;i++)
for(int j=0;j<n;j++)
for(int k=0;k<2;k++)
if(i!=j)dp[i][j][k]=inf;
for(int len=2;len<=n;len++){
for(int l=0;l+len-1<n;l++){
int r=l+len-1;
dp[l][r][0]=min(dp[l][r][0],dp[l+1][r][0]+(a[l]!=a[l+1]));
dp[l][r][0]=min(dp[l][r][0],dp[l+1][r][1]+(a[l]!=a[r]));
dp[l][r][1]=min(dp[l][r][1],dp[l][r-1][0]+(a[r]!=a[l]));
dp[l][r][1]=min(dp[l][r][1],dp[l][r-1][1]+(a[r]!=a[r-1]));
}
}
cout<<min(dp[0][n-1][1],dp[0][n-1][0])<<endl;
}
最长回文子序列
#include<bits/stdc++.h>
#define inf 0x3f3f3f3f
using namespace std;
const int maxn=5e3+5;
int n;
int a[maxn];
int dp[maxn][maxn];
int main(){
cin>>n;
vector<int>v1;
for(int i=0;i<n;i++)cin>>a[i];
for(int i=0;i<n;){
int c=a[i];
while(a[i]==c)i++;
v1.push_back(c);
}
int len=v1.size();
for(int i=len-1;i>=0;i--){//倒序遍历
dp[i][i]=1;
for(int j=i+1;j<len;j++){
if(v1[i]==v1[j])dp[i][j]=dp[i+1][j-1]+2;
else dp[i][j]=max(dp[i][j-1],dp[i+1][j]);
}
}
cout<<len-dp[0][len-1]/2-1<<endl;//暴力原本需要len-1,减去回文字符串长度/2
}
#include<bits/stdc++.h>
#define inf 0x3f3f3f3f
using namespace std;
const int maxn=5e3+5;
int n;
int a[maxn];
int dp[maxn][maxn];
int main(){
cin>>n;
for(int i=0;i<n;i++)cin>>a[i];
vector<int>v;
for(int i=0;i<n;){
int c=a[i];
while(a[i]==c)i++;
v.push_back(c);
}
int len=v.size();
for(int i=0;i<n;i++)dp[i][i]=1;
for(int i=2;i<=len;i++){//斜着遍历(按子序列长度遍历)
for(int j=0;j+i-1<len;j++){
if(v[j]==v[j+i-1])dp[j][j+i-1]=dp[j+1][j+i-2]+2;
else dp[j][j+i-1]=max(dp[j][j+i-2],dp[j+1][j+i-1]);
}
}
cout<<len-1-dp[0][len-1]/2<<endl;
}
最长公共子序列
会超时,因为进行了n次dp,复杂度接近 O ( n 3 ) O(n^3) O(n3)
#include<bits/stdc++.h>
#define inf 0x3f3f3f3f
using namespace std;
const int maxn=5e3+5;
int n;
int a[maxn];
int dp[maxn][maxn];
int main(){
cin>>n;
for(int i=0;i<n;i++)cin>>a[i];
vector<int>v;
for(int i=0;i<n;){
int c=a[i];
while(a[i]==c)i++;
v.push_back(c);
}
int len=v.size();
int mx=0;
for(int i=0;i<len;i++){//以每个点为分界点
int x=i-1,y=i+1;
for(int j=y;j<len;j++)dp[0][j]=0;
for(int j=x;j>=0;j--)dp[j][0]=0;//注意初始化
for(int l=x;l>=0;l--){//注意l是从逆向遍历的
for(int r=y;r<len;r++){
if(v[l]==v[r])dp[l][r]=max(dp[l+1][r-1]+1,max(dp[l+1][r],dp[l][r-1]));
else dp[l][r]=max(dp[l+1][r],dp[l][r-1]);
mx=max(dp[l][r],mx);
}
}
}
cout<<len-1-mx<<endl;
// cout<<mx<<endl;
}