修养身心打水题 Div 3 后面几题还是有点难度的
比赛链接
A. Linear Keyboard
难度: \(800\) (巨水)
题目大意:
给你一个按键打乱只有一行的键盘,你可以初始在任意位置,若第 \(i\) 个键在位置 \(x_i\) ,下个键 \(j\) 在 \(x_j\) 则问你需要 \(|x_i-x_j|\) 的时间来移动 ,输出打出所给序列的所需时间。
思路: 按照题目模拟即可,时间复杂度\(O(TS)\)
代码:
#include<bits/stdc++.h>
using namespace std;
const int N=55;
int t;
int str[30];
char ch[N];
inline void Read()
{
for(int i=1;i<=26;i++)
{
char ch=getchar();
while(!islower(ch)) ch=getchar();
str[ch-'a']=i;
}
}
inline void Work()
{
int ans=0;
scanf("%s",ch+1);
for(int i=2;ch[i];i++)
ans+=abs(str[ch[i]-'a']-str[ch[i-1]-'a']);
printf("%d\n",ans);
}
int main()
{
scanf("%d",&t);
while(t--)
{
Read();
Work();
}
return 0;
}
B. Odd Grasshopper
难度: \(900\) (没那么水)
题目大意:
一条无限长的数轴上有一个点,一开始在 \(x_0\) 处。它会移动 \(n\) 次,第 \(i\) 次它会按照以下规则移动:
- 如果它当前所处的位置上的数 \(x\) 是奇数,则它会移动到 \(x+i\) ,否则它会移动到 \(x-i\)。
求移动 \(n\) 次后该点所处的位置上的数。
思路:
按照题目模拟可以发现每过 \(4\) 步都会回到原点,而其他步数与出发点的奇偶有关
设出发点为奇数时与出发点相对位置为\(f_1()\) 偶数时为 \(f_2()\) 可以推出
时间复杂度\(O(T)\)
代码:
#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
int t;
ll st,n;
inline void Read()
{
scanf("%lld%lld",&st,&n);
}
inline void Work()
{
if(st&1)
{
if(n%4==0) printf("%lld\n",st);
if(n%4==1) printf("%lld\n",st+n);
if(n%4==2) printf("%lld\n",st-1);
if(n%4==3) printf("%lld\n",st-n-1);
}
else
{
if(n%4==0) printf("%lld\n",st);
if(n%4==1) printf("%lld\n",st-n);
if(n%4==2) printf("%lld\n",st+1);
if(n%4==3) printf("%lld\n",st+n+1);
}
}
int main()
{
scanf("%d",&t);
while(t--)
{
Read();
Work();
}
return 0;
}
C. Minimum Extraction
难度: \(1000\) (似乎比 \(B\) 题还水 ?)
题目大意:
给你一个序列 ,每次可以选择操作将序列中最小的一个数取出,再将序列剩下的所有数加上它。
再若干次操作后可以使序列中的最小值最大,请求出这个最大的最小值
思路:
排序后按照题目模拟并且动态更新答案即可,时间复杂度\(O(nlogn)\)
代码:
#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
const int N=2e5+10;
const ll INF=0X3f3f3f3f3f3f3f3f;
int t,n;
int a[N];
ll ans,res;
inline void Read()
{
scanf("%d",&n);
for(int i=1;i<=n;i++)
scanf("%d",&a[i]);
}
inline void Work()
{
ans=-INF,res=0;
sort(a+1,a+n+1);
for(int i=1;i<=n;i++)
{
ans=max(ans,a[i]-res);
res=a[i];
}
printf("%lld\n",ans);
}
int main()
{
scanf("%d",&t);
while(t--)
{
Read();
Work();
}
return 0;
}
D. Blue-Red Permutation
难度: \(1300\) (淼)
题目大意:
给你一个序列 ,序列中每个数有红色和蓝色两种 ,你可以将颜色为红色的数减掉任意值,颜色为蓝色的数加上任意值
问若干次操作后可以使序列中的值为 \(1 \sim n\)
思路:
对于红色的数和蓝色的数分别从小到大处理,根据贪心思想:当目前需要的值过大时,蓝色的数就没有用了,所以对于一个所需要的数,优先使用蓝色的,再用红色的,如果都不能用了,就无解了。时间复杂度\(O(nlogn)\)
代码:
#include<bits/stdc++.h>
using namespace std;
const int N=2e5+10;
int t,n;
int a[N];
multiset<int> blue,red;
inline void Read()
{
scanf("%d",&n);
for(int i=1;i<=n;i++)
scanf("%d",&a[i]);
blue.clear(),red.clear();
for(int i=1;i<=n;i++)
{
char ch;
do ch=getchar();
while(!isupper(ch));
if(ch=='B') blue.insert(a[i]);
else red.insert(a[i]);
}
}
inline void Work()
{
for(int i=1;i<=n;i++)
{
auto it=blue.begin(),jt=red.begin();
if(it!=blue.end()&&*it>=i) blue.erase(it);
else if(jt!=red.end()&&*jt<=i) red.erase(jt);
else return (void)puts("NO");
}
puts("YES");
}
int main()
{
scanf("%d",&t);
while(t--)
{
Read();
Work();
}
return 0;
}
E. Robot on the Board 1
难度: \(1600\) (淼)
题目大意:
有一个机器人在 \(n \times m\) 的棋盘上行走(每次只能上下左右移动),给你一个操作序列 \(s\) ,问你机器人从哪开始可以完成最多的操作而不超过边界。
思路:
由于棋盘是矩形的特性,我们只用管机器人路径的上下距离极差与左右距离极差即可。直接模拟记录答案并且在不合法时跳出即可。时间复杂度\(O(nm)\)
代码:
#include<bits/stdc++.h>
using namespace std;
const int N=1e6+10;
const int INF=0X3f3f3f3f;
int t;
int n,m;
char str[N];
int minx,miny,maxx,maxy,ansx,ansy;
inline void Read()
{
scanf("%d%d",&n,&m);
scanf("%s",str+1);
}
inline void Work()
{
int x=0,y=0;
ansx=ansy=1,minx=maxx=miny=maxy=0;
for(int i=1;str[i];i++)
{
if(str[i]=='U') x--;
if(str[i]=='D') x++;
if(str[i]=='L') y--;
if(str[i]=='R') y++;
minx=min(minx,x),miny=min(miny,y);
maxx=max(maxx,x),maxy=max(maxy,y);
if(maxx-minx>=n||maxy-miny>=m) break;
ansx=-minx+1,ansy=-miny+1;
}
printf("%d %d\n",ansx,ansy);
}
int main()
{
scanf("%d",&t);
while(t--)
{
Read();
Work();
}
return 0;
}
F. Robot on the Board 2
难度: \(2300\) (需要动脑)
题目大意:
还是那个机器人,在 \(n \times m\) 的棋盘上行走(每次只能上下左右移动),每个格子都有一个固定操作,机器人除了不能超过边界外还不能走到走过的格子上,问从哪出发可以最大化走过的路径长
思路:
由于每个格子上的操作都是固定的,我们可以得知当我们到了一个格子上时有会有一个固定的结局,掉出边界 \(or\) 走到环上。
所以我们可以考虑记忆化搜索,记录每个格子到自己的终点的距离。注意环上的点的答案要全部设定为环长(显然)。
时间复杂度\(O(nm)\)
代码:
#include<bits/stdc++.h>
using namespace std;
typedef pair<int,int> PII;
const int N=2010;
int t,n,m;
char str[N][N];
int ansx,ansy,ans;
int d[N][N];
vector<PII> path;
inline bool Check(int x,int y)
{
return x>0&&y>0&&x<=n&&y<=m;
}
inline void Read()
{
scanf("%d%d",&n,&m);
for(int i=1;i<=n;i++)
scanf("%s",str[i]+1);
}
inline void Work()
{
ans=ansx=ansy=0;
for(int i=1;i<=n;i++)
for(int j=1;j<=m;j++)
{
if(d[i][j]>0) continue;//记忆化
int nowx=i,nowy=j,len=0;
while(Check(nowx,nowy)&&d[nowx][nowy]==0)//直接进行搜索
{
d[nowx][nowy]=--len;
path.push_back({nowx,nowy});
if(str[nowx][nowy]=='L') nowy--;
else if(str[nowx][nowy]=='R') nowy++;
else if(str[nowx][nowy]=='U') nowx--;
else if(str[nowx][nowy]=='D') nowx++;
}
int cnt=1;
if(Check(nowx,nowy))//两种情况:在环上或已经访问了
{
if(d[nowx][nowy]<0)//如果最后是走到环上
{
int circle_len=d[nowx][nowy]-len+1;
for(int i=1;i<=circle_len;i++)
{
auto pos=path.back();
path.pop_back();
d[pos.first][pos.second]=circle_len;
}
}
cnt=d[nowx][nowy]+1;
}
while(path.size())//处理除环外的路径
{
auto pos=path.back();
path.pop_back();
d[pos.first][pos.second]=cnt++;
}
if(d[i][j]>ans)
{
ans=d[i][j];
ansx=i,ansy=j;
}
}
printf("%d %d %d\n",ansx,ansy,ans);
for(int i=1;i<=n;i++)
for(int j=1;j<=m;j++)
d[i][j]=0;//清空本次的查询
}
int main()
{
scanf("%d",&t);
while(t--)
{
Read();
Work();
}
return 0;
}
G. Banquet Preparations 1
难度: \(2200\) (需要动脑)
题目大意:
一共有 \(n\) 道菜,每道菜吃 \(m\) kg 每道菜有 \(a_i\) kg 的鱼肉 \(b_i\) kg 的肉,设每道吃 \(x_i\) 的鱼肉,试最小化
思路:
真阅读理解题
试着化简式子可得
对于式子的左半部分是个定值我们记为 \(sum\) ,式子的右边是一堆范围的和 ,其中每个 \(x_i\) 满足
所以我们可以设
接下来分类讨论
- 若 \(sum \leq 2L\) 将所有值取到最小
- 若 \(sum \leq 2R\) 将所有值取到最大
- 否则先都取最小值,在逐步考虑,能取大就取大即可(具体见代码)
时间复杂度\(O(n)\)
代码:
#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
typedef pair<ll,ll> PLL;
const int N=2e5+10;
int n,m,t;
ll sum,l,r;
int ans[N];
PLL dish[N],range[N];
inline void Read()
{
scanf("%d%d",&n,&m);
sum=l=r=0;
for(int i=1;i<=n;i++)
{
static int a,b;
scanf("%d%d",&a,&b);
sum+=a-b+m;
range[i].first=max(m-b,0),range[i].second=min(m,a);
l+=range[i].first,r+=range[i].second;
}
}
inline void Work()
{
if(2*r<=sum)//开到最大
{
printf("%lld\n",sum-2*r);
for(int i=1;i<=n;i++)
printf("%lld %lld\n",range[i].second,m-range[i].second);
}
else if(sum<=2*l)//最小
{
printf("%lld\n",2*l-sum);
for(int i=1;i<=n;i++)
printf("%lld %lld\n",range[i].first,m-range[i].first);
}
else//贪心分配
{
printf("%lld\n",sum%2);
ll base=(sum>>1)-l;
for(int i=1;i<=n;i++)
{
ans[i]=range[i].first;
int delta=range[i].second-range[i].first;
if(base>=delta)
base-=delta,ans[i]+=delta;
else ans[i]+=base,base=0;
}
for(int i=1;i<=n;i++)
printf("%d %d\n",ans[i],m-ans[i]);
}
}
int main()
{
scanf("%d",&t);
while(t--)
{
Read();
Work();
}
return 0;
}
H. Banquet Preparations 2
难度: \(2200\) (需要动脑)
题目大意:
一共有 \(n\) 道菜,每道菜吃 \(m_i\) kg,每道菜有 \(a_i\) kg 的鱼肉 \(b_i\) kg 的肉,设每道吃 \(x_i\) 的鱼肉,试最小化剩下的不同菜
- 两道菜 \(i\),\(j\)相同当且仅当两道菜 \(a_i=a_j \wedge b_i=b_j\)
思路:
我们可以得到两道菜可能相同必须要满足 \(a_i+b_i-m_i=a_j+b_j-m_j\) ,所以我们可以按照这个值给所有的菜分类。
分类之后设吃完后的 \(a_i\) 为 \(x_i\) 则
令式子左边为 \(l_i\) 式子右边为 \(r_i\)
所以我们把每类的按 \(r_i\) 为第一关键字排序,\(l_i\) 为第二关键字排序,用单指针贪心维护右端点即可。
时间复杂度\(O(nlogn)\)
代码:
#include<bits/stdc++.h>
using namespace std;
typedef pair<int,int> PII;
const int N=2e5+10;
int T,n,res;
struct node
{
int a,b,m;
int l,r,sum,id;
inline bool operator<(const node &t) const
{
if(sum!=t.sum) return sum<t.sum;
if(r!=t.r) return r<t.r;
return l<t.l;
}
}seg[N];
PII ans[N];
inline void Read()
{
scanf("%d",&n);
for(int i=1;i<=n;i++)
{
scanf("%d%d%d",&seg[i].a,&seg[i].b,&seg[i].m);
seg[i].sum=seg[i].a+seg[i].b-seg[i].m;
seg[i].l=max(0,seg[i].a-seg[i].m),seg[i].r=seg[i].a+min(seg[i].b-seg[i].m,0);
seg[i].id=i;
}
}
inline void Work()
{
res=0;
sort(seg+1,seg+n+1);
int maxr=-1;
for(int i=1;i<=n;i++)
{
if(seg[i].sum!=seg[i-1].sum||seg[i].l>maxr)
maxr=seg[i].r,res++;
ans[seg[i].id].first=seg[i].a-maxr;
ans[seg[i].id].second=seg[i].m-ans[seg[i].id].first;
}
printf("%d\n",res);
for(int i=1;i<=n;i++)
printf("%d %d\n",ans[i].first,ans[i].second);
}
int main()
{
scanf("%d",&T);
while(T--)
{
Read();
Work();
}
return 0;
}