2019, XII Samara Regional Intercollegiate Programming Contest 解题报告

2019 XII Samara Regional Intercollegiate Programming Contest

传送门


A. Rooms and Passages

题意:

n+1n+1 个房间,nn 把钥匙,有第 ii 把钥匙你可以从房间 i1i-1 走到房间 ii。每个房间 ii 都有两种类型中的一种:

  • 一、只有拥有钥匙 aia_i 才能从 i1i-1 走到 ii
  • 二、从 i1i-1 走到 ii 之后会失去钥匙 aia_i

现在问你从每个房间出发开始往后走,最远能走几个房间。

题解:

尺取(two pointers),根据走到房间的种类:如果是种类一,则判断所需钥匙数量是否为 1;如果是种类二,则增加当前房间消耗的钥匙数量,并且左端点退出时减少被消耗的钥匙数量。

代码:

#include <cstdio>
#include <cstring>
#include <algorithm>
using namespace std;
const int maxn=500005;
int a[maxn];
int type[maxn];
int tag[maxn];
int ans[maxn];
int main(){
    int n;scanf("%d",&n);
    for(int i=1;i<=n;i++){
        int x;scanf("%d",&x);
        if(x>0) a[i]=x,type[i]=1;
        else a[i]=-x,type[i]=2;
    }
    int ed=1;
    for(int st=1;st<=n;st++){
        while(ed<=n){
            if(type[ed]==1){
                if(tag[a[ed]]) break;
            }else{
                tag[a[ed]]++;
            }
            ed++;
        }
        ans[st]=ed-st;
        if(type[st]==2) tag[a[st]]--;
    }
    for(int i=1;i<=n;i++) printf("%d%c",ans[i],(i==n)?'\n':' ');
    return 0;
}

B. Rearrange Columns

题意:

给定一个 2len2*len 的点阵,每个点均为 ..#\#,问能否将点阵的每一列重排,使得所有的 #\# 构成一个联通块。

题解:

显然我们可以分析出,每一列只有第一行是 #\# 的都可以放到一起,形如:

######
......

同理每一列只有第二行是 #\# 的也都可以放到一起,形如:

......
######

而这两种情况同时出现的情况下,我们只需要存在某一列两行都是 #\#,我们就可以把他们拼接成如下形式:

#######......
......#######

这样就成功将所有 #\# 放到了同一个联通块中。

代码:

#include <cstdio>
#include <cstring>
#include <algorithm>
using namespace std;
const int maxn=1005;
char s[2][maxn];
int cnt[4];
int main(){
    scanf("%s%s",s[0],s[1]);
    int len=(int)strlen(s[0]);
    for(int i=0;i<len;i++){
        if(s[0][i]=='#'&&s[1][i]=='.') cnt[0]++;
        else if(s[0][i]=='#'&&s[1][i]=='#') cnt[1]++;
        else if(s[0][i]=='.'&&s[1][i]=='#') cnt[2]++;
        else if(s[0][i]=='.'&&s[1][i]=='.') cnt[3]++;
    }
    if(cnt[0]&&cnt[2]&&!cnt[1]) printf("NO\n");
    else{
        printf("YES\n");
        for(int j=0;j<cnt[0];j++) printf("#");
        for(int j=0;j<cnt[1];j++) printf("#");
        for(int j=0;j<cnt[2];j++) printf(".");
        for(int j=0;j<cnt[3];j++) printf(".");
        printf("\n");
        for(int j=0;j<cnt[0];j++) printf(".");
        for(int j=0;j<cnt[1];j++) printf("#");
        for(int j=0;j<cnt[2];j++) printf("#");
        for(int j=0;j<cnt[3];j++) printf(".");
        printf("\n");
    }
    return 0;
}

C. Jumps on a Circle

题意:

pp 个点 00p1p-1 围成一圈(即对于 1<i<n1 < i < nii 的左边为 i1i-1ii 的右边为 i+1i+111 的左边为 nnnn 的右边为 11),一个人初始站在 00,第一次跳 11 步,第二次跳 22 步,第 ii 次跳 ii 步,共跳 nn 次,问总共有多少个点被跳到过(包括初始站过的点 00)。

题解:

数据给出的 nn 大到 101810^{18},当然我们不能全部跳完,所以我们思考会不会在某个地方出现循环节。而这种情况下的循环节要求到达同一个地方之后的所有行动都要一致。找规律可以发现当 pp 为奇数时,跳跃 pp 次即可找到循环节,思考原因我们可以发现,跳跃 pp 次我们总共跳跃的步数是 (1+p)p2\frac{(1+p)*p}{2} ,其中 1+p1+p 为偶数,即 (1+p)%2=0(1+p)\%2=0,则(1+p)p2%p=0\frac{(1+p)*p}{2}\%p=0 成立,且下一次跳跃的步数为 p+1%p=1p+1\%p=1,满足循环节的条件。接下来思考为什么当 pp 为偶数的时候循环节不是 pp:同样,跳跃 pp 步之后的总步数为 (1+p)p2\frac{(1+p)*p}{2},此时 pp 为偶数,(1+p)p2%p=0\frac{(1+p)*p}{2}\%p=0 不成立。而我们想要使式子成立的方法也很简单,将等式左边的 pp 替换为 2p2p 即可,这样式子变成 (1+2p)2p2%p=((1+p)p)%p=0\frac{(1+2p)*2p}{2}\%p=((1+p)*p)\%p=0,显然是成立的。所以实际上当 nn 过大时,我们只需要跳最多 2p2p 步即可解决问题。

代码:

#include <cstdio>
#include <cstring>
#include <algorithm>
using namespace std;
typedef long long ll;
const int maxn=10000007;
bool tag[maxn*2];
int main(){
    ll p,n;scanf("%lld%lld",&p,&n);
    n=min(n,p*2);
    memset(tag,false, sizeof(tag));
    int ans=0,tmp=0;
    ans++;tag[0]=true;
    ll sum=0;
    for(int i=1;i<=n;i++){
        tmp++;
        if(tmp>=p) tmp-=p;
        sum+=tmp;
        if(sum>=p) sum-=p;
        if(!tag[sum]){
            tag[sum]=true;
            ans++;
        }
    }
    printf("%d\n",ans);
    return 0;
}

D. Country Division

题意:

给出一棵 nn 个点的树,接下来有多组询问,每次询问给出一种树上点的染色方式(部分点染红色,部分点染蓝色,部分点布染色),要求将树分成森林,使得所有红色点在同一棵子树上,所有蓝色点在同一棵子树上,且红色点和蓝色点不在同一棵树上。每组询问独立。

题解:

未通过


E. Third-Party Software - 2

题意:

给出 nn 条线段,问能否找出一些线段覆盖整个区间 [1,m][1,m],如果能的话找出需要线段数量最少的方案。

题解:

贪心,对线段进行排序,排序规则为左端点小的排在前面,若左端点相同则右端点大的排在前面。将当前位置设置为上一次右端点+1,继续从上一次不符合条件的线段开始往后找,找到所有左端点 \leq 当前点的线段中右端点最大的一条,更新当前点。当当前点 m+1\geq m+1时,说明所有已选择的线段已经可以覆盖整个区间了。

正确性:这样能找到的一定是当前情况下左端点最大且右端点最大的,若无法找到,则无论如何选择线段都不可能满足覆盖整个区间。

代码:

#include <cstdio>
#include <cstring>
#include <algorithm>
using namespace std;
const int maxn=200005;
struct node{
    int l,r,id;
    bool operator<(const node &t)const{
        if(l!=t.l) return l<t.l;
        else return r>t.r;
    }
}p[maxn];
int ans[maxn],tot;
int main(){
    int n,m;scanf("%d%d",&n,&m);
    for(int i=0;i<n;i++){
        p[i].id=i+1;
        scanf("%d%d",&p[i].l,&p[i].r);
    }
    sort(p,p+n);
    int now=1,mx=0,ed=0;
    while(true){
        bool flag=false;
        int tmp=0;
        for(;ed<n;ed++){
            if(p[ed].l>now) break;
            flag=true;
            if(p[ed].r>mx){
                mx=p[ed].r;
                tmp=ed;
            }
        }
        if(!flag||mx<now) break;
        ans[tot++]=p[tmp].id;
        now=mx+1;
        if(now==m+1) break;
    }
    if(now!=m+1) printf("NO\n");
    else{
        printf("YES\n");
        printf("%d\n",tot);
        for(int i=0;i<tot;i++) printf("%d%c",ans[i],(i==tot-1)?'\n':' ');
    }
    return 0;
}

F. Friendly Fire

题意:

共有 nn 只怪兽,每只有一个攻击力 aa 和血量 hh,现在你可以选择其中的两只怪兽自相残杀。假设你选择两个怪兽为 xxyy,若 axbya_x \geq b_y 则怪兽 bb 死亡,若 bxayb_x \geq a_y 则怪兽 aa 死亡(两个事件可以同时触发)。问你最少会受到多少伤害(所有最后活着的怪兽的攻击力之和),并且在这种情况下选择的是哪两只怪兽。

题解:

首先我们可以对每个怪兽的血量从小到大排序,这样我们对每个怪兽 ii 的攻击力 aia_i 就可以在怪兽中二分,知道哪些怪兽是可以被怪兽 ii 打死的(即 hposaih_{pos} \leq a_ipospos 的界限)。这样保证了选择的怪兽一定是可以被怪兽 ii 打死的,剩下要确认的事只有能被怪兽 ii 打死的怪兽中是否存在某一只能打死怪兽 ii(即 ajhi,1jpos\exists a_j \geq h_i, 1\leq j \leq pos 是否成立)。显然我们只需要在 [1,pos][1,pos] 中找到一个最大的攻击力 aa 即可判断这件事,所以我们用线段树维护。

而细节是在查询最大攻击力前要把怪兽 ii 从线段树中删去,查询完之后要重新加回去,因为怪兽 ii 本身可能会出现 aihia_i \geq h_i 的情况。

代码:

#include <cstdio>
#include <vector>
#include <cstring>
#include <algorithm>
using namespace std;
const int maxn=300005;
struct node{
    int a,h,id;
    bool operator<(const node &t)const{
        return h<t.h;
    }
}p[maxn];
int mx[maxn<<2],idx[maxn<<2],mxtmp,idtmp;
vector<int> vec;
void pushup(int rt){
    if(mx[rt*2]>mx[rt*2+1]){
        mx[rt]=mx[rt*2];
        idx[rt]=idx[rt*2];
    }else{
        mx[rt]=mx[rt*2+1];
        idx[rt]=idx[rt*2+1];
    }
}
void build(int l,int r,int rt){
    if(l==r){
        mx[rt]=p[l].a;
        idx[rt]=p[l].id;
        return;
    }
    int mid=(l+r)/2;
    build(l,mid,rt*2);
    build(mid+1,r,rt*2+1);
    pushup(rt);
}
void update(int l,int r,int rt,int pos,int val){
    if(l==r){
        mx[rt]=val;
        return;
    }
    int mid=(l+r)/2;
    if(pos<=mid) update(l,mid,rt*2,pos,val);
    else update(mid+1,r,rt*2+1,pos,val);
    pushup(rt);
}
void query(int l,int r,int rt,int L,int R){
    if(L<=l&&r<=R){
        if(mx[rt]>mxtmp){
            mxtmp=mx[rt];
            idtmp=idx[rt];
        }
        return;
    }
    int mid=(l+r)/2;
    if(L<=mid) query(l,mid,rt*2,L,R);
    if(mid<R) query(mid+1,r,rt*2+1,L,R);
}
int main(){
    int n;scanf("%d",&n);
    for(int i=1;i<=n;i++){
        p[i].id=i;
        scanf("%d%d",&p[i].a,&p[i].h);
    }
    sort(p+1,p+1+n);
    for(int i=1;i<=n;i++) vec.push_back(p[i].h);
    build(1,n,1);
    int mxx=0,x=1,y=2;
    for(int i=1;i<=n;i++){
        int id=(int)(lower_bound(vec.begin(),vec.end(),p[i].a+1)-vec.begin());
        if(!id) continue;
        mxtmp=0;idtmp=1;
        update(1,n,1,i,0);
        query(1,n,1,1,id);
        update(1,n,1,i,p[i].a);
        int tmp=mxtmp;
        if(mxtmp>=p[i].h) tmp+=p[i].a;
        if(tmp>mxx){
            mxx=tmp;x=p[i].id;y=idtmp;
        }
    }
    printf("%d\n%d %d\n",mxx,x,y);
    return 0;
}


G. Akinator

题意:

太乱了没看懂

不理解。

题解:

未通过。


H. Missing Number

题意:

交互题。

给出 nn,总共有 0n0-nn+1n+1 个数,现在给出一个大小为 nn 的数组 aa,数组中的元素各不相同,现在希望你进行至多 2n+192n+19 次询问求出缺少的一个元素的值。每次询问的格式为 ? i b,即询问 aia_i 二进制位第 bb 位是 00 还是 11。得知答案后的输出方法为 ! xxx 即为求出的答案。

题解:

已知 nn 后可以求出 0n0-n 中有多少个数二进制最低位为 00 和多少个数二进制最低位为 11,进行 nn 次询问,求出数组 aa 中这两类值的数量,可以知道哪一类数少了一个。在缺少的那一类继续枚举下一个二进制位,直到对所有二进制位做完操作。

因为这样每次能筛掉一半的数,所以询问次数为 n+n2+n22+...+1n+\frac{n}{2}+\frac{\frac{n}{2}}{2}+...+1,即不会超过 2n2n 次。

代码:

#include <vector>
#include <cstring>
#include <iostream>
#include <algorithm>
using namespace std;
vector<int> vec,veco,vece;
int main(){
    ios_base::sync_with_stdio(false);
    int n;cin>>n;
    for(int i=1;i<=n;i++) vec.push_back(i);
    int bit=0;
    for(int tmp=n;tmp;tmp/=2){
        bit++;
    }
    int now=0;
    for(int i=0;i<bit;i++){
        int tmp=1<<(i+1);
        int etmp=now;
//        int otmp=(1<<i)+now;
        int e=(n+1)/tmp+((n+1)%tmp>etmp);
//        int o=(n+1)/tmp+((n+1)%tmp>otmp);
        for(int j=0,sz=(int)vec.size();j<sz;j++){
            cout<<'?'<<' '<<vec[j]<<' '<<i<<endl;
            int x;cin>>x;
            if(!x) vece.push_back(vec[j]);
            else veco.push_back(vec[j]);
        }
        if(vece.size()<e){
            vec=vece;
        }else{
            vec=veco;
            now+=1<<i;
        }
        vece.clear();veco.clear();
    }
    cout<<'!'<<' '<<now<<endl;
    return 0;
}

I. Painting a Square

题意:

你有一个初始全白的 aaa*a 的矩阵,此时在矩阵左上角放置一个 bbb*b 的红色矩阵,这个红色矩阵只能平移(即水平或垂直移动)。问这个红色矩阵最少移动多少步可以使得整个白色矩阵都被染红(初始可以认为左上角 bbb*b 的矩阵已经被染红)。

题解:

分析可得红色矩阵应当螺旋移动才能使得步数最少,而给定矩阵边长相同,在纸上模拟直接找到规律即可。

代码:

#include <cstdio>
#include <cstring>
#include <algorithm>
using namespace std;
typedef long long ll;
int main(){
    int a,b;scanf("%d%d",&a,&b);
    ll tmp=a-b;
    ll ans=tmp;
    while(tmp>0){
        ans+=tmp+tmp;
        tmp-=b;
    }
    printf("%lld\n",ans);
    return 0;
}

J. The Power of the Dark Side - 2

题意:

给出 nn 个三元组,每个三元组有三个元素 a,b,ca,b,c。每个三元组可以调整自己 a,b,ca,b,c 的值,要求是 a+b+ca+b+c 的和不变,且 a,b,ca,b,c 都不能是负数(即要满足 a,b,c0a,b,c \geq 0)。

给出三元组比较大小的方法:三元组 xx 比三元组 yy 大的条件为 xa>ya,xb>yb,xc>ycx_a > y_a , x_b > y_b , x_c > y_c 三者满足至少两个。

询问对于每个三元组 ii 比多少个其他三元组大。

题解:

假设某个三元组 xx 的三个元素为 a,b,ca,b,c,其中两个较小的为 a,ba,b,则对于某个三元组 yy 的三个元素为
{a+1,b+1,0}\{a+1,b+1,0\},我们可以得出 y>xy > x 成立。
由此我们可知:如果 ya+yb+yc2>min{xa,xb,xc}+mid{xa,xb,xc}y_a+y_b+y_c-2 > min\{x_a,x_b,x_c\} + mid\{x_a,x_b,x_c\} ,可以得到 y>xy>x,其中 minmin 为取最小值,midmid 为取第二小值。

所以我们要做的事就是对每个三元组求一个 a+b+c2a+b+c-2min{xa,xb,xc}+mid{xa,xb,xc}min\{x_a,x_b,x_c\} + mid\{x_a,x_b,x_c\},并按后者排序,再对每个三元组二分答案即可。

代码:

#include <cstdio>
#include <cstring>
#include <algorithm>
using namespace std;
typedef long long ll;
const int maxn=500005;
struct node{
    int id;
    ll sum,mn;
    bool operator<(const node &t)const{
        return mn<t.mn;
    }
}p[maxn];
ll a[3];
int ans[maxn];
int main(){
    int n;scanf("%d",&n);
    for(int i=0;i<n;i++){
        p[i].id=i;
        p[i].sum=0;
        for(int j=0;j<3;j++){
            scanf("%lld",&a[j]);
            p[i].sum+=a[j];
        }
        sort(a,a+3);
        p[i].mn=a[0]+a[1]+2;
    }
    sort(p,p+n);
    for(int i=0;i<n;i++){
        int l=0,r=n-1;
        while(l<=r){
            int mid=(l+r)/2;
            if(p[mid].mn<=p[i].sum){
                ans[p[i].id]=mid;
                if(mid<i) ans[p[i].id]++;
                l=mid+1;
            }else{
                r=mid-1;
            }
        }
    }
    for(int i=0;i<n;i++) printf("%d%c",ans[i],(i==n-1)?'\n':' ');
    return 0;
}

K. Deck Sorting

题意:

给你一个只包含 R,G,BR,G,B 三种元素的字符串,并且给你两个栈,你可以从左向右遍历字符串,将每个元素放入两个栈的其中一个,问最后能否将某个栈放到另一个栈上,使得所有相同的元素都相邻,如:
对于串 RGBRGB,栈的内容变化过程为:

    |     |     |     | G   | G B |
    |     |   B | R B | R B | R B |
R   | R G | R G | R G | R G | R G |

最后将右边的堆放到左边的堆上,结果为:

B
B
G
G
R
R

满足所有相同元素都相邻。

题解:

因为只有三个元素,所以我们只需要枚举三个元素的顺序,方案只有六种。不妨假设我们只会把第二个堆放在第一个堆的上方,则此时第一个元素必定在第一个堆的下部,第三个元素必定在第二个堆的上部,而位于中间的第二个元素会出现在第一个堆的上部和第二个堆的下部。接下来我们讨论对于每种某种情况字符串是否可能取到满足的方法(因为六种情况的本质做法是一样的):

假设当前要求的重排结果为 RGB

  • 当字符串最前面为 R,GR,G 元素相互交替时,我们一定能把这一部分分别放到两个堆的底部。

  • 而当串中第一次出现 BB 时,我们一定会将这个 BB 放在第二个堆的顶部,自此之后,第二个堆就只能放 BB 了,
    而第一个串的顶部仍为 RR,所以此时我们能接受的字符串内容为 R,BR,B 元素相互交替

  • 情况会再次发生变化,当字符串再一次出现 GG 时,由于第二个堆的顶部已经出现了 BB,所以这个 GG 只能放到第一个堆的堆顶,自此之后,第一个堆就只能放 GG 了,而第二个串的顶部仍为 BB,所以此时我们能接受的字符串内容为 B,GB,G 元素相互交替

看懂上面的情况后就可以解决每个状态了。

代码:

#include <cstdio>
#include <cstring>
#include <algorithm>
using namespace std;
const int maxn=1005;
char s[maxn];
int len;
char t[5]="RGB";
int cnt[3],a[3];
bool check(){
    int tmp1=-1;
    for(int i=0;i<len;i++){
        if(s[i]==t[a[2]]){
            tmp1=i;
            break;
        }
    }
    if(tmp1==-1) return true;
    int tmp2=-1;
    for(int i=tmp1+1;i<len;i++){
        if(s[i]==t[a[1]]){
            tmp2=i;
            break;
        }
    }
    if(tmp2==-1) return true;
    for(int i=tmp1+1;i<tmp2;i++){
        if(s[i]==t[a[1]]) return false;
    }
    for(int i=tmp2+1;i<len;i++){
        if(s[i]==t[a[0]]) return false;
    }
    return true;
}
int main(){
    scanf("%s",s);
    len=(int)strlen(s);
    for(int i=0;i<len;i++){
        for(int j=0;j<3;j++){
            if(s[i]==t[j]) cnt[j]++;
        }
    }
    for(int i=0;i<3;i++) a[i]=i;
    do{
        if(check()){
            printf("YES\n");
            return 0;
        }
    }while(next_permutation(a,a+3));
    printf("NO\n");
    return 0;
}

L. Inscribed Circle

题意:

给出两个圆的座标与半径,保证两个圆恰好交于两点,求相交部分里能放下的最大圆的座标与半径。

题解:

简单几何题。

分析可得内部圆的圆心处于两个给定圆的圆心连线上,接下来要做的只是计算座标与半径。

代码:

#include <cmath>
#include <cstdio>
#include <cstring>
#include <algorithm>
using namespace std;

int main(){
    double x1,y1,r1,x2,y2,r2;scanf("%lf%lf%lf%lf%lf%lf",&x1,&y1,&r1,&x2,&y2,&r2);
    if(x1>x2) swap(x1,x2),swap(y1,y2),swap(r1,r2);
    double L=sqrt((x2-x1)*(x2-x1)+(y2-y1)*(y2-y1));
    double r=(r1+r2-L)/2;
    double x=(L-r2+r)/L*(x2-x1)+x1;
    double y=(L-r2+r)/L*(y2-y1)+y1;
    printf("%.10f %.10f %.10f",x,y,r);
    return 0;
}

M. Shlakoblock is live!

题意:

nn 个游戏,每个游戏有一个快乐值 pp,并且每个游戏有一个已经被投的票数 vv。现在问你你可以给哪些游戏投票,最大化快乐值的期望值(对于每个游戏你只能投一票)。

题解:

可以分析出快乐值的期望值为 i=1npivii=1nvi\frac{\sum_{i=1}^{n}{p_i*v_i}}{\sum_{i=1}^{n}{v_i}},当你给某个游戏 jj 投票后,快乐值得期望值会变为 (i=1npivi)+pj(i=1nvi)+1\frac{(\sum_{i=1}^{n}{p_i*v_i})+p_j}{(\sum_{i=1}^{n}{v_i})+1}
所以只需要对 pp 进行排序,每次判断选取后能否使期望值变大即可。

代码:

#include <cstdio>
#include <cstring>
#include <algorithm>
using namespace std;
typedef long long ll;
const int maxn=1005;
struct node{
    ll p,v;
    int id;
    bool operator<(const node &t)const{
        return p>t.p;
    }
}a[maxn];
int ans[maxn],tot;
int main(){
    int t;scanf("%d",&t);
    while(t--){
        tot=0;
        int n;scanf("%d",&n);
        ll up=0,down=0;
        for(int i=1;i<=n;i++){
            a[i].id=i;
            scanf("%lld%lld",&a[i].p,&a[i].v);
            up+=a[i].p*a[i].v;
            down+=a[i].v;
        }
        sort(a+1,a+1+n);
        for(int i=1;i<=n;i++){
            ll uptmp=up+a[i].p;
            ll downtmp=down+1;
            if(uptmp*down>=up*downtmp){
                up=uptmp;
                down=downtmp;
                ans[tot++]=a[i].id;
            }else{
                break;
            }
        }
        ll g=__gcd(up,down);
        up/=g;down/=g;
        printf("%lld/%lld\n",up,down);
        printf("%d\n",tot);
        sort(ans,ans+tot);
        for(int i=0;i<tot;i++) printf("%d%c",ans[i],(i==tot-1)?'\n':' ');
    }
    return 0;
}
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章