【补题】2022-2023 ACM-ICPC German Collegiate Programming Contest (GCPC 2022)
题目链接
https://codeforces.com/gym/104059
A. Alternative Architecture
思路
简单题,但要注意细节。
给的方格很干扰思考,事实上注意到顶点指的是四个角上的圆圈,我们将长宽都减去,问题就转化成了标准的格点矩形问题。
然后我们可以枚举左边的小三角形的直角边长,确定矩形的方向。简单利用一下相似三角形的性质就判定右上角的点是否在格点上。
注意这样枚举出来的方向是,根据是否相等分类讨论才是最终答案。
时间复杂度
代码
点击查看代码
#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
思路
中档题,需要一些观察和灵感。
首先,看见,基本可以肯定不是数论推式子,那么就可能需要一些相对暴力的办法。
我们把相加为质数的两个数间连一条边,表示不能同时取,于是就变成了一个图的最大独立集问题。
但是一般图的最大独立集是非类问题,但是作为特殊情况,二分图的最大独立集是非常简单的:最大独立集=总点数-最大匹配数
然后我们考虑这个图是不是二分图。
假设图上存在奇环,设环为元环,那么:
全部相加得。
注意到右边的全部都是质数,而奇数个质数相加为奇数(?),推出矛盾,图上不存在二元环,所以图是二分图。
诶,不对,你这推理有问题啊,如果右边的当中有呢?那不是寄了?
但是我们注意到,,所以我们在选数的过程中,最多选一个,所以我们的当中是不会出现的。
于是,过滤掉多余的,建图跑匈牙利就完事了。
时间复杂度
有傻逼匈牙利写挂了,我不说是谁
代码
点击查看代码
#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
思路
简单题。
是个环,那么从到有和两条路可走。
我们只需要实时维护数组的区间和就可以了,如果路径上点全部被覆盖(值为1)就可达,否则不可达。
单点加,区间求和,我用的是树状数组。
时间复杂度。
代码
点击查看代码
#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
思路
简单题,但是容易写错。
第一次看错题了,以为是个简单二分,写上去寄了。
然后再审一遍题,发现要求询问的严格递增。
这就比较麻烦了,但是观察一下数据范围,我们相信二分的思路是没错的,与数据吻合得非常好。
然后我们考虑怎么在递增的情况下达成二分的效果。
手搓几组样例可以发现,交互时返回的值,除了根据和的大小关系来判断是否兜了圈之外,我们还同时找到了圈长的倍数。
于是如果我们当前准备询问的比上次小,我们可以不停加上这个倍数直到比上次大。
然后这题就做完了。
时间复杂度。
代码
点击查看代码
#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
思路
中档题(?)其实看过题人数应该是简单题的,但是实现起来有点麻烦。
理解一下题意,就是可以询问若干个集合的并,求出每个集合的内容,然后就可以想到交并补差等集合运算。。。
然后发现如果我们询问一次,一次就可以同时求出三个字母各自的位置信息。然后我们的方案构造就一定是往上面去凑。
我们把个字母平均分成三段()?但是为了避免分类讨论,我们直接认为27号字母有意义,但不存在于原串之中。
然后我们询问 ,求出三个大小为的集合信息,然后递归下去即可。
由于三个集合的交集为空,所以接下去一次查询可以同时对三个集合进行操作。
所以,正好次查完。
代码
点击查看代码
#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]表示以为结尾的最小代价。
代码
点击查看代码
#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
思路
中档题,容易想错。
刚开始以为是个高妙贪心,然后构造了很多贪法都有反例。
然后注意到题目只要求同花色的在一起,没有规定花色间的顺序,于是我们可以全排列一下四种花色。
于是变成了的升序排列。
考虑dp(现在也只能考虑dp了),令为将前个数排好序,且以结尾的最小代价。
则
为什么是?感性理解一下,如果我们需要把当前位置的数移走,我们肯定是希望它一步到位。尽管我们不知道具体是哪,但是可以相信一定存在这样一个位置。
然后就做完了(感觉讲得很玄乎诶)
代码
点击查看代码
#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
思路
签到题,注意到最多有个人掉下去,剩下的就都可以顺利过桥,模拟即可。
代码
点击查看代码
#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;
}
}
还有题应该是当时的金牌题吧,感觉不太好做,能做出来再填坑吧qwq。
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 分享一个免费、快速、无限量使用的满血 DeepSeek R1 模型,支持深度思考和联网搜索!
· 基于 Docker 搭建 FRP 内网穿透开源项目(很简单哒)
· ollama系列01:轻松3步本地部署deepseek,普通电脑可用
· 25岁的心里话
· 按钮权限的设计及实现