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;
}
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章