【补题】2022-2023 ACM-ICPC German Collegiate Programming Contest (GCPC 2022)

题目链接

https://codeforces.com/gym/104059

A. Alternative Architecture

思路

简单题,但要注意细节。

给的方格很干扰思考,事实上注意到顶点指的是四个角上的圆圈,我们将长宽都减去\(1\),问题就转化成了标准的格点矩形问题。

然后我们可以枚举左边的小三角形的直角边长\((x,y)\),确定矩形的方向。简单利用一下相似三角形的性质就判定右上角的点是否在格点上。

注意这样枚举出来的方向是\((0,90)\),根据\(a,b\)是否相等分类讨论才是最终答案。

时间复杂度\(\Theta(a)\)

代码

点击查看代码
#include<cstdio>
#include<cstdlib>
#include<algorithm>
#include<cmath>
#define ll long long
using namespace std;
int main(){
    int ans=1;
    ll a,b;
    scanf("%lld%lld",&a,&b);
    a--,b--;
    if(a>b) swap(a,b);
    for(int x=1;x<a;++x){
        ll t=(a+x)*(a-x);
        ll y=(ll)floor(sqrt(t));
        if(y*y==t||(y+1)*(y+1)==t){
            if(!(x*b%a)&&!(y*b%a)){
                ans++;
            }
        }
    }
    if(a!=b) ans*=2;
    printf("%d",ans);
    return 0;
}

B. Breeding Bugs

思路

中档题,需要一些观察和灵感。

首先,看见\(n\leq 750\),基本可以肯定不是数论推式子,那么就可能需要一些相对暴力的办法。

我们把相加为质数的两个数间连一条边,表示不能同时取,于是就变成了一个图的最大独立集问题。

但是一般图的最大独立集是非\(P\)类问题,但是作为特殊情况,二分图的最大独立集是非常简单的:最大独立集=总点数-最大匹配数

然后我们考虑这个图是不是二分图。

假设图上存在奇环,设环为\(k\)元环,那么:

\[a_1+a_2=p_1 \]

\[a_2+a_3=p_2 \]

\[...... \]

\[a_k+a_1=p_k \]

全部相加得\(2\sum_{i=1}^k a_i=\sum_{i=1}^k p_k\)

注意到右边的\(p\)全部都是质数,而奇数个质数相加为奇数(?),推出矛盾,图上不存在二元环,所以图是二分图。

诶,不对,你这推理有问题啊,如果右边的\(p\)当中有\(2\)呢?那不是寄了?

但是我们注意到,\(1+1=2\),所以我们在选数的过程中,最多选一个\(1\),所以我们的\(p\)当中是不会出现\(2\)的。

于是,过滤掉多余的\(1\),建图跑匈牙利就完事了。

时间复杂度\(\Theta(n^3)\)

有傻逼匈牙利写挂了,我不说是谁

代码

点击查看代码
#include<cstdio>
#include<cstdlib>
#include<cstring>
#define maxn 760
#define lim 20000000
#define max_p 1400000
using namespace std;
int is_p[lim+1],p[max_p],cnt=0;
int a[maxn],tot=0;
int fst[maxn],nxt[maxn*maxn*2],to[maxn*maxn*2],e=0;
void sieve(){
    int i,j;
    memset(is_p,1,sizeof(is_p));
    is_p[1]=0;
    for(i=2;i<=lim;++i){
        if(is_p[i]) p[++cnt]=i;
        for(j=1;j<=cnt&&p[j]<=lim/i;++j){
            is_p[i*p[j]]=0;
            if(!(i%p[j])) break;
        }
    }
}
void add(int x,int y){
    to[++e]=y;
    nxt[e]=fst[x];
    fst[x]=e;
}
int used[maxn],c[maxn],vis[maxn],match[maxn];
void paint(int x,int color){
    c[x]=color;vis[x]=1;
    for(int i=fst[x];i;i=nxt[i]){
        if(vis[to[i]]) continue;
        paint(to[i],color^1);
    }
    return;
}
int dfs(int x){
    for(int i=fst[x];i;i=nxt[i]){
        if(used[to[i]]) continue;
        used[to[i]]=1;
        if(!match[to[i]]||dfs(match[to[i]])){
            match[to[i]]=x;
            return 1;
        }
    }
    return 0;
}
int main(){
    int i,j,n,x,flag=1;
    int ans=0;
    // freopen("test.in","r",stdin);
    // freopen("test.out","w",stdout);
    scanf("%d",&n);
    sieve();
    for(i=1;i<=n;++i){
        scanf("%d",&x);
        if(x>1) a[++tot]=x;
        else if(flag){
            flag=0;
            a[++tot]=x;
        } 
    }
    n=tot;
    for(i=1;i<=n;++i){
        for(j=i+1;j<=n;++j){
            if(is_p[a[i]+a[j]]){
                add(i,j),add(j,i);
            }
        }
    }
    for(i=1;i<=n;++i){
        if(!vis[i]) paint(i,0);
    }
    for(i=1;i<=n;++i){
        if(c[i]) continue;
        for(j=1;j<=n;++j) used[j]=0;
        ans+=dfs(i);
    }
    printf("%d\n",n-ans);
    return 0;
}

C. Chaotic Construction

思路

简单题。
是个环,那么从\(a\)\(b\)\(a->b\)\(a->1->n->b\)两条路可走。

我们只需要实时维护数组的区间和就可以了,如果路径上点全部被覆盖(值为1)就可达,否则不可达。

单点加,区间求和,我用的是树状数组。

时间复杂度\(\Theta(nlogn)\)

代码

点击查看代码
#include<cstdio>
#include<cstdlib>
#include<algorithm>
#define maxn 200010
using namespace std;
int lowbit(int x){
    return x&(-x);
}
int c[maxn],n,m;
void modify(int x,int d){
    while(x<=n){
        c[x]+=d;
        x+=lowbit(x);
    }
}
int query(int x){
    int ans=0;
    while(x){
        ans+=c[x];
        x-=lowbit(x);
    }
    return ans;
}
int main(){
    int i,j,x,y;
    char op[2];
    // freopen("test.in","r",stdin);
    // freopen("test.out","w",stdout);
    scanf("%d%d",&n,&m);
    for(i=1;i<=n;++i) modify(i,1);
    for(i=1;i<=m;++i){
        scanf("%s",op);
        if(op[0]=='?'){
            scanf("%d%d",&x,&y);
            if(x>y) swap(x,y);
            int flag=0;
            int sum=query(n)+query(x)-query(y-1);
            flag|=(sum==n-y+1+x);
            sum=query(y)-query(x-1);
            flag|=(sum==y-x+1);
            if(flag) printf("possible\n");
            else printf("impossible\n");
        }
        else if(op[0]=='+'){
            scanf("%d",&x);
            modify(x,1);
        }
        else{
            scanf("%d",&x);
            modify(x,-1);
        }
    }
    return 0;
}

D. Diabolic Doofenshmirtz

思路

简单题,但是容易写错。

第一次看错题了,以为是个简单二分,写上去寄了。

然后再审一遍题,发现要求询问的\(t\)严格递增。

这就比较麻烦了,但是观察一下数据范围,我们相信二分的思路是没错的,与数据吻合得非常好。

然后我们考虑怎么在\(t\)递增的情况下达成二分的效果。

手搓几组样例可以发现,交互时返回的\(d\)值,除了根据\(t\)\(d\)的大小关系来判断是否兜了圈之外,我们还同时找到了圈长的倍数。

于是如果我们当前准备询问的\(t\)比上次小,我们可以不停加上这个倍数直到比上次大。

然后这题就做完了。

时间复杂度\(log(10^{12})\)

代码

点击查看代码
#include<cstdio>
#include<cstdlib>
#define ll long long
using namespace std;
int main(){
    ll t,l=1,r=1ll<<40,d,ans,lap=-1;
    t=l+r>>1;
    while(l<=r){
        printf("? %lld\n",t);
        fflush(stdout);
        scanf("%lld",&d);
        ll mid=l+r>>1,dt;
        if(d==mid) l=mid+1,dt=l+r>>1;
        else{
            r=mid-1;
            if(!d) ans=mid;
            if(t-d<lap||lap<0) lap=t-d;
            dt=(l+r>>1);
        }
        if(dt<=t) dt+=((t-dt)/lap+1)*lap;
        t=dt;
        // printf("%lld %lld %lld\n",l,r,lap);
        
    }
    printf("! %lld",ans);
    fflush(stdout);
    return 0;
}

E. Enjoyable Entree

思路

签到题,简单找规律。

代码

点击查看代码
#include<cstdio>
#include<cstdlib>
#include<cmath>
#define ll long long
#define db double
using namespace std;
int main(){
    int i,j;
    ll n;
    db ans;
    scanf("%lld",&n);
    if(n==1) ans=1;
    else if(n==2) ans=0;
    else{
        ans=1.0/3.0+2.0/3.0*pow(-0.5,n-1);
    }
    ans*=100;
    printf("%.6lf %.6lf",ans,100-ans);
    return 0;
}

H. Hardcore Hangman

思路

中档题(?)其实看过题人数应该是简单题的,但是实现起来有点麻烦。

理解一下题意,就是可以询问若干个集合的并,求出每个集合的内容,然后就可以想到交并补差等集合运算。。。

然后发现如果我们询问一次\(? ab\),一次\(? bc\)就可以同时求出\(a,b,c\)三个字母各自的位置信息。然后我们的方案构造就一定是往\(3\)上面去凑。

我们把\(26\)个字母平均分成三段(\(9,9,8\))?但是为了避免分类讨论,我们直接认为27号字母有意义,但不存在于原串之中。

然后我们询问\(? abc....r\) \(? jkl...z\),求出三个大小为\(9\)的集合信息,然后递归下去即可。

由于三个集合的交集为空,所以接下去一次查询可以同时对三个集合进行操作。

所以\(f(n)=2+f(\frac{n}{3})\),正好\(6\)次查完。

代码

点击查看代码
#include<cstdio>
#include<cstdlib>
#include<vector>
#include<algorithm>
#include<cstring>
#define maxn 10010
using namespace std;
int len;
char c[7][20];
int book[7][maxn];
int tmp[100][maxn];
int ans[maxn],dfn=1;
void dfs(int l,int r,int step,int deep){
    int i,j;
    int z=(r-l+1)/3;
    if(l==r){
        if(l==27) return;
        for(i=1;i<=len;++i){
            if(tmp[step][i]) ans[i]=l;
        }
        return;
    }
    dfn++;
    for(i=1;i<=len;++i) tmp[dfn][i]=max(0,tmp[step][i]-book[2*deep][i]);
    dfs(l,l+z-1,dfn,deep+1);
    dfn++;
    for(i=1;i<=len;++i) tmp[dfn][i]=tmp[step][i]&book[2*deep-1][i]&book[2*deep][i];
    dfs(l+z,l+2*z-1,dfn,deep+1);
    dfn++;
    for(i=1;i<=len;++i) tmp[dfn][i]=max(0,tmp[step][i]-book[2*deep-1][i]);
    dfs(l+2*z,l+3*z-1,dfn,deep+1);
    return;
}
int main(){
    int i,j,n,m=0,x;
    char s1[20]="abcdefghijklmnopqr";
    char s2[20]="jklmnopqrstuvwxyz";
    char s3[20]="abcdefjklmnostuvwx";
    char s4[20]="defghimnopqrvwxyz";
    char s5[20]="abdeghjkmnpqstvwyz";
    char s6[20]="bcefhiklnoqrtuwxz";
    strcpy(c[1],s1);
    strcpy(c[2],s2);
    strcpy(c[3],s3);
    strcpy(c[4],s4);
    strcpy(c[5],s5);
    strcpy(c[6],s6);
    for(i=1;i<=6;++i){
        printf("? %s\n",c[i]);
        fflush(stdout);
        scanf("%d",&n);
        for(j=1;j<=n;++j){
            scanf("%d",&x);
            len=max(len,x);
            book[i][x]=1;
        }
    }
    for(i=1;i<=len;++i) tmp[1][i]=1;
    dfs(1,27,1,1);
    printf("! ");
    for(i=1;i<=len;++i) printf("%c",ans[i]+'a'-1);
    printf("\n");
    return 0;
}

I. Improving IT

思路

简单DP,dp[i]表示以\(CPU_i\)为结尾的最小代价。

代码

点击查看代码
#include<cstdio>
#include<cstdlib>
#include<vector>
#include<cstring>
#include<algorithm>
#define maxn 500010
#define ll long long
using namespace std;
ll dp[maxn];
vector<ll> p[maxn];
int main(){
    int i,j,n,m;
    ll x;
    // freopen("test.in","r",stdin);
    // freopen("test.out","w",stdout);
    scanf("%d%d",&n,&m);
    for(i=1;i<=n;++i){
        for(j=0;j<=min(m,n-i+1);++j){
            scanf("%lld",&x);
            p[i].push_back(x);
        }
    }
    p[n+1].push_back(0);
    dp[1]=p[1][0];
    for(i=2;i<=n+1;++i){
        for(j=max(1,i-m);j<i;++j){
            if(j>max(1,i-m)) dp[i]=min(dp[i],dp[j]+p[i][0]-p[j][i-j]);
            else dp[i]=dp[j]+p[i][0]-p[j][i-j];
        }
    }
    printf("%lld",dp[n+1]);
    return 0;
}

J. Jesting Jabberwocky

思路

中档题,容易想错。

刚开始以为是个高妙贪心,然后构造了很多贪法都有反例。

然后注意到题目只要求同花色的在一起,没有规定花色间的顺序,于是我们可以全排列一下四种花色。

于是变成了\({1,2,3,4}\)的升序排列。

考虑dp(现在也只能考虑dp了),令\(dp[i][j]\)为将前\(i\)个数排好序,且以\(j\)结尾的最小代价。

则$$dp[i][x]=dp[i-1][x]+1(x\neq a[i])$$

\[dp[i][a[i]]=min_{j=0}^{a[i]}(dp[i][a[i]],dp[i-1][j]) \]

为什么是\(+1\)?感性理解一下,如果我们需要把当前位置的数移走,我们肯定是希望它一步到位。尽管我们不知道具体是哪,但是可以相信一定存在这样一个位置。

然后就做完了(感觉讲得很玄乎诶)

代码

点击查看代码
#include<cstdio>
#include<cstdlib>
#include<cstring>
#include<algorithm>
#define maxn 100010
using namespace std;
char s[maxn];
int b[maxn];
int p[5],dp[maxn][5];
int work(int n){
    int i,j,ans=maxn;
    for(i=1;i<=4;++i) dp[0][i]=maxn;
    dp[0][0]=0;
    for(i=1;i<=n;++i){
        for(j=0;j<=4;++j) dp[i][j]=dp[i-1][j]+1;
        for(j=0;j<=p[b[i]];++j) dp[i][p[b[i]]]=min(dp[i][p[b[i]]],dp[i-1][j]);
    }
    for(i=0;i<=4;++i) ans=min(ans,dp[n][i]);
    return ans;
}
int main(){
    int i,j,n,m,ans=maxn;
    // freopen("test.in","r",stdin);
    // freopen("test.out","w",stdout);
    scanf("%s",s);
    n=strlen(s);
    for(i=0;i<n;++i){
        if(s[i]=='s') b[i+1]=1;
        if(s[i]=='h') b[i+1]=2;
        if(s[i]=='c') b[i+1]=3;
        if(s[i]=='d') b[i+1]=4;
    }
    for(i=1;i<=4;++i) p[i]=i;
    for(i=1;i<=24;++i){
        ans=min(ans,work(n));
        if(i<24) next_permutation(p+1,p+5);
    }
    printf("%d",ans);
    return 0;
}

K. K.O. Kids

思路

签到题,注意到最多有\(n\)个人掉下去,剩下的就都可以顺利过桥,模拟即可。

代码

点击查看代码
#include<cstdio>
#include<cstdlib>
#define maxn 1010
using namespace std;
char s[maxn];
int a[maxn];
int main(){
    int i,j,n,k,pre=0;
    // freopen("test.in","r",stdin);
    // freopen("test.out","w",stdout);
    scanf("%d%d",&n,&k);
    scanf("%s",s);
    for(i=0;i<n;++i){
        if(s[i]=='L') a[i+1]=1;
        else a[i+1]=0;
    }
    while(pre<n){
        int t;
        t=a[pre]^1;
        for(i=pre+1;;++i){
            if(!k){
                pre=n;
                break;
            }
            if(i>n){
                pre=n;
                break;
            }
            if(a[i]^t){
                pre=i,k--;
                break;
            }

            t^=1;
        }
    }    
    printf("%d",k);
    return 0;
}

L. Lots of Land

思路

中档题,实现有点麻烦。

首先判掉格子根本无法均分的情况。

然后大胆猜测一波,如果存在解,那么一定存在每个小块形状都相同的解。

然后一定可以摆成这种形式

然后枚举一下横着摆多少,竖着摆多少就做完了,细节还是挺多的。

代码

点击查看代码
#include<cstdio>
#include<cstdlib>
using namespace std;
int c[101][101],l,w;
int check(int a,int b){//b>a
    int i,j,k;
    int x,y,num=0;
    if(!(l%a)){
        for(i=0;i<=w/b;++i){
            if((w-b*i)%a) continue;
            x=i,y=(w-b*i)/a;
            if(y&&(l%b)) continue;
            for(k=1;k<=l/a;++k){
                for(j=1;j<=x;++j){
                    num=(k-1)*x+j;
                    for(int t1=(k-1)*a+1;t1<=k*a;++t1){
                        for(int t2=(j-1)*b+1;t2<=j*b;++t2){
                            c[t1][t2]=num;
                        }
                    }
                }
            }
            for(k=1;k<=l/b;++k){
                for(j=1;j<=y;++j){
                    num++;
                    for(int t1=(k-1)*b+1;t1<=k*b;++t1){
                        for(int t2=(j-1)*a+1;t2<=j*a;++t2){
                            c[t1][b*x+t2]=num;
                        }
                    }
                }
            }
            return 1;
        }
    }
    else if(!(w%a)){
        for(i=0;i<=l/b;++i){
            if((l-b*i)%a) continue;
            x=i,y=(l-b*i)/a;
            if(y&&(w%b)) continue;
            for(k=1;k<=x;++k){
                for(j=1;j<=w/a;++j){
                    num=(k-1)*(w/a)+j;
                    for(int t1=(k-1)*b+1;t1<=k*b;++t1){
                        for(int t2=(j-1)*a+1;t2<=j*a;++t2){
                            c[t1][t2]=num;
                        }
                    }
                }
            }
            for(k=1;k<=y;++k){
                for(j=1;j<=w/b;++j){
                    num++;
                    for(int t1=(k-1)*a+1;t1<=k*a;++t1){
                        for(int t2=(j-1)*b+1;t2<=j*b;++t2){
                            c[t1+x*b][t2]=num;
                        }
                    }
                }
            }
            return 1;
        }
    }
    return 0;
}
void output(){
    int i,j;
    for(i=1;i<=l;++i){
        for(j=1;j<=w;++j){
            printf("%c",c[i][j]+'A'-1);
        }
        printf("\n");
    }
}
int main(){
    int i,j,n,flag=0;
    int S;
    // freopen("test.in","r",stdin);
    // freopen("test.out","w",stdout);
    scanf("%d%d%d",&l,&w,&n);
    if(l*w%n){
        printf("IMPOSSIBLE");
        return 0;
    }
    else{
        S=l*w/n;
        for(int i=1;i*i<=S;++i){
            if(S%i) continue;
            if(check(i,S/i)){
                flag=1;
                break;
            }
        }
        if(flag) output();
        else printf("IMPOSSIBLE");
        return 0;
    }
}

还有\(3\)题应该是当时的金牌题吧,感觉不太好做,能做出来再填坑吧qwq。

posted @ 2023-03-08 23:50  文艺平衡树  阅读(716)  评论(0编辑  收藏  举报