杭电第九场 题解

比赛传送门
作者: fn


基本题

1003题 Dota2 Pro Circuit / Dota2职业巡回赛

题目大意
给定每个队伍之前的得分 a i a_i ai 和下一场比赛每个名次的得分 b i b_i bi ,求解每个队伍打完下一场比赛后最好和最坏的名次。

考察内容
贪心,双指针

分析
首先把 a a a 数组放在结构体里,进行一次排序,然后按照贪心策略在 a , b a,b a,b 数组上跑双指针。

最好名次的贪心策略:给当前队伍加上最高分,记为N,按照之前的得分从低到高枚举其他队伍。每一支队伍在不超过N的情况下,给一个尽可能高的得分。在b数组上放一个指针q,可以证明,q只需要一直往后跑就行,单次复杂度 O ( n ) O(n) O(n)
最坏名次的贪心策略:类似地,给当前队伍加上最低分,记为N,按照之前的得分从低到高枚举其他队伍。在b数组上放一个指针q,记录当前能给出的最高分。如果 a i a_i ai 加上最高分都不能超过N ,就直接考虑下一只队伍。如果加上最高分能超过N,就给他最高分,然后q往后移动一格。单次复杂度 O ( n ) O(n) O(n)

枚举n支队伍,总复杂度 O ( n 2 ) O(n^2) O(n2)

O ( n 2 l o g n ) O(n^2logn) O(n2logn) 的做法会被卡掉。

code by lcl:

#include<bits/stdc++.h>
using namespace std;
int t,n;
struct node{
    long long num,val;
}poi[5001]; 
int ans[5001][2];
long long b[5001];
bool cmp(node a1,node a2)
{
    return a1.val<a2.val;
}
void init()
{

    scanf("%d",&n);
    for(int i=1;i<=n;i++) {
	    scanf("%lld",&poi[i].val);
	    poi[i].num=i;
    }
    for(int i=1;i<=n;i++) scanf("%lld",&b[i]);
    sort(poi+1,poi+1+n,cmp);
    return;
}
int maxn(int k)
{
    int N=poi[k].val+b[1],cnt=0;

    int p=1;	
	int q=1;
	while(p<=n && q<=n){
		if(p==k){
			p++;
			continue;
		} 
		if(poi[p].val+b[q]<=N){
			p++;
			cnt++;
		}
		q++;
	}
    
    return n-cnt;
}
int minn(int k)
{
    int d=1,N=poi[k].val+b[n],ans=0;
    //ans超过当前的个数,N为队伍后 

    for(int i=1;i<=n;i++){
        if(i==k) continue;
        if(poi[i].val>N){
            ans++;
            continue;
        }//如果不用加分数就已经超过 
        else
        {
            if(poi[i].val+b[d]>N){
                ans++;
                d++;
            }
        }
    }
    return ans+1;
}
int main() // AC
{
    scanf("%d",&t);
    while(t>0)
    {
        t--;
        init();
        for(int i=1;i<=n;i++){
        	int number=poi[i].num;
        	ans[number][0]=maxn(i);
        	ans[number][1]=minn(i);
        }
        for(int i=1;i<=n;i++){
            printf("%d %d\n",ans[i][0],ans[i][1]);
        }
    }
    return 0;
 } 

1002题 Just another board game / 又一个棋盘游戏

题目大意
两个人下棋,棋盘的每个格子上有一个数字。先手可以同一行左右移动(可以不动),要让停留的数字最大;后手可以同一列上下移动(可以不动),要让停留的数字最小。轮到任何一方走时,他可以选择直接结束游戏,走 k k k 步后游戏也会结束。
给定一个棋盘和步数上限 k k k ,求最后停留位置的数字。

考察内容
贪心,博弈论

分析
根据k分类

  1. k=1,直接输出第一行的最大值。

  2. k为偶数

k为偶数,等价于k=2。这是因为游戏可以随时结束,如果k=4时的结果对先手更有利,后手会选择在k=2时结束;如果k=4时的结果对后手更有利,先手会选择在k=1时结束;所以k=4时的结果和k=2时的结果是等价的。同理,k为偶数时的结果和k=2时的结果是等价的。

设刚开始位置的数字是 v [ 1 ] [ 0 ] v[1][0] v[1][0] ,下面考虑最多走两步的情况。

如果直接结束,答案就是 v [ 1 ] [ 0 ] v[1][0] v[1][0]
如果走了两步,因为最后一步是后手走的,所以必定会会停留在本列的最小值上。
所以预处理出每列的最小值,输出 m a x ( v [ 1 ] [ 0 ] , m a x ( 每 列 最 小 值 ) ) max(v[1][0] ,max(每列最小值)) max(v[1][0]max()) 即可。

  1. k为大于等于3的奇数

k为大于等于3的奇数,等价于k=3,证明和上面同理。下面考虑最多走三步的情况。

如果直接结束,答案就是 v [ 1 ] [ 0 ] v[1][0] v[1][0]

如果走了三步,因为最后一步(第三步)是先手走的,所以必定会会停留在本行的最大值上。后手为了让值最小,会在第二步走到行的最大值最小的那一行。(有点绕… …)
梳理一下
第一步,先手走到第一行的最大值(或者不动);
第二步,后手走到行的最大值最小的那一行;
第三步,先手走这一行的最大值。

所以预处理出每行的最大值,输出 m a x ( v [ 1 ] [ 0 ] , m i n ( 每 行 最 大 值 ) ) max(v[1][0] ,min(每行最大值)) max(v[1][0]min()) 即可。

code by me:

#include<bits/stdc++.h>
#define ll long long
#define cer(x) cerr<<(#x)<<" = "<<(x)<<'\n'
using namespace std;
const int N=1e6+10;
ll n,m,k;
ll min1[N];
ll maxline[N];
vector<ll> v[N];

void init(){
	for(int i=1;i<=n;i++){
		v[i].clear();
	}
}

int main(){ 
	ios::sync_with_stdio(0); cin.tie(0);
	int t; cin>>t;
	while(t--){
		cin>>n>>m>>k;
		
		init();
		
		ll temp;
		for(int i=1;i<=n;i++){
			for(int j=1;j<=m;j++){
				cin>>temp;
				v[i].push_back(temp);
			}
		}
		
		ll max1=0;
		for(int i=0;i<m;i++){
			max1=max(max1,v[1][i]);
		}
		
		for(int i=1;i<=m;i++){
			min1[i]=1e9+10; // inf
		}
		for(int i=0;i<m;i++){
			for(int j=1;j<=n;j++){
				min1[i+1]=min(min1[i+1],v[j][i]);
			}
		}
		for(int i=1;i<=m;i++){
			maxline[i]=0;
		}
		for(int i=1;i<=n;i++){
			for(int j=0;j<m;j++){
				maxline[i]=max(maxline[i],v[i][j]);
			}
		}
		
		if(k==1){
			cout<<max1<<endl;
			continue;
		}
		
		// k>=2
		if(k%2==0){ // 相当于k==2 
			ll maxx=0;
			for(int i=2;i<=m;i++){
				maxx=max(maxx,min1[i]);
			}
			cout<<max(v[1][0],maxx)<<endl;
		}
		else{ // k>=3,奇数 
			ll minn=1e9+10;
			for(int i=1;i<=n;i++){
				minn=min(minn,maxline[i]);
			}
			cout<<max(v[1][0],minn)<<endl;
		}
	}
	return 0;
}

1007题 Boring data structure problem / 无聊的数据结构问题

题目大意
维护一个支持左插入,右插入,按值删除,查找中点(靠右)值的“双端队列”。要求所有操作均为 O(1) 。

考察内容
链表

分析
用两个双链表维护中点。每次“队列”清空后需要重新初始化。

code by pyf:

#include<bits/stdc++.h>
using namespace std;

struct node {
    int pre,nt,v,midData;
} p[10000005];
int q,x,ind,mid,ip,lp,rp,midL,midR,pos[10000005],num;
char c[105];
void LInsert() {
    ++ip;
    if(num==0) {
        p[ip].v=ind;
        p[ip].midData=0;
        pos[ind]=ip;
        lp=ip;
        rp=ip;
        mid=ip;
        num++;
        midR=midL=0;
        return;
    }
    p[lp].pre=ip;
    p[ip].nt=lp;
    p[ip].v=ind;
    p[ip].midData=-1;
    pos[ind]=ip;
    lp=ip;
    midL++;
    num++;
    if(midL-midR>1) {
        midL--;
        midR++;
        p[mid].midData=1;
        mid=p[mid].pre;
        p[mid].midData=0;
    }
    return;
}
void RInsert() {
    ++ip;
    if(num==0) {
        p[ip].v=ind;
        p[ip].midData=0;
        pos[ind]=ip;
        lp=ip;
        rp=ip;
        mid=ip;
        num++;
        midR=midL=0;
        return;
    }
    p[rp].nt=ip;
    p[ip].pre=rp;
    p[ip].v=ind;
    p[ip].midData=1;
    rp=ip;
    pos[ind]=ip;
    midR++;
    num++;
    if(midR-midL>0) {
        midL++;
        midR--;
        p[mid].midData=-1;
        mid=p[mid].nt;
        p[mid].midData=0;
    }
    return;
}
void Remove() {
    int l=p[pos[x]].pre,r=p[pos[x]].nt;
    if(pos[x]==rp) {
        rp=l;
    }
    if(pos[x]==lp) {
        lp=r;
    }
    p[l].nt=r;
    p[r].pre=l;
    num--;
    if(p[pos[x]].midData==0) {
        if(midR==midL) {
            midR--;
            mid=r;
            p[mid].midData=0;
        } else if(midL-midR>0) {
            midL--;
            mid=l;
            p[mid].midData=0;
        }
    } else if(p[pos[x]].midData==-1) {
        midL--;
        if(midR-midL>0) {
        midL++;
        midR--;
        p[mid].midData=-1;
        mid=p[mid].nt;
        p[mid].midData=0;
        }
    } else if(p[pos[x]].midData==1) {
        midR--;
        if(midL-midR>1) {
        midL--;
        midR++;
        p[mid].midData=1;
        mid=p[mid].pre;
        p[mid].midData=0;
        }
    }
    return ;
}
int main(){ // AC
    scanf("%d",&q);
    while(q--) {
        scanf("%s",&c);
        if(c[0]=='L') {
            ++ind;
            LInsert();
        } else if(c[0]=='R') {
            ++ind;
            RInsert();
        } else if(c[0]=='G') {
            scanf("%d",&x);
            Remove();
        } else if(c[0]=='Q') {
            printf("%d\n",p[mid].v);
        }
    }
    return 0;
}

进阶题

1010题 Unfair contest / 黑哨

题目大意
n个裁判给A、B两个人打分,得分为去除s个最高分和t个最低分后的总分。
最后一个裁判给A打 a n a_n an 分,给B打 b n b_n bn 分。他押了A赢,所以他希望A赢的同时 a n − b n a_n-b_n anbn 最小。( a n − b n a_n-b_n anbn 可以是负数)
已知前 n − 1 个 n-1个 n1 裁判给两人的打分,求出 a n − b n a_n-b_n anbn 的最小值,或者在A不可能赢的情况下输出“IMPOSSIBLE”。

分析
在对 a , b a,b a,b 数组排序之后,则新加的 a n ( b n ) a_n(b_n) an(bn) 可分为三类:
s s s 个最高分中,在 t t t 个最低分中,或者两者都不是。
对于每一个类别,我们可以得到 a n ( b n ) a_n(b_n) an(bn) 的可能范围,剩下的只是仔细和彻底的分析。 注意 s = 0 s = 0 s=0 t = 0 t = 0 t=0 的情况。 由于排序,时间复杂度是 O ( n l o g n ) O(n log n) O(nlogn)

std:

#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
const int maxn=1e5+10;
ll a[maxn],b[maxn],h;
int _,n,s,t;
int main(){
    scanf("%d",&_);
    while (_--){
        scanf("%d%d%d%lld",&n,&s,&t,&h); n--;
        assert(s+t<=n);
        for (int i=1;i<=n;i++) scanf("%lld",&a[i]);
        for (int i=1;i<=n;i++) scanf("%lld",&b[i]);
        sort(a+1,a+n+1);
        sort(b+1,b+n+1);
        a[0]=b[0]=1; a[n+1]=b[n+1]=h;
        ll sa=0,sb=1;
        for (int i=t+1;i<=n-s;i++) sa+=a[i],sb+=b[i];
        ll aL=sa+a[t],aR=sa+a[n-s+1];
        ll bL=sb+b[t],bR=sb+b[n-s+1];
        if (aR<bL) puts("IMPOSSIBLE");
        else if (aL>=bR){
            printf("%lld\n",1-h);
        } else {
            ll r=0;
            if (aL>=bL) r=max(r,a[t]-1);
            if (bR<=aR) r=max(r,h-b[n-s+1]);
            printf("%lld\n",sb-sa-r);
        }
    }
    return 0;
}
  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值