周赛48D小红的乘2除2
题目描述
小红拿到了一个数组。她需要进行恰好一次以下操作:
先使得一个元素除以2,向下取整。然后使得一个元素乘以2。
小红希望操作后,数组的陡峭值尽可能小。你能帮帮她吗?
定义数组的陡峭值为:相邻两数之差的绝对值之和。例如,[2,3,1]的陡峭值为|2-3|+|3-1|=3。
输入描述:
第一行输入一个正整数nnn,代表数组大小。 第二行输入nnn个正整数aia_iai,代表小红拿到的数组。 2≤n≤1052 \leq n \leq 10^52≤n≤105 1≤ai≤1091 \leq a_i \leq 10^91≤ai≤109
输出描述:
一个整数,代表最终数组的陡峭值的最小值。
示例1
输入
复制3 2 3 2
3 2 3 2
输出
复制0
0
说明
先对第二个元素除以2,然后对第二个元素乘以2即可。
示例2
输入
复制4 1 2 3 4
4 1 2 3 4
输出
复制2
2
说明
先对第四个元素除以2,然后对第一个元素乘以2。
做法:
分类讨论,i和j的位置有三种情况。一是i=j,即除以2和乘以2是同一个数。二是i+1=j,即除以二的数和乘以二的数相邻。三是i和j的距离大于1,这时i和j互不影响。
#include<bits/stdc++.h>
using namespace std;
int n;
long long res;//原始陡峭值
long long ans=0x3f3f3f3f3f3f3f3f,ans2=0x3f3f3f3f3f3f3f3f;
long long a[100010];
int main(){
vector<int> v;
scanf("%d",&n);
for(int i=1;i<=n;i++){
scanf("%lld",&a[i]);
if(i!=1)res+=fabs(a[i]-a[i-1]);
}
//特判n=2的情况
if(n==2){
long long cnt=fabs(a[1]/2*2-a[2]),cnt2=fabs(a[1]-a[2]/2*2);
cnt=min(cnt,cnt2);
cnt2=fabs(a[1]/2-a[2]*2);
cnt=min(cnt,cnt2);
cnt2=fabs(a[1]*2-a[2]/2);
cnt=min(cnt,cnt2);
cout<<cnt;
return 0;
}
//找出除以二后陡峭值最小的坐标
int idex;
for(int i=1;i<=n;i++){
long long cnt;
if(i==1){
cnt=res-fabs(a[i+1]-a[i])+fabs(a[i+1]-a[i]/2);
if(ans>cnt){
ans=cnt;
idex=i;
}
}
else if(i==n){
cnt=res-fabs(a[i]-a[i-1])+fabs(a[i]/2-a[i-1]);
if(ans>cnt){
ans=cnt;
idex=i;
}
}
else{
cnt=res-fabs(a[i]-a[i-1])-fabs(a[i+1]-a[i])+fabs(a[i+1]-a[i]/2)+fabs(a[i]/2-a[i-1]);
if(ans>cnt){
ans=cnt;
idex=i;
}
}
}
//看是否有多个数除以二后陡峭值最小,下标放入vector
for(int i=1;i<=n;i++){
long long cnt;
if(i==1){
cnt=res-fabs(a[i+1]-a[i])+fabs(a[i+1]-a[i]/2);
if(ans==cnt){
v.push_back(i);
}
}
else if(i==n){
cnt=res-fabs(a[i]-a[i-1])+fabs(a[i]/2-a[i-1]);
if(ans==cnt){
v.push_back(i);
}
}
else{
cnt=res-fabs(a[i]-a[i-1])-fabs(a[i+1]-a[i])+fabs(a[i+1]-a[i]/2)+fabs(a[i]/2-a[i-1]);
if(ans==cnt){
v.push_back(i);
}
}
}
//找出乘以2后陡峭值最小的 (因为两个数互不影响,所以可以分开求)
for(int j=0;j<v.size();j++){
if(j==v[j]||j==v[j]+1||j==v[j]-1) continue;//除去其他两种情况
int tmp=a[v[j]];
a[v[j]]/=2;
for(int i=1;i<=n;i++){
long long cnt;
if(i==1){
cnt=ans-fabs(a[i+1]-a[i])+fabs(a[i+1]-a[i]*2);
if(ans2>cnt){
ans2=cnt;
}
}
else if(i==n){
cnt=ans-fabs(a[i]-a[i-1])+fabs(a[i]*2-a[i-1]);
if(ans2>cnt){
ans2=cnt;
}
}
else{
cnt=ans-fabs(a[i]-a[i-1])-fabs(a[i+1]-a[i])+fabs(a[i+1]-a[i]*2)+fabs(a[i]*2-a[i-1]);
if(ans2>cnt){
ans2=cnt;
}
}
}
a[v[j]]=tmp;
}
//除以二,乘以二为同一个数
for(int i=1;i<=n;i++){
long long cnt;
if(i==1){
cnt=res+fabs(a[i]/2*2-a[i+1])-fabs(a[i]-a[i+1]);
}
else if(i==n){
cnt=res+fabs(a[i-1]-a[i]/2*2)-fabs(a[i-1]-a[i]);
}
else {
cnt=res+fabs(a[i]/2*2-a[i+1])-fabs(a[i]-a[i+1])+fabs(a[i-1]-a[i]/2*2)-fabs(a[i-1]-a[i]);
}
ans2=min(ans2,cnt);
}
//除以二的数和乘以二的数相邻
for(int i=1;i<n;i++){
long long cnt1,cnt2;
if(i==1){
cnt1=res+fabs(a[i]/2-a[i+1]*2)-fabs(a[i]-a[i+1])+fabs(a[i+1]*2-a[i+2])-fabs(a[i+1]-a[i+2]);
cnt2=res+fabs(a[i]*2-a[i+1]/2)-fabs(a[i]-a[i+1])+fabs(a[i+1]/2-a[i+2])-fabs(a[i+1]-a[i+2]);
}
else if(i==n-1){
cnt1=res+fabs(a[i-1]-a[i]/2)-fabs(a[i-1]-a[i])+fabs(a[i]/2-a[i+1]*2)-fabs(a[i]-a[i+1]);
cnt2=res+fabs(a[i-1]-a[i]*2)-fabs(a[i-1]-a[i])+fabs(a[i]*2-a[i+1]/2)-fabs(a[i]-a[i+1]);
}
else{
cnt1=res+fabs(a[i-1]-a[i]/2)-fabs(a[i-1]-a[i])+fabs(a[i]/2-a[i+1]*2)-fabs(a[i]-a[i+1])+fabs(a[i+1]*2-a[i+2])-fabs(a[i+1]-a[i+2]);
cnt2=res+fabs(a[i-1]-a[i]*2)-fabs(a[i-1]-a[i])+fabs(a[i]*2-a[i+1]/2)-fabs(a[i]-a[i+1])+fabs(a[i+1]/2-a[i+2])-fabs(a[i+1]-a[i+2]);
}
ans2=min(cnt1,ans2);
ans2=min(cnt2,ans2);
}
cout<<ans2;
}
WA的原因
我只考虑到乘以2的数和除以2 的数互不影响的情况,没想到其他两种情况,就是没想到这三种情况是不一样的。我只想着先找到除以二使得陡峭值最小的数,再找乘以二使得陡峭值最小的数就行了,没想到他们的位置关系也是会有影响的,会多减多加一些数。
周赛48F小红的迷宫行走
题目描述
小红来到了一个矩形迷宫,每个房间写着一个数字。小红初始在左上角的房间,她准备前往该迷宫的右下角房间,每次小红可以向右或者向下行走一步。
另外,小红可以进行若干次传送,每次可以花费1的步数,前往和当前房间不互素的任意一个房间。
现在,小红希望你求出从左上角走到右下角的最小步数。你能帮帮她吗?
输入描述:
第一行输入两个正整数n,mn,mn,m,代表迷宫的行数和列数。 接下来的nnn行,每行输入mmm个正整数aija_{ij}aij,用来表示每个房间的数字。 1≤n,m≤5001\leq n,m \leq 5001≤n,m≤500 1≤aij≤1051\leq a_{ij} \leq 10^51≤aij≤105
输出描述:
一个整数,代表左上角走到右下角的最小步数。
示例1
输入
复制3 3 1 2 3 2 3 3 3 4 5
3 3 1 2 3 2 3 3 3 4 5
输出
复制3
3
做法
最短路加边,大约有2e5个点,每个点都建边的话肯定会爆。不互素就可以传送,那就是两个数有一个相同的质因子就可以传送,那么我们只需要找出地图中每个数的最小质因子,根据每个最小质因子搭个平台,中转一下。上去平台需要花费1代价,从下来到达某点花费0代价,从平台可以走向任何一个最小质因数和该平台相同的数。该过程既是传送。然后跑一遍最短路即可。
#include <bits/stdc++.h>
using namespace std;
int n, m;
int a[510][510];
struct ty
{
int id, w;
};
struct ty2
{
int id, dis;
bool operator<(const ty2 &a) const
{
return dis > a.dis;
}
};
vector<ty> v[400010];
int p[100010];
int dis[400010], vis[400010];
priority_queue<ty2> q;
void dij()
{
q.push({0, 0});
dis[0] = 0;
while (!q.empty())
{
ty2 tmp = q.top();
q.pop();
if (vis[tmp.id])
continue;
vis[tmp.id] = 1;
for (int i = 0; i < v[tmp.id].size(); i++)
{
if (vis[v[tmp.id][i].id])
continue;
if (dis[tmp.id] + v[tmp.id][i].w < dis[v[tmp.id][i].id])
{
dis[v[tmp.id][i].id] = dis[tmp.id] + v[tmp.id][i].w;
q.push({v[tmp.id][i].id, dis[v[tmp.id][i].id]});
}
}
}
}
int main()
{
for (int i = 2; i <= 100000; i++)//找出最小质因子
{
if (p[i])
continue;
for (int j = i; j <= 100000; j += i)
{
if (!p[j])
p[j] = i;
}
}
cin >> n >> m;
for (int i = 0; i < n; i++)
{
for (int j = 0; j < m; j++)
{
scanf("%d", &a[i][j]);
}
}
for (int i = 0; i < n; i++)//建边
{
for (int j = 0; j < m; j++)
{
if (i != n - 1)
{ // 向下
v[i * m + j].push_back({(i + 1) * m + j, 1});
}
if (j != m - 1)
{ // 向右
v[i * m + j].push_back({i * m + j + 1, 1});
}
if (p[a[i][j]] != 0){//平台
v[i * m + j].push_back({300000 + p[a[i][j]], 1});
v[300000 + p[a[i][j]]].push_back({i * m + j, 0});
}
}
}
memset(dis, 0x3f, sizeof(dis));
dij();
if (dis[(n - 1) * m + m - 1] == 0x3f3f3f3f)
cout << -1;
else
cout << dis[(n - 1) * m + m - 1];
}
周赛47F花花的地图
链接:登录—专业IT笔试面试备考平台_牛客网
来源:牛客网
题目描述
A市地图是一个n×mn \times mn×m的网格图,字符'#'表示障碍,'.' 表示空地。花花住在左上角(1,1)(1,1)(1,1),花花的朋友萌萌住在右下角(n,m)(n,m)(n,m)。花花要去萌萌家玩。
花花如果走到空地则不需要任何代价,但是如果要走到障碍点则他需要先摧毁该障碍。每次可以走到相邻的四个方向之一,即从(x,y)(x,y)(x,y)可以走到(x+1,y)(x+1,y)(x+1,y),(x−1,y)(x-1,y)(x−1,y),(x,y+1)(x,y+1)(x,y+1),(x,y−1)(x,y-1)(x,y−1)。
花花可以花费CiC_iCi的代价将第iii列的所有障碍都消灭。
请帮花花计算她去萌萌家最少需要多少代价。
输入描述:
第一行输入两个正整数nnn,mmm,为网格图的大小。 接下来nnn 行,每行mmm 个字符,描述迷宫的地图。字符仅由'.','#',保证起点一定是空地。 接下来一行mmm个正整数CiC_iCi,为消灭第iii列的障碍的代价 1≤n,m≤1001 \leq n,m \leq 1001≤n,m≤100 1≤Ci≤1051 \leq C_i \leq 10^51≤Ci≤105
输出描述:
输出一个整数,为从花花家到萌萌家的最小代价。
示例1
输入
复制4 4 .##. .#.# .### ..#. 5 3 9 4
4 4 .##. .#.# .### ..#. 5 3 9 4
输出
复制7
7
说明
消除第2列和第4列的障碍,总代价为7
做法
给每一列搭一个平台,从相邻的列到平台需要c[j]的代价,从平台到该列的任意一行代价为0。
这题和上题对平台的处理有点不一样,上一题是直接将每个点建一条边到平台上,然后直接跑dij。这题没有建边,直接把符合的点塞进优先队列里,算是从平台上走下来。
#include<bits/stdc++.h>
using namespace std;
int n,m;
char a[110][110];
int dis[20000];
int vis[20000];
int c[110];
struct ty{
int id,w;
};
struct ty2{
int id,dis;
bool operator < (const ty2 &a) const{
return dis>a.dis;
}
};
int dx[]={0,0,1,-1};
int dy[]={1,-1,0,0};
priority_queue<ty2> q;
vector<ty> v[20000];
void dij(){
q.push({0,0});
dis[0]=0;
while(!q.empty()){
ty2 tmp=q.top();
q.pop();
if(vis[tmp.id]) continue;
vis[tmp.id]=1;
int x=tmp.id/m,y=tmp.id%m;
for(int i=0;i<4;i++){
int xx=x+dx[i],yy=y+dy[i];
if(xx<0||xx>=n||yy<0||yy>=m) continue;
if(vis[xx*m+yy]) continue;
if(a[xx][yy]=='#'){//走向平台
for(int j=0;j<n;j++){//平台走向该列的任意一点
if(vis[j*m+yy]) continue;
if(dis[tmp.id]+c[yy]<dis[j*m+yy]){
dis[j*m+yy]=dis[tmp.id]+c[yy];
q.push({j*m+yy,dis[tmp.id]+c[yy]});
}
}
}
else{
if(dis[tmp.id]<dis[xx*m+yy]){
dis[xx*m+yy]=dis[tmp.id];
q.push({xx*m+yy,dis[tmp.id]});
}
}
}
}
}
int main(){
cin>>n>>m;
for(int i=0;i<n;i++){
for(int j=0;j<m;j++){
cin>>a[i][j];
}
}
for(int i=0;i<m;i++) scanf("%d",&c[i]);
memset(dis,0x3f,sizeof(dis));
dij();
cout<<dis[(n-1)*m+m-1];
}
wa的原因
1.段错误(数组开小了,范围算错了qaq)
2.没有想到要搭平台,从该列到别的列直接分类讨论不同的代价然后直接建边了,后来看视频发现这样做不行,还是要搭平台。因为位于同一列遇到#就不知道到底要不要加上代价c[j]。
周赛48E小红的伪回文子串(hard)
链接:登录—专业IT笔试面试备考平台_牛客网
来源:牛客网
题目描述
本题和easy版的唯一区别是数据范围不同!
定义一个字符串的“伪回文值”是:修改最少字符数量使得其变成回文串的修改次数。例如,"abca"的伪回文值是1。任何回文串的伪回文值是0。
现在小红拿到了一个字符串,她希望你求出所有连续子串的伪回文值之和。你能帮帮她吗?
输入描述:
一个仅包含小写字母的字符串。长度不超过200000200000200000
输出描述:
所有连续子串的回文值之和。
示例1
输入
复制abca
abca
输出
复制6
6
说明
所有长度不小于2的子串的伪回文值都是1。其余子串伪回文值是0。
示例2
输入
复制acac
acac
输出
复制5
5
做法
easy版本的用n的3次方也能过
#include<bits/stdc++.h>
using namespace std;
string s;
long long ans;
int main(){
cin>>s;
for(int i=0;i<s.size();i++){
for(int j=1;j+i<=s.size();j++){
string a=s.substr(i,j);
for(int k=0;k<a.size()/2;k++){
if(a[k]!=a[a.size()-1-k]) ans++;
}
}
}
cout<<ans;
}
有一个n的2次方的做法,就是枚举i和j,看s[i]和s[j]这对字母对子串的贡献,但还是过不了hard版
#include<bits/stdc++.h>
using namespace std;
string s;
long long ans;
int main(){
cin>>s;
int n=s.size();
for(int i=0;i<s.size();i++){
for(int j=i+1;j<s.size();j++){
if(s[i]!=s[j]){
ans+=min(i+1,n-j);
}
}
}
cout<<ans;
}
O(n)做法是只枚举i,然后可以发现其中的规律。
#include<bits/stdc++.h>
using namespace std;
string s;
long long ans;
long long qzh[26][200010],sum[26][200010];
int main(){
cin>>s;
s='#'+s;
int n=s.size()-1;
for(int i=1;i<=n;i++){
for(int j=0;j<26;j++){
if(s[i]-'a'!=j) qzh[j][i]=qzh[j][i-1]+1;
else qzh[j][i]=qzh[j][i-1];
if(s[i]-'a'!=j) sum[j][i]=sum[j][i-1]+n+1-i;
else sum[j][i]=sum[j][i-1];
}
}
for(int i=1;i<n;i++){
if(i<(n+1)/2)
ans+=i*(qzh[s[i]-'a'][n+1-i]-qzh[s[i]-'a'][i])+sum[s[i]-'a'][n]-sum[s[i]-'a'][n+1-i];
else
ans+=sum[s[i]-'a'][n]-sum[s[i]-'a'][i];
}
cout<<ans;
}
wa的原因
没开long long(qzh数组不用long long,但sum数组要用,等差数列1到200000会爆)