『解题报告』CF1607 (Codeforces Round #753 (Div. 3))


修养身心打水题 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()\) 可以推出

\[ f_1(x)=\left\{ \begin{aligned} & 0 && n \ mod \ 4 = 0 \\ & -n && n \ mod \ 4 = 1 \\ & 1 && n \ mod \ 4 = 2 \\ & n+1 && n \ mod \ 4=1 \end{aligned} \right. \]

\[\\ f_2(x)=-f_1(x) \]

时间复杂度\(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\) 的鱼肉,试最小化

\[\mid \sum_{i=1}^n(a_i+x_i)-\sum_{i=1}^n(b_i-(m-x_i))\mid \]

思路:
真阅读理解题
试着化简式子可得

\[\mid \sum_{i=1}^n(a_i+b_i-m)-2\sum_{i=1}^nx_i\mid \]

对于式子的左半部分是个定值我们记为 \(sum\) ,式子的右边是一堆范围的和 ,其中每个 \(x_i\) 满足

\[max(m- b_i ,0) \leq x_i \leq min(a_i,m) \]

所以我们可以设

\[L \leq \sum_{i=1}^nx_i \leq R \]

接下来分类讨论

  • \(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\)

\[max(0,a_i-m_i) \leq x_i \leq min(0,b_i-m_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;
}


免责声明!

本站转载的文章为个人学习借鉴使用,本站对版权不负任何法律责任。如果侵犯了您的隐私权益,请联系本站邮箱yoyou2525@163.com删除。



 
粤ICP备18138465号  © 2018-2025 CODEPRJ.COM