2021“MINIEYE杯”中国大学生算法设计超级联赛 第九场题解

前几场太拉胯了,也就偷懒不写题解了。(这回其实爆零了

7067 Just another board game

题意:
给你一个棋盘,对于每个坐标i,j对应一个数值a[i][j],两个人玩游戏。

规则如下:

先手方只能在当前行移动,他想要最终停下的值最大;

后手方只能在当前列移动,他想要最终停下的值最小;

任何一个人喊停 或者 达到了最大的轮次数量(题目上给的是K),游戏结束,当然那个值也就确定了。

然后询问给定n*m的棋盘上面的数字,再给定一个K(最大移动轮次),最终的值是多大?

假设他俩都以最优的方式进行移动。

一个人移动一次算1轮,也就是之后只剩下K - 1轮了。

思路:

一个很简单的贪心:

先手方需要值最大,那么他肯定移动当前行的最大值上面;

后手方需要值最小,那么他肯定移动当前列的最小值上面;

然后我们来讨论K这个值:

显然,K = 1的时候,先手肯定移动到当前行(第一行)的最大值,然后这个游戏就结束了

其次:

我们知道最终决定权(最后谁判定)在于K是奇数还是偶数:

因为K为奇数时:

先手最后一次移动,而且必定拿走当前行的最大值,这是肯定的,下面简单说明一下:

K为奇数,那么最终游戏结束前,一定是先手在移动的:

因为

如果不喊停,结束之前先手一定移动,那么拿走当前行最大值

如果先手喊停,一定是到达某一行的最大值处(不然他就可以移动到某一行的最大值处

如果后手喊停,也一定是到达某一行的最大值处(这时候先手已经移动完毕了

那么后手要使得这个值最小,他一定使得最终停在最大值最小的那一行上面

So:这样K为奇数的情况就有了

K为偶数时:

同样的,后手一定最后一次移动,而且必定拿走当前列的最小值,和上面的想法是完全一致的!

So:这样K为偶数的情况就是  最小值最大的那一列上面

注意特判!

最后还要和a[1][1]取最大值(因为最开始就在1,1处,先手可以直接拿走,结束游戏

番外一:比赛时只花了10分钟思考,第一发WA了就跑了,QAQ

最后拿两个数组记录一下 列的最小值和行的最大值即可QAQ

 

#include <cstdio>
#include <cstring>
using namespace std;
const int MAXN = 1e5 + 7;
const int inf = 1e9 + 7;
typedef long long ll;
inline int max(int x,int y)
{return x > y ? x : y;}
inline int min(int x,int y)
{return x < y ? x : y;}
int Mincol[MAXN];
int Maxrow[MAXN];
/*
1
2 2 3
4 2
2 3
*/
int main()
{
//    freopen("input.txt","r",stdin);
//    freopen("ans.out","w",stdout);
    int t;
    scanf("%d",&t);
    while(t--)
    {
        int n,m;
        ll k;
        scanf("%d %d %lld",&n,&m,&k);
        memset(Mincol,0x3f,sizeof Mincol);
        memset(Maxrow,0,sizeof Maxrow);
        int ans = 0;
        if(k == 1)
        {
            for(int i = 1;i <= n;++i)
            for(int j = 1;j <= m;++j)
            {
                int x;
                scanf("%d",&x);
                if(i == 1)
                ans = max(ans,x);
            }
        }
        else
        {
            int fir = 0;
            for(int i = 1;i <= n;++i)
            for(int j = 1;j <= m;++j)
            {
                int x;
                scanf("%d",&x);
                if(i == 1 && j == 1)    fir = x;
                Mincol[j] = min(Mincol[j],x);
                
                Maxrow[i] = max(Maxrow[i],x);
            }
            if(k&1)
            {
                ans = inf;
                for(int i = 1;i <= n;++i)
                ans = min(ans,Maxrow[i]);
                
                ans = max(ans,fir);
            }
            else
            {
                ans = -inf;
                for(int i = 1;i <= m;++i)
                ans = max(ans,Mincol[i]);
                
                ans = max(ans,fir);
            }
        }
        printf("%d\n",ans);
    }
    return 0;
}
View Code

 

7068  Dota2 Pro Circuit

题意:

给定N支队伍,他们有起始分数Ai,以及比赛后会获得的分数Bi。

每一个Bi只能拿一次,要你求出最终N支队伍的最好和最坏的排名是多少。

思路:

对于 最好 排名:

对于任意一支队伍Ai起始分数,肯定拿走最大的Bk分数

然后判定可能在他前面的队伍数量

这些队伍的起始分数一定大于Ai,而且其中最小的起始分数Aj尽量拿走较大的Bh分数使得小于Ai + Bk

不然的话(如果最小的起始分数Aj,拿走最小的Bh分数还是大于Ai + Bk那么最好排名就是 j + 1

因此由于两个序列的单调性,我们使用双指针求出这个j + 1

具体做法是,A序列按照大到小排序

B序列按照大到小排序(题目已经排好

只分析最好情况,最坏情况类似:

对于任意一个Ai,能够排名超过它(Ai + B1)只能是 1 ~ i - 1的队伍

对于Ai - 1这支队伍,肯定优先从B2开始寻找Bk使得

  \[{A \left[ i \left] \text{ }+B \left[ 1 \left]  > =A \left[ i-1 \left] +B \left[ k \right] \right. \right. \right. \right. \right. \right. }\]

现在选的B一定不会影响之后的A进行选择。

证明:

因为当前Ai - 1 + Bk > Ai + B1的

那么由于A是递减有序的对于任意一个1 ~ i - 2的A,加上Bk也一定大于Ai + B1,也会抛弃这个Bk从而找后面的...

因此先拿走较大的B无后效性!

这样我们就可以通过双指针O(n)复杂度找出 j + 1 也即使当前队伍的最好排名。

#include <cstdio>
#include <algorithm>
#include <unordered_map>
using namespace std;
const int MAXN = 5005;
int read()
{
    int x = 0,f = 1;
    char c = getchar();
    while(c > '9' || c < '0')    {if(c == '-')    f = -1;c = getchar();}
    while(c >= '0' && c <= '9')    {x = (x << 3) + (x << 1) + c - '0';c = getchar();}
    return x * f;
}
struct node{
    int dat;
    int idx;
}a[MAXN];
bool cmp(node a,node b)
{return a.dat > b.dat;}
int b[MAXN];
int ans[MAXN][2];
/*
1
3
1 1 1 
1 1 1
*/
unordered_map<int,int> mp[2];
bool cmp1(node a,node b)
{
    return a.idx < b.idx;
}
int main()
{
//    freopen("in.txt","r",stdin);
//    freopen("ans.out","w",stdout);
    int t = read();
    while(t--)
    {
        mp[0].clear();mp[1].clear();
        int n = read();
        for(int i = 1;i <= n;++i)    a[i].idx = i,a[i].dat = read();
        for(int i = 1;i <= n;++i)    b[i] = read();
        
        sort(a + 1,a + n + 1,cmp);
        for(int i = 1;i <= n;++i)
        {
            int le = i - 1,ri = 2;
            int now = a[i].dat + b[1];
            while(le >= 1 && ri <= n)
            {
                while(le >= 1 && ri <= n && a[le].dat + b[ri] > now)
                ++ri;
                if(ri > n)    break;
                --le,++ri;
                if(le < 1)    break;
            }
            ans[a[i].idx][0] = le + 1;
            if(mp[0].count(a[i].dat) == 0)    mp[0][a[i].dat] = le + 1;
            mp[0][a[i].dat] = min(mp[0][a[i].dat],le + 1);
            
            le = i + 1,ri = n - 1;
            now = a[i].dat + b[n];
            while(le <= n && ri >= 1)
            {
                while(le <= n && ri >= 1 && a[le].dat + b[ri] <= now)
                --ri;
                if(ri < 1)    break;
                ++le,--ri;
                if(le > n)    break;
            }
            ans[a[i].idx][1] = le - 1;
            if(mp[1].count(a[i].dat) == 0)    mp[1][a[i].dat] = le - 1;
            mp[1][a[i].dat] = min(mp[1][a[i].dat],le - 1);
        }
        sort(a + 1,a + n + 1,cmp1);
        for(int i = 1;i <= n;++i)
        printf("%d %d\n",mp[0][a[i].dat],mp[1][a[i].dat]);
    }
    return 0;
}
View Code

这里使用map存是因为对于同一为Ai的队伍,他们的值应该都是相同的!

番外二:不想离散化

未完待续....

7072  Boring data structure problem

题意:

标准的数据结构题...

给定四个操作:

①尾部添加一个元素

②首部添加一个元素

③删除值为x的元素

④求中间那个数

每次添加的数字只能是从1开始递增的

思路:

应该是很容易想到使用数组模拟的吧(当然

但是删除的话我们会得到一个O(q²)的算法...显然不好接受

然后对于单点删除,我们就会想到树形结构或者链表结构...

很显然,这题用链表好做!

具体方法

维护首尾指针、各个数值的链表位置指针

对于插入和删除我们都可以O(1)回答

当然,我们要动态维护这个中间数的指针mid_pos...

然后这个题就做完啦...


下面讲解一下我的代码

对于链表,我的做法是:

开一个1e7多一点的数组

维护mid_pos左边有多少个节点,mid_pos右边有多少个节点

维护le和ri指针代表链表的首尾指针

插入:

①然后最初从数组最中间作为起始点进行插入

②每次左边插入的时候,le - 1作为新结点,右边插入ri + 1作为新结点...(其实就是数组下标

③如果在左边插入,lecnt + 1,否则ricnt + 1,通过比较lecnt和ricnt我们可以维护mid_pos

④保存当前结点的指针mp[val] = pos

删除:

①将当前删除下标和当前mid_pos进行比较就能维护lecnt和ricnt,进而维护mid_pos

②链表的结点删除就不用多说了吧

再唠叨一下lecnt和ricnt怎么维护mid_pos

lecnt = ricnt + 1 or lecnt = ricnt + 2这时候mid_pos不变化

lecnt <= ricnt 右移mid_pos

lecnt > ricnt + 2 左移mid_pos

点击查看代码
#include <cstdio>
using namespace std;
const int MAXN = 1e7 + 8e6;
int read()
{
    int x = 0,f= 1;
    char c = getchar();
    while(c > '9' || c < '0')    {if(c == '-')    f = -1;c = getchar();}
    while(c >= '0' && c <= '9')    {x = (x << 3) + (x << 1) + c - '0';c = getchar();}
    return x * f;
}
struct node{
    int le,ri;
    int data;
}a[MAXN];
int mid_pos = 0;
int tot;//现在总数
int le = -1,ri = -1; 
int lecnt = 0,ricnt = 0;
/*
20
L
L
L
L
G 1
G 2
G 3
R
Q
G 5
Q


*/
void erase(int x)
{//将x位置删除
    a[a[x].le].ri = a[x].ri;
    a[a[x].ri].le = a[x].le;
    if(x > mid_pos)
    ricnt -= 1;
    else
    lecnt -= 1;
    
    if(x == mid_pos)
    {
        if(tot&1)    mid_pos = a[mid_pos].ri,lecnt += 1,ricnt -= 1;
        else mid_pos = a[mid_pos].le;
    }
    
    if(lecnt > ricnt + 2)    mid_pos = a[mid_pos].le,ricnt += 1,lecnt -= 1;
    else if(ricnt >= lecnt)    mid_pos = a[mid_pos].ri,lecnt += 1,ricnt -= 1;
    
    if(x == le)
    le = a[x].ri;
    if(x == ri)
    ri = a[x].le;
    
    if(le == 0 && ri == 0)
    ri = le = -1;
    tot -= 1;
}
int mp[int(1e7) + 1];
void ins(int &pos,int val,int op)
{
    if(pos == -1)
    {
        a[MAXN/2].le = 0;
        a[MAXN/2].ri = 0;
        a[MAXN/2].data = val;
        mp[val] = MAXN/2;
        le = ri = MAXN/2;
        ++tot;
        mid_pos = le;
        lecnt = 1;ricnt = 0;
        return ;
    }
    if(op == 0)
    {
        a[pos].le = pos - 1;
        a[pos - 1].le = 0;
        a[pos - 1].ri = pos;
        a[pos - 1].data = val;
        pos = pos - 1;
        lecnt += 1;
    }
    else
    {
        a[pos].ri = pos + 1;
        a[pos + 1].le = pos;
        a[pos + 1].ri = 0;
        a[pos + 1].data = val;
        pos = pos + 1; 
        ricnt += 1;
    }
    mp[val] = pos;
    ++tot;
    
    if(lecnt == ricnt + 1 || lecnt == ricnt + 2)    return ;
    if(lecnt > ricnt + 2)    mid_pos = a[mid_pos].le,ricnt += 1,lecnt -= 1;
    else if(ricnt >= lecnt)    mid_pos = a[mid_pos].ri,lecnt += 1,ricnt -= 1;
}
/*
20
L
R
R
Q
G 2
Q
G 3
Q
*/ 
int main()
{
//    freopen("input.txt","r",stdin);
//    freopen("ans.out","w",stdout);
    int q;
    int now = 1;
    scanf("%d",&q); 
    char c;int x;
    
    mid_pos = -1;
    while(q--)
    {
        scanf(" %c",&c);
        if(c == 'L')
        ins(le,now++,0);
        else if(c == 'R')
        ins(ri,now++,1);
        else if(c == 'G')
        {
            scanf("%d",&x);
            erase(mp[x]);
        }
        else
        printf("%d\n",a[mid_pos].data);
    }
    return 0;
}
View Code

7075 Unfair contest

题意:

还是两个人玩游戏(算是吧,

已经给出了A玩家和B玩家前N - 1个分数,现在要你在去掉s个最高分,去掉t个最低分的情况下,给出A玩家和B玩家的第N个分数

使得A玩家的分数严格大于B玩家,要求给出第N个分数的差值最小值,即An - Bn

思路:

①取得An - Bn最小值

那么就是要得到An尽可能小,Bn尽可能大

②A玩家严格大于B玩家

还要满足①,那么让A玩家的最终分数恰好为B玩家的最终分数 + 1即可

③去掉最高分最低分

那么我们就可以得到A玩家和B玩家的一个上下界,无论怎么取数,最终的分数都会在这个上下界中

定义:

前N - 1个有效分数S:删除了s个最高分,t个最低分

第t小数Kt、第s大数Ks

显然,对于一名玩家的

下界就是

Le = S + Kt

上界即

Ri = S + Ks

因此仔细把玩会发现一下几种情况:

①ALe > BRi

Ans = 1 - h

②ARi <= BLe

Ans = IMPOSSIBLE

③其他情况

而在这个情况中,我们一定能够找到那么一个数,使得Sa = Sb + 1

分析1:ALe >= BLe

那么这时候我只需要A,B都是下界即可,还需要答案最小

那么A显然取1

这样就有公式:

\[{Sa+a \left[ t \left] =Sb+k+1\right. \right. }\]

这个k是B最重取得的数,可以求得:

\[{k=Sa+a \left[ t \left] -Sb-1\right. \right. }\]

那么显然答案就是:

1-k了

分析2:

ARi >= BRi

那么这时候只需要A,B都是上界,那么B拿上界(显然B是拿h的,会使得Bn最大

就有公式

\[{Sa+k=Sb+b \left[ n-s \left] +1\right. \right. }\]

得到

\[k=Sb-Sa+b \left[ n-s \left] +1\right. \right. \]

最后答案即

\[\begin{array}{*{20}{l}}{k-h=Sb-Sa+b \left[ n-s \left] +1-h\right. \right. }\\{\text{ }\text{ }\text{ }\text{ }\text{ }\text{ }\text{ }=Sb+1-Sa- \left( h-b \left[ n-s \left]  \right) \right. \right. }\end{array}\]

点击查看代码
#include <cstdio>
#include <algorithm>
using namespace std;
int read()
{
    int x = 0,f = 1;
    char c = getchar();
    while(c > '9' || c < '0')    {if(c == '-')    f = -1;c = getchar();}
    while(c >= '0' && c <= '9')    {x = (x << 3) + (x << 1) + c - '0';c = getchar();}
    return x * f;
}
const int MAXN = 1e5 + 7;
int a[MAXN];
int b[MAXN];
typedef long long ll;
int main()
{
    int T = read();
    while(T--)
    {
        ll s1 = 0,s2 = 1;
        int n = read(),s = read(),t = read(),h = read();
        for(int i = 1;i < n;++i)    a[i] = read();
        for(int i = 1;i < n;++i)    b[i] = read();
        sort(a + 1,a + n);
        sort(b + 1,b + n);
        a[0] = b[0] = 1;
        b[n] = a[n] = h;
        
        for(int i = t + 1;i < n - s;++i)    s1 += a[i],s2 += b[i];
        ll la = s1 + a[t],ra = s1 + a[n - s];
        ll lb = s2 + b[t],rb = s2 + b[n - s];
        
        if(ra < lb)    puts("IMPOSSIBLE");
        else
        {
            if(la >= rb)    printf("%d\n",1 - h);
            else
            {
                int r = 0;
                if(la >= lb)    r = max(r,a[t] - 1);
                if(ra >= rb)    r = max(r,h - b[n - s]);
                printf("%d\n",s2 - s1 - r);
            }
        }
    }
    return 0;
}
View Code

路还很长,慢慢走,享受这个过程。

 

posted @ 2021-08-18 11:33  K0njac  阅读(181)  评论(0编辑  收藏  举报