寒假训练(复健)记录
在vjudge上打了一场最近的edu的virtual
写了ABCD
E来不及看了,感觉有思路
太久没写代码,稳定性好差(
A. Equidistant Letters
发现距离可以是0,于是直接排序输出即可
代码不贴了(
B. Minor Reduction
直接贪心即可
优先保证位数,然后如果无法保证位数则优先令高位变大
保证位数的情况下优先让高位的变大
#include <bits/stdc++.h> using namespace std; string ss; int a[505]; int que1[505],que2[505],cnt1,cnt2,T; int main(){ cin>>T; while (T--){ cin>>ss; int Len=ss.length(); int pos=-1; bool flag=false; for (int i=0;i<Len-1;i++){ int x=ss[i]-'0',y=ss[i+1]-'0'; if (x+y>=10){ pos=i; int sum=x+y; sum/=10; if (sum>x){ flag=true; for (int j=0;j<i;j++){ printf("%c",ss[i]); printf("%d",x+y); for (int j=i+2;j<Len;j++) printf("%c",ss[i]); printf("\n"); break; } } } } //cout<<pos<<endl; if (!flag&&pos!=-1){ for (int i=0;i<pos;i++) printf("%c",ss[i]); int x=ss[pos]-'0',y=ss[pos+1]-'0'; printf("%d",x+y); for (int i=pos+2;i<Len;i++) printf("%c",ss[i]); printf("\n"); } if (!flag&&pos==-1){ printf("%d",ss[0]-'0'+ss[1]-'0'); for (int i=2;i<Len;i++) printf("%c",ss[i]); printf("\n"); } } return 0; }
C. Monsters And Spells
画画图发现本质上是个线段合并问题
直接排序瞎写写即可
记得开long long
#include <bits/stdc++.h> using namespace std; int N,T; long long k[505],h[505]; pair<long long,long long> a[505]; int main(){ cin>>T; while (T--){ long long ans=0; scanf("%d",&N); for (int i=1;i<=N;i++) scanf("%lld",&k[i]); for (int i=1;i<=N;i++) scanf("%lld",&h[i]); for (int i=1;i<=N;i++){ pair<long long,long long> Now; long long L=k[i]-h[i],R=k[i]; L=max(L,0ll); Now.first=L; Now.second=R; a[i]=Now; } sort(a+1,a+N+1); long long l=a[1].first,r=a[1].second; for (int i=2;i<=N;i++){ if (r<=a[i].first){ ans+=1ll*(1ll+r-l)*(r-l)/2ll,l=0,r=0; } if (!l&&!r) {l=a[i].first,r=a[i].second; continue;} if (a[i].first>=l&&a[i].second<=r) continue; if (r<a[i].second) r=a[i].second; } ans+=1ll*(1ll+r-l)*(r-l)/2ll; cout<<ans<<endl; } }
于是可以暴力枚举一下每一段有几个人
然后贪心的去填就可以了。
其中K是常数
#include <bits/stdc++.h> using namespace std; int T,N; int cnt[200005],a[200005]; int Calc(int x,int y){ int sum1=0; int i; for (i=1;i<=N;i++){ if (sum1+cnt[i]>x) break; sum1+=cnt[i]; } int sum2=0; for (;i<=N;i++){ if (sum2+cnt[i]>y) break; sum2+=cnt[i]; } int sum3; sum3=N-sum1-sum2; for (i=0;(1<<i)<sum3;i++); return (x+y+(1<<i)-(sum1+sum2+sum3)); } int main(){ scanf("%d",&T); while (T--){ scanf("%d",&N); for (int i=1;i<=N;i++){ scanf("%d",&a[i]); cnt[a[i]]++; } int ans=1e9; for (int i=0;i<=20;i++) for (int j=0;j<=20;j++) ans=min(ans,Calc(1<<i,1<<j)); for (int i=1;i<=N;i++) cnt[a[i]]=0; printf("%d\n",ans); } return 0; }
E和F明天再说.jpg
Day 2
Codeforces Round #657 (Div. 2)
A. Acacius and String
直接模拟
代码注意一下细节
#include <bits/stdc++.h> using namespace std; int T; string ss; int main(){ cin>>T; string st="abacaba"; int Len1=st.length(); while (T--){ string ss; int N; cin>>N; cin>>ss; bool flag2=false; int Len=ss.length(); for (int i=0;i<Len;i++){ string s=ss; int Len=s.length(); bool flag=false; for (int j=0;j<Len1;j++){ if (s[i+j]=='?') s[i+j]=st[j]; else if (s[i+j]!=st[j]) {flag=true;break;} } if (flag) continue; else { int cnt=0; for (int i=0;i<Len;i++) if (s[i]=='?') s[i]='z'; for (int i=0;i+Len1-1<Len;i++){ bool flag1=false; for (int j=0;j<Len1;j++) if (s[i+j]!=st[j]) {flag1=true;break;} if (!flag1) cnt++; } //cout<<s<<" "<<cnt<<endl; if (cnt==1){ printf("Yes\n"); cout<<s<<endl; flag2=true; break; } } } if (!flag2) printf("No\n"); } return 0; }
B. Dubious Cyrpto
考虑移项
又由于的范围是到
于是其实只要在这个范围内有一个整数就行
也就是
处理一下即可
#include <bits/stdc++.h> using namespace std; long long T,l,r,m; int main(){ cin>>T; while(T--){ cin>>l>>r>>m; bool flag=false; for (int i=l;i<=r;i++){ long long x=(m+l-r+i-1)/i,y=(m+r-l)/i; x=max(x,1ll); if (x<=y){ flag=true; int t=m-i*x; if (t<0) printf("%lld %lld %lld\n",i,l,l-t); else printf("%lld %lld %lld\n",i,l+t,l); } if (flag) break; } } return 0; }
C. Choosing flowers
先考虑如果把所有的全部买完要怎么做
显然剩下的是全部取最大的
现在考虑钦定
钦定"剩下的"所有花是买某个
于是在买这个之前,我们贪心的想
肯定会先把比它大的和它本身的买掉
然后剩下的所有的花的数量买这个类型的
于是每次二分找出这个在中的位置,前缀和后就是计算
总复杂度
#include <bits/stdc++.h> using namespace std; int T; long long N,M; struct Node{ long long a,b; }a[100005]; long long temp(Node x,Node y){ return (x.a<y.a); } long long c[100005],Sum[100005]; int main(){ cin>>T; while (T--){ cin>>N>>M; for (int i=1;i<=M;i++) scanf("%d%d",&a[i].a,&a[i].b); sort(a+1,a+M+1,temp); long long ans=0,res=0; if (N>M) for (int i=1;i<=M;i++){ ans+=a[i].a; res=max(res,a[i].b); } else{ int cntt=0; for (int i=M;cntt<N;i--,cntt++) ans+=a[i].a; } ans=ans+1ll*(N-M)*res; for (int i=1;i<=M;i++){ c[i]=a[i].a; Sum[i]=Sum[i-1]+a[i].a; } for (int i=1;i<=M;i++){ long long nw=a[i].b; long long x=lower_bound(c+1,c+M+1,nw)-c-1; if (M-x>N) continue; else{ long long Ans=Sum[M]-Sum[x]; long long nd=M-x; if (i<=x) Ans+=a[i].a,nd++; if (nd>N) continue; Ans+=1ll*(N-nd)*a[i].b; ans=max(ans,Ans); } } printf("%lld\n",ans); } return 0; }
DEF还没看
老年选手两小时只能写到C了
Day3
打了场ARC,然后心态崩了(
忘记开long long了C题挂了,到最后都没发现是long long的问题(
B二分dp最后都没调出来(
看了眼D发现会但是来不及了
考虑删除一个数字后的序列会不会更优,如果会的话就直接删
如果不会的话就继续找。
#include <iostream> #include <stdio.h> using namespace std; int N; int a[200005]; bool b[200005]; int main(){ cin>>N; a[N+1]=-1; int X=-3; for (int i=1;i<=N;i++){ scanf("%d",&a[i]); } for (int i=1;i<=N;i++){ int x=a[i]; while (a[i]==a[i+1]) i++; if (a[i+1]<x&&!b[a[i]]) {X=a[i];break;} b[a[i]]=true; } for (int i=1;i<=N;i++) if (a[i]==X) continue; else cout<<a[i]<<" "; return 0; }
B - Dividing Subsequence
代码没调出来,大自闭(
把数组中每个位置在中的合法位置找出来,因为题目给的是个排列,所以根据调和级数,这一定是级别的
然后问题就是给你数组和对应的一堆位置,找一个最长上升的子序列。
求解最长上升子序列是有一个的二分dp做法的
又因为找最接近的上升数需要再二分一次
所以效率是的
代码还没调出来
C - Row Column Sums
显然全部填一定是最优的
现在加入限制
限制怎么满足?
对于每一行和每一列,先把它达到它的限制所需要调整的量算出来
这个用一个模意义下的加减法就可以处理
然后会发现,这个调整量的和同余才能调整是显然的(因为对行的改变也会同样的反应在相应的列上)
然后调整量小的是可以在调整量大的调整的时候顺便被调整的
所以调整量就是两个调整量中较大的那个
最后答案就是,表示调整量的和。
#include <bits/stdc++.h> using namespace std; long long K,H,W; long long ans1,ans2,ans; int main(){ cin>>H>>W>>K; ans=1ll*H*W*1ll*(K-1ll); long long nd=1ll*W*(K-1ll)%K; for (int i=1;i<=H;i++){ long long x; scanf("%lld",&x); ans1=ans1+((nd-x)+K)%K; } nd=1ll*H*(K-1)%K; for (int i=1;i<=W;i++){ long long x; scanf("%lld",&x); ans2=ans2+(nd-x+K)%K; } if (ans1%K!=ans2%K) printf("-1"); else{ ans=ans-max(ans1,ans2); cout<<ans; return 0; } }
D - Range XOR
最后十几分钟看了一眼,口胡了个思路
先写着,错了再说吧(
题目考虑的是之间的自然数连续异或和
因为异或的性质,我们可以拆成和的自然数连续异或和考虑
之间的连续异或和就是的异或和跟的异或
然后自然数的连续异或和是有结论的
如果不知道的话打表也能发现
大概是这样:
令
连续异或和的值为:
那么对于值为和的部分,显然我们只需要考虑和所产生的数字是否能由情况和情况产生即可
对于只由情况和情况构成的数
问题转化为如下问题:
问,且或且 的无序数对的数对个数
按照二进制位做一个类似于数位dp的东西就行了
还没写,如果写了挂了再说吧(
还是太久没做题了,代码水平和思维水平都好差……
CF1633D
发现的最大值才,要求的最少次数直接dp找就行了。
然后问题变成一个背包问题
然后发现到级别没有意义,因为值最大也才12
于是和比较一下就行了。
#include <bits/stdc++.h> using namespace std; int dp[1005],dp1[32005],ans; const int mx=1000; int T; int N,K,b[1005],c[1005]; int main(){ cin>>T; memset(dp,63,sizeof(dp)); dp[1]=0; for (int i=1;i<=mx;i++) for (int j=1;j<=i;j++){ int x=i+i/j; if (x<=mx) dp[x]=min(dp[x],dp[i]+1); } while (T--){ cin>>N>>K; K=min(K,12*N); for (int i=1;i<=N;i++){ int x; cin>>x; b[i]=dp[x]; } ans=0; memset(dp1,0,sizeof(dp1)); for (int i=1;i<=N;i++) cin>>c[i]; for (int i=1;i<=N;i++) for (int j=K;j>=b[i];j--){ dp1[j]=max(dp1[j],dp1[j-b[i]]+c[i]); ans=max(ans,dp1[j]); } cout<<ans<<endl; } return 0; }
CF1632D
套路,一段连续的gcd,随着区间长度的增加,它的gcd一定是递减的
而区间长度是递增的
于是这两个函数如果有交点一定只有一个交点
于是就对于每一个位置,二分找一下是否有令它不合法的右端点
至于连续gcd的话,线段树随便写写就行了
如果有的话就存起来
然后考虑问题的转化
如果我修改区间中的一个数字为一个没有出现过的素数,那么这个区间的gcd一定是
这样的话问题就变成了
对于一堆线段,每条线段上都要取其中一个点,问你最少取几个点
这就是一个经典的贪心问题
然后题目要的是前缀
我们离线一下,根据排个序
然后每次做的时候把答案标记在上,只有右侧是这个答案
然后扫一遍区间输出即可。
#include<bits/stdc++.h> using namespace std; struct Node{ int l,r; }a[200005]; int Tree[800005],N,cnt,ans[200005]; int aa[200005]; bool temp(Node a,Node b){ return (a.r<b.r); } void Build(int nw,int l,int r){ if (l==r){ Tree[nw]=aa[l]; return; } int mid=(l+r)>>1; Build(nw<<1,l,mid); Build(nw<<1|1,mid+1,r); Tree[nw]=__gcd(Tree[nw<<1],Tree[nw<<1|1]); return; } int query(int Now,int L,int R,int l,int r){ if (L<=l&&r<=R) return Tree[Now]; int mid=(l+r)>>1; if (L<=mid&&mid<R) return __gcd(query(Now<<1,L,R,l,mid),query(Now<<1|1,L,R,mid+1,r)); else if (L<=mid) return query(Now<<1,L,R,l,mid); else if (mid<R) return query(Now<<1|1,L,R,mid+1,r); } void Pre(){ for (int i=1;i<=N;i++){ int l=i,r=N+1; while (l<=r){ int mid=(l+r)>>1; if (query(1,i,mid,1,N)>mid-i+1) l=mid+1; else if (query(1,i,mid,1,N)==mid-i+1){ a[++cnt].r=mid; a[cnt].l=i; break; } else r=mid-1; } } } int main(){ scanf("%d",&N); for (int i=1;i<=N;i++) scanf("%d",&aa[i]); Build(1,1,N); Pre(); sort(a+1,a+cnt+1,temp); int x=a[1].r,Ans=1; ans[a[1].r]=Ans; for (int i=1;i<=cnt;i++) if (a[i].l<=x) continue; else{ x=a[i].r; Ans++; ans[a[i].r]=Ans; } int nw=0; for (int i=1;i<=N;i++){ if (ans[i]!=0&&ans[i]!=nw) nw=ans[i]; printf("%d ",nw); } return 0; }
CF1632E
思路歪了没做出来(
麻了.jpg
这种最大值最小的问题先考虑能不能二分
假装二分出一个答案,发现其实只要所有的大于的点中,距离最远的两个点的距离除以2加上当前枚举的边权比小就可以了(类似树的直径,但是这里的树只有一部分点,连接“直径”的中点和号点就行了)
而本来就小于的值根本就不用管。
因为题目要的是所有的
所以我们考虑离线一下
表示最大深度恰为的点的最大距离
这个东西跑一遍dfs,在lca处记录统计一下即可
然后再后缀取个max,找到深度大于时,点的最大距离
然后发现答案一定是单调递增的,于是我们枚举的同时把答案算一下即可。
#include <bits/stdc++.h> using namespace std; int las[600005],d[600005],N,Arrive[600005],cnt,nex[600005]; void jt(int x,int y){ cnt++; nex[cnt]=las[x]; las[x]=cnt; Arrive[cnt]=y; } void Clear(){ cnt=0; for (int i=0;i<=N;i++) d[i]=0; for (int i=1;i<=N;i++) las[i]=0; } int dfs(int Now,int fa,int dep){ int x1=dep,x2=dep; for (int i=las[Now];i;i=nex[i]){ int v=Arrive[i]; if (v==fa) continue; int nw=dfs(Arrive[i],Now,dep+1); if (nw>x1){ x2=x1,x1=nw; } else if (nw>x2){ x2=nw; } } int Dis=x2-1; if (Dis>=0) d[Dis]=max(d[Dis],x1+x2-2*dep+1); return x1; } void Init(){ for (int i=1;i<=N-1;i++){ int u,v; cin>>u>>v; jt(u,v); jt(v,u); } } void Solve(){ int Ans=dfs(1,1,0); for (int i=N-2;i>=0;i--) d[i]=max(d[i],d[i+1]); int ans=0; for (int i=1;i<=N;i++){ while (ans<Ans&&i+d[ans]/2>ans) ans++; cout<<ans<<" "; } cout<<endl; return; } int main(){ int T; cin>>T; while (T--){ Clear(); cin>>N; Init(); Solve(); } }
CF1637D
稍微展开一下式子
考虑一个位置的贡献
拆开
对于最后一项,把提出去
同样的,对于来说,把所有的替换成即可
发现交换两个数字时,会对答案产生影响的只有最后一项
发现且
我们可以设计一个dp,dp[i][j]表示填到第位,前缀和为时的答案
的前缀和用 总的前缀和-的前缀和即可转移
#include <bits/stdc++.h> using namespace std; long long dp[105][105*105]; int a[105],b[105],Sum,N; long long ans; int T; int main(){ cin>>T; while (T--){ scanf("%d",&N); ans=0,Sum=0; for (int i=1;i<=N;i++){ scanf("%d",&a[i]); } for (int i=1;i<=N;i++){ scanf("%d",&b[i]); } for (int i=1;i<=N;i++) for (int j=0;j<=100*N;j++) dp[i][j]=1e9+7; dp[1][a[1]]=dp[1][b[1]]=0; long long Sum1=0; for (int i=1;i<=N;i++){ Sum+=a[i]+b[i]; for (int j=0;j<=100*N;j++){ if (Sum-j-b[i]>=0&&j-a[i]>=0) dp[i][j]=min(dp[i][j],(i-1)*b[i]*b[i]+(i-1)*a[i]*a[i]+Sum1+dp[i-1][j-a[i]]+2ll*(j-a[i])*1ll*a[i]+2ll*(Sum-j-b[i])*1ll*b[i]); if (j-b[i]>=0&&Sum-j-a[i]>=0) dp[i][j]=min(dp[i][j],(i-1)*b[i]*b[i]+(i-1)*a[i]*a[i]+Sum1+dp[i-1][j-b[i]]+2ll*(j-b[i])*1ll*b[i]+2ll*(Sum-j-a[i])*1ll*a[i]); } Sum1+=a[i]*a[i]+b[i]*b[i]; } long long Ans=1e9+7; for (int i=1;i<=100*N;i++) Ans=min(Ans,dp[N][i]); cout<<ans+Ans<<endl;; } return 0; }
2019-2020 ICPC Asia Hong Kong Regional Contest
队友好猛
随便写点东西(
D. Defining Labels
签到,显然是个进制转换再找找规律
随便写写就行了
C. Constructing Ranches
看到所有路径显然的想到点分治
形成多边形的条件是2*边中最大的大于其他所有的和
移项,存一下最大值和路径和,离散化一下树状数组统计答案
队友写的所以没有代码
J. Junior Mathematician
我写的.jpg(
显然的数位dp
容易想到用dp[i][j][k][l]表示填到位,位数和在模意义下为,当前数在模意义下为,在模的意义下为
但是发现这样5000*60*60*60内存爆了
发现后两维其实是为了判断和之间在模意义下的关系而存在的
于是把后两维压成一维,即
然后就够了
然后记得,或者像我的代码里直接判断是否合法
然后我就被卡常了
教训是在多次调用的部分里取模少写,然后乘除法尽量少写
多写一个 *1ll 被卡爆了(
#include <bits/stdc++.h> using namespace std; char ss1[5005],ss2[5005]; int M; int mxx; const int fish=1e9+7; long long dp[5005][65][65]; int a[5005]; int Pow[5005]; int T; long long dfs(int Now,int Sum,int F,bool chk){ if (!Now) return (F==0); if (dp[Now][Sum][F]!=-1&&!chk) return dp[Now][Sum][F]; int mx=chk?a[Now]:9; long long ans=0; for (int i=0;i<=mx;i++) ans+=dfs(Now-1,(Sum+i)%M,(F+Sum*i%M-Pow[Now-1]*i%M+M)%M,chk&&i==mx); (ans+=fish)%=fish; if (!chk) dp[Now][Sum][F]=ans; return ans; } void Clear(){ for (int i=1;i<=mxx;i++) for (int j=0;j<=M;j++) for (int k=0;k<=M;k++) dp[i][j][k]=-1; return; } void Pre(){ Clear(); Pow[0]=1; for (int i=1;i<=mxx;i++){ Pow[i]=Pow[i-1]*10ll%M; } return; } int main(){ cin>>T; Pre(); while (T--){ scanf("%s%s%d",ss1,ss2,&M); int Len=strlen(ss1); mxx=Len; int Len1=strlen(ss2); mxx=max(mxx,Len1); Pre(); for (int i=Len-1;i>=0;i--) a[Len-i]=ss1[i]-'0'; a[1]--; for (int i=1;i<=Len;i++){ if (a[i]<0) a[i+1]--,a[i]+=10; else break; } int ans=dfs(Len,0,0,1); Len=strlen(ss2); for (int i=Len-1;i>=0;i--) a[Len-i]=ss2[i]-'0'; ans=(dfs(Len,0,0,1)-ans+fish)%fish; printf("%d\n",ans); } return 0; }
赛后补题:
I. Incoming Asteroids
最后半小时口胡了个思路,没写完(
发现只有3,然后发现每次都要求输出所有满足条件的人,我的想法是用某种手段快速的找出可能是答案的人选,然后再一个个检定过去
发现,则一定要有一个数字比大
于是我们可以在每次出现当前位置的总时间-该元素进入时间 大于的时候才进行检定(即当前元素是所有需要的位置里最大的那个)
做完检定后,如果满足的话,就把它加入答案队列里
如果不满足的话,则把剩下的部分重新按照相同的方式进行分配,扔回各自对应的里
这么做的话,发现其实每个元素每次至少会除2,所以其实每个元素会被访问到的次数是log级别的
然后用 set 维护一下就行。
#include<bits/stdc++.h> using namespace std; struct Node{ long long nd; int id; long long Time; bool operator <(const Node &a) const{ return nd<a.nd||nd==a.nd&&id<a.id||nd==a.nd&&id==a.id&&Time<a.Time; } }; set<Node> a[500005]; int b[500005][4],cnt,Nd[500005]; long long d[500005]; int lst; Node c[500005][4]; int N,T; int main(){ scanf("%d%d",&N,&T); while (T--){ int opt; scanf("%d",&opt); if (opt==1){ int y,k; scanf("%d%d",&y,&k); y^=lst; Nd[++cnt]=y; for (int i=1;i<=k;i++){ scanf("%d",&b[cnt][i]); b[cnt][i]=b[cnt][i]^lst; Node New; New.nd=(y+k-1)/k+d[b[cnt][i]]; New.id=cnt; New.Time=d[b[cnt][i]]; a[b[cnt][i]].insert(New); c[cnt][i]=New; } } else{ int x,y; scanf("%d%d",&x,&y); x^=lst;y^=lst; d[x]+=y; vector<int> ans; while (!a[x].empty()){ auto it=a[x].begin(); Node Now=*it; if (d[x]>=Now.nd){ vector <Node> nw; int xx=Now.id; long long Get=0; int K=0; for (int i=1;i<=3;i++) if (b[xx][i]){ K++; Node Need; Need=c[xx][i]; int yy=b[xx][i]; auto it1=a[yy].lower_bound(Need); a[yy].erase(it1); Get+=d[yy]-Need.Time; } Nd[xx]-=Get; if (Nd[xx]<=0){ ans.push_back(xx); continue; } for (int i=1;i<=3;i++) if (b[xx][i]){ Node New; New.id=xx; New.nd=(Nd[xx]+(K-1))/K+d[b[xx][i]]; New.Time=d[b[xx][i]]; a[b[xx][i]].insert(New); c[xx][i]=New; } } else break; } printf("%d",ans.size()); lst=ans.size(); sort(ans.begin(),ans.end()); for (int i=0;i<ans.size();i++){ printf(" %d",ans[i]); } printf("\n"); } } return 0; }
The 2020 ICPC Asia Macau Regional Contest
A. Accelerator
期望题
首先题目这个式子你稍微处理一下会发现其实本质上就是求一个随机排序的数组的后缀积的和的期望
整体统计答案不好统计,可以转化成各个元素的贡献去推
大概是
然后回发现前面的系数可以统一乘,后半段其实本质是从n个数的数组里选择个数字乘起来然后求和的总贡献。
扔给负责多项式的学长之后说是直接ntt就行了
然后就过了
队友写的所以没有代码
D.Artifacts
模拟,没啥好说的
写的学长写了好久,最后我上机帮忙debug了两下过了(
#include <bits/stdc++.h> using namespace std; double ans; double ATK,ATKRate,CritDMGRate,CritRate,a; string s; int main() { ATK=ATKRate=CritDMGRate=CritRate=a=0.00; ATK=0.00;CritRate=0.05;CritDMGRate=0.50; for(int i=1;i<=5;i++) { // ATK=ATKRate=CritDMGRate=CritRate=a=0.00; // s=""; // ATK=0.00;CritRate=0.05;CritDMGRate=0.50; int flag=0; double dd=1.00; bool book=false; for(int j=1;j<=5;j++) { s="";a=0.00;flag=0;book=false;dd=1.00; char c; while(c=getchar()) { if(c=='+') { if(s=="ATK") flag=1; else if(s=="ATK Rate") flag=2; else if(s=="Crit DMG Rate") flag=3; else if(s=="Crit Rate") flag=4; break; } if (c!='\n')s=s+c; } while((c=getchar())&&c!='\n') { if(c=='%') break; if(c=='.') { book=true; continue; } a=a*10.00+(c-'0')*1.00; if(book) dd*=10.00; } a=a/dd; if(flag==1) { //cout<<a<<endl; ATK+=a; } else if(flag==2) { //scanf("%lf",&a); a=a/100.00; ATKRate+=a; //cout<<a<<endl; } else if(flag==3) { //scanf("%lf%",&a); a=a/100.00; CritDMGRate+=a; //cout<<a<<endl; } else if(flag==4) { a=a/100.00; CritRate+=a; //cout<<a<<endl; } } } ATK=ATK+1500.00*(1.00+ATKRate); CritRate=min(CritRate,1.00); ans=ATK*(1+CritDMGRate*CritRate); printf("%.10lf",ans); return 0; }
G. Game on Sequence
我写的(
因为写的太丑贡献了6发罚时
我是傻逼(
首先可以比较容易的想到一个
表示以i为开头,先手必胜或必败
那么显然转移就是
如果可以到的一个点是必败态,那么这个点就是必胜态
但是如果大力跑这东西每次都是的,会T飞
发现题目有个小提示,的范围最多只到,复杂度应该会有一个
所以我开始思考重复元素的性质
发现其实重复元素除了最后一个元素,其他的都是必胜的
prove:
对于最后一个元素来说,如果它是先手必胜态的话,那么一定在它之后有一个先手必败态让它成为了必胜态。
而因为元素相同,所以我可以越过这个元素直接跳到那个必败的位置,先手胜利
如果最后一个元素是必败的话,那直接跳到这个元素即可。
于是我们就发现其实这个只会和最多个位置有关系,每次跑询问的时候转移一下就行。
然后有一个小trick,添加数字的次数比询问次数多,所以操作的时候不算答案,之后询问的时候跑这个dp就行了,转移的时候显然优先转移靠后的元素。
#include <bits/stdc++.h> using namespace std; int N,M; int a[400005],cnt,lst[500]; bool dp[500]; void Add(){ priority_queue<int> b; for (int i=0;i<=255;i++) if (lst[i]) b.push(lst[i]); while (!b.empty()){ int nw=b.top(); b.pop(); bool flag=false; for (int i=0;i<8;i++){ int y=a[nw]^(1<<i); if (dp[y]==0&&lst[y]>nw){ flag=true;break; } } if (!flag) dp[a[nw]]=0; else dp[a[nw]]=1; } } int main(){ scanf("%d%d",&N,&M); for (int i=1;i<=N;i++){ int xx; scanf("%d",&xx); cnt++; a[cnt]=xx; lst[xx]=cnt; } for (int i=1;i<=M;i++){ int opt; scanf("%d",&opt); if (opt==1){ int xx; scanf("%d",&xx); cnt++; a[cnt]=xx; lst[xx]=cnt; } else{ int pos; scanf("%d",&pos); Add(); if (pos!=lst[a[pos]]) printf("Grammy\n"); else if (dp[a[pos]]==1) printf("Grammy\n"); else printf("Alice\n"); } } return 0; }
L. Random Permutation
结论
类似于错排的推法,表示排列长度为的时候的期望答案
然后把这个数塞进排列就好了
所以答案是
暴力跑这个函数的值即可
代码不贴了
赛后补题:
F. Fixing Networks
我是弱智
首先,比较显然的想法是先构造个点的完全图,把个联通图先弄好
现在问题就转化成了如下问题:
构造一个有个点的无向图,每个点的度都是,是否有解
有则输出方案,没有则输出no
由于这个图的对称性非常强,所有点的度都一样
其实不难想到(但是赛时没想到……)可以把所有的点做成一个环
因为这样的话每个点覆盖别人和被覆盖的次数都会是一样的
然后每个点向环上前和后个点连边(下取整)
然后因为对称性,如果d是奇数的话一定会少一条,而和它对面的那个点也少一条,所以两个接上就好了
判断无解的条件就是 和不能同时是奇数,否则无法完全配对。
没了(
#include <bits/stdc++.h> using namespace std; int n,d,c; int Getid(int x,int md){ int t; if (x<0) t=((x+md)%md); else t=x%md; if (t!=0) return t; else return md; } int main(){ cin>>n>>d>>c; if (d==1){ if(n==c*2){ printf("Yes\n"); for (int i=1;i<=n;i++) if (i&2) printf("%d\n",i+1); else printf("%d\n",i-1); } else printf("No"); return 0; } if (d==0){ if (n==c){ printf("Yes\n"); } else printf("No\n"); return 0; } int nd=c*(d+1); if (nd>n){ printf("No\n"); return 0; } int res=n-(d+1)*(c-1); if ((res&1)&&(d&1)) printf("No\n"); else{ printf("Yes\n"); int T=d/2; for (int i=1;i<=res;i++){ set<int> ans; for (int j=i-T;j<=i+T;j++) if (j!=i) ans.insert(Getid(j,res)); if (d&1) ans.insert(Getid(i+res/2,res)); for (auto j:ans) printf("%d ",j); printf("\n"); } for (int i=res+1;i<=n;i+=(d+1)){ for (int j=i;j<=i+d;j++){ for (int k=i;k<=i+d;k++){ if (j==k) continue; else printf("%d ",k); } printf("\n"); } } } return 0; }
C. Club Assignment
最小异或生成树
把数字一个个从高位到低位插入最小异或生成树中
首先,三个以上相同的数答案一定为0,特判。
然后考虑什么子树有贡献
当子树大小为1,2,3,4时才有贡献
对于2,直接把两个数字分开放
3的话,一定一个子树是1,一个子树是2
子树是2的肯定优先放在两个集合,保证高位大小
所以枚举剩下那个位置和谁配对
4是2+2,也是暴力枚举谁和谁配对
对于更大的子树,一定可以通过递归到<=4的子树
#include<bits/stdc++.h> using namespace std; int N,cnt; int L[31*200005],R[32*200005],Tree[31*200005][3],as[200005]; long long Ans; struct Node{ int x; long long id; }a[200005]; int temp(Node x,Node y){ return x.x<y.x; } void Insert(long long x,int pos){ int Now=0; for (int i=32;i>=0;i--){ if ((x>>i)&1ll){ int &nw=Tree[Now][1]; if (!nw) nw=++cnt; if (!L[nw]) L[nw]=pos; R[nw]=pos; Now=nw; } else{ int &nw=Tree[Now][0]; if (!nw) nw=++cnt; if (!L[nw]) L[nw]=pos; R[nw]=pos; Now=nw; } } } void Getans(int rt,int pos){ int x=Tree[rt][0],y=Tree[rt][1]; if (y) Getans(y,pos-1); if (x) Getans(x,pos-1); if (!rt) return; int xx=R[x]-L[x]+1,yy=R[y]-L[y]+1; if (R[rt]-L[rt]+1<=2){ as[a[L[rt]].id]=1; as[a[R[rt]].id]=2; return; } if (R[rt]-L[rt]+1==3){ if (!x||!y) return; int xx=R[x]-L[x]+1,yy=R[y]-L[y]+1; if (xx==1){ long long ans=0; int ps=0; for (int i=L[y];i<=R[y];i++) if (ans<(a[L[x]].x^a[i].x)){ ans=a[L[x]].x^a[i].x; ps=i; } //cout<<ps<<endl; Ans=min(Ans,ans); as[a[L[x]].id]=as[a[ps].id]; } else{ long long ans=0; int ps=0; for (int i=L[x];i<=R[x];i++) if (ans<(a[L[y]].x^a[i].x)){ ans=a[L[y]].x^a[i].x; ps=i; } Ans=min(Ans,ans); as[a[L[y]].id]=as[a[ps].id]; //cout<<a[L[y]].id<<" "<<a[ps].id<<endl; } return; } else if (R[rt]-L[rt]+1==4){ if (!x||!y) return; int xx=R[x]-L[x]+1,yy=R[y]-L[y]+1; if (xx==2&&yy==2){ long long a1=min(a[R[x]].x^a[R[y]].x,a[L[x]].x^a[L[y]].x); long long a2=min(a[R[x]].x^a[L[y]].x,a[L[x]].x^a[R[y]].x); if (a1>=a2){ Ans=min(Ans,a1); //cout<<"qwq"<<endl; as[a[L[x]].id]=as[a[L[y]].id]; as[a[R[x]].id]=as[a[R[y]].id]; } else{ //cout<<Ans<<endl; //cout<<"qwq"<<endl; Ans=min(Ans,a2); //cout<<Ans<<endl; as[a[R[x]].id]=as[a[L[y]].id]; as[a[L[x]].id]=as[a[R[y]].id]; } return; } } return; } void Clear(){ for (int i=0;i<=cnt;i++){ Tree[i][0]=Tree[i][1]=L[i]=R[i]=0; } cnt=0; return; } int main(){ int T; scanf("%d",&T); while (T--){ Clear(); Ans=1e17; map<int,int> mp; scanf("%d",&N); bool flag=false; for (int i=1;i<=N;i++){ scanf("%d",&a[i].x); a[i].id=i; mp[a[i].x]++; as[i]=0; if (mp[a[i].x]>2) flag=true; } if (flag){ printf("0\n"); printf("1"); for (int i=2;i<=N;i++) printf("2"); printf("\n"); continue; } sort(a+1,a+N+1,temp); for (int i=1;i<=N;i++) Insert(a[i].x,i); Getans(0,32); for (int i=1;i<=N;i++) if (!as[i]) as[i]=1; bool flag1=false,flag2=false; for (int i=1;i<=N;i++){ if (as[i]==1) flag1=true; else flag2=true; } if (!flag1) as[1]=1; if (!flag2) as[1]=2; printf("%lld\n",Ans); for (int i=1;i<=N;i++) printf("%d",as[i]); printf("\n"); } return 0; }
2020-2021 ACM-ICPC, Asia Nanjing Regional Contest
E. Evil Coordinate
考虑分类讨论
首先终点如果是个地雷输出impossible
假设地雷点在的右上方
于是我们可以考虑先尽量往远离它的方向走,先左后右,先下后上
但是这样会遇到一个问题
如果遇到地雷点有一维坐标恰好和终点相同时,这样模拟可能会导致在最后一次走的过程中撞到地雷
于是我们可以通过调整向上/下,向左/右的顺序来规避这个情况
发现其实范围并不太大
为了避免复杂的分类讨论,直接暴力枚举全排列来做一个模拟。
队友写的所以没有代码
F. Firework
打上花火
首先,最优策略肯定是每做个放一次
于是我们不妨假设这个最优的点是
第一次放有一个的时间期望:
第二次放有一个的时间期望:
(前次全部没有,后次至少一个)
第三次放有一个的时间期望:
以此类推
然后根据期望的线性性求个和
把和也提出来
然后会发现,求和的项构成一个以的为公比的等比数列和一个以为公差的等差数列相乘的结果
运用高中数学求一下这个求和式子趋于无穷时的解
最后解出来是
对着这个式子求一下二阶导,发现是单峰的
三分一下即可
#include <bits/stdc++.h> using namespace std; int T,N,M,P; double p; const double eps=1e-6; double F(double k){ double ans = (N*k+M)/(1-pow(p,k)); return ans; } int main(){ scanf("%d",&T); while (T--){ scanf("%d%d%d",&N,&M,&P); if (P==10000){ printf("%d\n",N+M); continue; } p=1-P/10000.0; double l=0,r=1e9; while (fabs(l-r)>1e-7){ double mid=(l+r)/2; //cout<<F(mid-eps)<<endl; if (F(mid-eps)>F(mid+eps)) l=mid; else r=mid; } int ans=l; printf("%.10lf\n",min(F(ans),F(ans+1))); } return 0; }
H. Harmonious Rectangle
考虑一个的矩阵
显然,当的时候,无论怎么填都是满足题意的
所以当或者的时候,答案就是
且的时候直接打表即可
#include <bits/stdc++.h> using namespace std; int N,T,M; const long long fish=1e9+7; const int Table[][10]={ {0, 0, 0, 0, 0, 0, 0, 0, 0,}, {0, 15, 339, 4761, 52929, 517761, 4767849, 43046721, 387420489,}, {0, 339, 16485, 518265, 14321907, 387406809, 460338013, 429534507, 597431612,}, {0, 4761, 518265, 43022385, 486780060, 429534507, 792294829, 175880701, 246336683,}, {0, 52929, 14321907, 486780060, 288599194, 130653412, 748778899, 953271190, 644897553,}, {0, 517761, 387406809, 429534507, 130653412, 246336683, 579440654, 412233812, 518446848,}, {0, 4767849, 460338013, 792294829, 748778899, 579440654, 236701429, 666021604, 589237756,}, {0, 43046721, 429534507, 175880701, 953271190, 412233812, 666021604, 767713261, 966670169,}, {0, 387420489, 597431612, 246336683, 644897553, 518446848, 589237756, 966670169, 968803245}, }; long long Pow(long long x,long long y){ long long ans=1; while (y){ if (y&1) ans=1ll*ans*x%fish; x=1ll*x*x%fish; y>>=1ll; } return ans%fish; } int main(){ scanf("%d",&T); int N,M; while (T--){ scanf("%d%d",&N,&M); if (N==1||M==1) cout<<"0\n";else if (N>9||M>9) printf("%lld\n",Pow(3ll,1ll*N*M)); else printf("%d\n",Table[N-1][M-1]); } return 0; }
K. K Co-prime Permutation
构造满足的对数为的排列
所以显然把前个数循环右移一位即可。
注意特判
代码是学长写的,不贴了
L. Let's Play Curling
转化成数轴上的点的距离
画画图发现其实本质上就是两个之间最多的的数量
M. Monster Hunter
树上背包,扔给学长写的,回头可能会自己写一遍
赛后补题:
D. Degree of Spanning Tree
考虑先随便找一颗生成树
一个显然的结论,一颗生成树里最多一个度大于的点
考虑怎么调整这个度大于的点
先把它放到根上
如果两个点的是这个点,那么把这两个点接上之后,可以删掉一个根的度(画图理解)
但是有一种情况,如果删完之后又产生了一个大于的点
就说明接的这个点实际上是接着根的,分类讨论一下换一个点接上就好了
动态查询可以写个lct,但是对于这题来说,我们只需要知道他们的是不是根。所以对直接接在根上的点做并查集,如果同属于一个集合就不是。
不断调整直到无法调整为止
写的时候注意细节和清空数组
#include<bits/stdc++.h> using namespace std; int N,M; vector<pair<int,int> >a; map<pair<int,int> ,int> mp; int fa[100005],Fa[100005]; int las[400005],cnt,nex[400005],d[100005],Arrive[400005]; void jt(int x,int y){ cnt++; nex[cnt]=las[x]; las[x]=cnt; Arrive[cnt]=y; } int Getfa(int x){return (x==fa[x])?x:fa[x]=Getfa(fa[x]);} int Getfa1(int x){return (x==Fa[x])?x:Fa[x]=Getfa1(Fa[x]);} void dfs(int Now,int faa,int Rt){ fa[Now]=Rt; for (int i=las[Now];i;i=nex[i]){ int v=Arrive[i]; if (v==faa) continue; dfs(v,Now,Rt); } } void Clear(){ a.clear(); mp.clear(); cnt=0; for (int i=1;i<=N;i++) d[i]=las[i]=0; } int main(){ int T; scanf("%d",&T); while (T--){ Clear(); scanf("%d%d",&N,&M); for (int i=1;i<=M;i++){ int u,v; scanf("%d%d",&u,&v); if (u>v) swap(u,v); pair<int,int> nw=make_pair(u,v); if (mp[nw]||u==v) continue; mp[nw]=true; a.push_back(nw); } for (int i=1;i<=N;i++) Fa[i]=i; map<pair<int,int>,bool> Cho; for (auto nw:a){ int u=nw.first,v=nw.second; int fu=Getfa1(u),fv=Getfa1(v); if (fu!=fv){ Fa[fu]=fv; jt(u,v); jt(v,u); d[u]++,d[v]++; Cho[nw]=true; } } bool flag1=false; for (int i=2;i<=N;i++) if (Getfa1(i)!=Getfa1(1)) {flag1=true;break;} if (flag1){ printf("No\n"); continue; } bool flag=false; int rt=0; for (int i=1;i<=N;i++) if (d[i]>(N/2)){ flag=true; rt=i; break; } if (!flag){ printf("Yes\n"); for(auto nw:a){ if (Cho[nw]) printf("%d %d\n",nw.first,nw.second); } continue; } for (int i=1;i<=N;i++) fa[i]=i; for (int i=las[rt];i;i=nex[i]) dfs(Arrive[i],rt,Arrive[i]); for (auto nw:a){ if (Cho[nw]) continue; int u=nw.first,v=nw.second; int fx=Getfa(u),fy=Getfa(v); if (fx==fy||u==rt||v==rt) continue; d[rt]--;d[fx]--; d[u]++;d[v]++; if (d[u]>N/2||d[v]>N/2){ d[fx]++,d[fy]--; if (d[u]>N/2||d[v]>N/2){ d[rt]++; d[fy]++; d[u]--; d[v]--; continue; } else{ fa[fy]=fx; Cho[nw]=true; Cho[{min(rt,fy),max(rt,fy)}]=false; } } else{ fa[fx]=fy; Cho[nw]=true; Cho[{min(rt,fx),max(rt,fx)}]=0; } if (d[rt]<=N/2) break; } if (d[rt]<=N/2){ printf("Yes\n"); for (auto nw:a){ if (Cho[nw]) printf("%d %d\n",nw.first,nw.second); } } else printf("No\n"); } return 0; }
J. Just Another Game of Stones
两个操作分开处理
区间取min/max操作是个经典的吉老师线段树
考虑怎么回答询问
我们知道游戏必胜的条件是区间和不为
先手只要取一堆,导致后手的和为就是必败
那么其实只要求区间的和之后,统计
的数的个数
显然,按照最高位讨论就可以了
之后可能会有个吉老师线段树的学习笔记
#include <bits/stdc++.h> using namespace std; const int M=2e5+10; struct Node{ int mn,sec,num,tag; int Sum,cnt[33]; }Tree[4*M]; int a[M]; const int inf=1<<30; void PushUp(int x){ Tree[x].Sum=Tree[x<<1].Sum^Tree[x<<1|1].Sum; for (int i=0;i<30;i++) Tree[x].cnt[i]=Tree[x<<1].cnt[i]+Tree[x<<1|1].cnt[i]; Node a=Tree[x<<1],b=Tree[x<<1|1]; if (a.mn<b.mn){ Tree[x].mn=a.mn; Tree[x].num=a.num; Tree[x].sec=min(a.sec,b.mn); } else if (a.mn>b.mn){ Tree[x].mn=b.mn; Tree[x].num=b.num; Tree[x].sec=min(b.sec,a.mn); } else{ Tree[x].mn=a.mn; Tree[x].num=a.num+b.num; Tree[x].sec=min(a.sec,b.sec); } } void PushDown(int x){ int nw=Tree[x].tag; if (nw>Tree[x<<1].mn&&nw<Tree[x<<1].sec){ for (int i=0;i<30;i++){ int st=1<<i; if (st>nw&&st>Tree[x<<1].mn) break; if (Tree[x<<1].mn&st) Tree[x<<1].cnt[i]-=Tree[x<<1].num; if (nw&st) Tree[x<<1].cnt[i]+=Tree[x<<1].num; } if (Tree[x<<1].num&1) Tree[x<<1].Sum^=Tree[x<<1].mn^nw; Tree[x<<1].mn=nw; Tree[x<<1].tag=nw; } if (nw>Tree[x<<1|1].mn&&nw<Tree[x<<1|1].sec){ for (int i=0;i<30;i++){ int st=1<<i; if (st>nw&&st>Tree[x<<1|1].mn) break; if (Tree[x<<1|1].mn&st) Tree[x<<1|1].cnt[i]-=Tree[x<<1|1].num; if (nw&st) Tree[x<<1|1].cnt[i]+=Tree[x<<1|1].num; } if (Tree[x<<1|1].num&1) Tree[x<<1|1].Sum^=Tree[x<<1|1].mn^nw; Tree[x<<1|1].mn=nw; Tree[x<<1|1].tag=nw; } } void Build(int Now,int l,int r){ if (l==r){ Tree[Now].tag=Tree[Now].mn=Tree[Now].Sum=a[l]; for (int i=0;i<30;i++){ int st=1<<i; if (a[l]&st) Tree[Now].cnt[i]++; } Tree[Now].sec=inf,Tree[Now].num=1; return; } int mid=(l+r)>>1; Build(Now<<1,l,mid),Build(Now<<1|1,mid+1,r); PushUp(Now); } void Modify(int Now,int l,int r,int L,int R,int x){ if (L<=l&&r<=R){ if (x<=Tree[Now].mn) return; int nw=x; if (Tree[Now].mn<x&&x<Tree[Now].sec){ for (int i=0;i<30;i++){ int st=1<<i; if (st>x&&st>Tree[Now].mn) break; if (Tree[Now].mn&st) Tree[Now].cnt[i]-=Tree[Now].num; if (nw&st) Tree[Now].cnt[i]+=Tree[Now].num; } // if (Tree[Now].num&1) Tree[Now].Sum^=Tree[Now].mn^nw; Tree[Now].mn=nw; Tree[Now].tag=nw; } else{ PushDown(Now); //if (Now==627376) cout<<x<<" "<<Tree[Now].mn<<endl; int mid=(l+r)>>1; Modify(Now<<1,l,mid,L,R,x),Modify(Now<<1|1,mid+1,r,L,R,x); PushUp(Now); } return; } PushDown(Now); int mid=(l+r)>>1; if (L<=mid) Modify(Now<<1,l,mid,L,R,x); if (mid<R) Modify(Now<<1|1,mid+1,r,L,R,x); PushUp(Now); } int GetAns(int Now,int l,int r,int L,int R,int w){ if (L<=l&&r<=R) return Tree[Now].cnt[w]; int mid=(l+r)>>1; if (l!=r) PushDown(Now); int ans=0; if (L<=mid) ans+=GetAns(Now<<1,l,mid,L,R,w); if (mid<R) ans+=GetAns(Now<<1|1,mid+1,r,L,R,w); return ans; } int GetSum(int Now,int l,int r,int L,int R){ if (L<=l&&r<=R) return Tree[Now].Sum; int mid=(l+r)>>1; if (l!=r) PushDown(Now); int ans=0; if (L<=mid) ans^=GetSum(Now<<1,l,mid,L,R); if (mid<R) ans^=GetSum(Now<<1|1,mid+1,r,L,R); return ans; } int main(){ int N,T; scanf("%d%d",&N,&T); for (int i=1;i<=N;i++) scanf("%d",&a[i]); Build(1,1,N); while (T--){ int opt,l,r,x; scanf("%d%d%d%d",&opt,&l,&r,&x); if (opt==1) Modify(1,1,N,l,r,x); else{ int st; st=GetSum(1,1,N,l,r); int pos=0; st^=x; if (!st) { printf("0\n"); continue; } for (int i=29;i>=0;i--) if (st&(1<<i)) { pos=i; break; } printf("%d\n",GetAns(1,1,N,l,r,pos)+((x^st)<=x)); } } return 0; }
2018-2019 ACM-ICPC, Asia Jiaozuo Regional Contest
A. Xu Xiake in Henan Province
签到,模拟
D. Keiichi Tsuchiya the Drift King
高中数学
分两种情况讨论
一种是车整个进入弯道,另一种情况是车没有完全进入弯道
前者最远点的轨迹显然是一个圆,算个半径完事
后者做一条平行线,算三角函数
临界条件是刚好出弯的时候碰到直道的角度
#include <bits/stdc++.h> using namespace std; int a,b,r,d; int T; #define PI 3.141592653589793 #define eps 1e-6 int main(){ cin>>T; while (T--){ cin>>a>>b>>r>>d; double Lim=1.0*b/(r+a); double Ang=d/360.0*2*PI; double Ang1=atan(Lim); double ans=0; if (Ang-Ang1>eps) ans=sqrt((r+a)*(r+a)+b*b)-r; else ans=(r+a)/cos(Ang)+(b-(r+a)*tan(Ang))*sin(Ang)-r; printf("%.10lf\n",ans); } return 0; }
F. Honeycomb
BFS,注意读入格式
#include <bits/stdc++.h> using namespace std; const int N=6e3+10,M=1e3+10; struct node { int x,y,d; }; char g[N][N]; bool vis[N][N]; vector<int>dx{-1,-2,-1,1,2,1}; vector<int>dy{-3,0,3,3,0,-3}; vector<int>centerx{-2,-4,-2,2,4,2}; vector<int>centery{-6,0,6,6,0,-6}; int main() { int T; cin>>T; while(T--) { int r,c; cin>>r>>c; getchar(); for(int i=1;i<=4*r+3;i++) gets(g[i]+1); pair<int,int>S,T; for(int i=1;i<=4*r+3;i++) for(int j=1;j<=6*c+3;j++) { vis[i][j]=0; if(g[i][j]=='S') S={i,j}; if(g[i][j]=='T') T={i,j}; } queue<node>q; q.push({S.first,S.second,0}); vis[S.first][S.second]=1; bool flag=0; while(q.size()) { node t=q.front(); q.pop(); if(t.x==T.first&&t.y==T.second) { cout<<t.d+1<<'\n'; flag=true; break; } for(int i=0;i<6;i++) { int x=t.x+dx[i]; int y=t.y+dy[i]; if(g[x][y]==' '&&x>0&&y>0&&x<=4*r+3&&y<=6*c+3) { int nx=t.x+centerx[i],ny=t.y+centery[i]; if(!vis[nx][ny]&&nx>0&&ny>0&&nx<=4*r+3&&ny<=6*c+3) q.push({nx,ny,t.d+1}),vis[nx][ny]=1; } } } if(!flag) cout<<-1<<'\n'; } return 0; }
B的贪心策略最后三十分钟想的差不多了,但是没机时了
C感觉应该是维护四根线,把动棋子转化成动线,然后按照线维护答案,也没来得及细想
感觉时间分配有问题
赛后补题
B. Ultraman vs. Aodzilla and Bodzilla
最后三十几分钟口胡了一下做法
把两个怪的贡献拆开算
+
其中一定有一个是总的轮数,另一个是某个怪被击杀的轮数
于是思路就来了
保证总轮数最少的前提下,使得一个怪被击杀的轮数尽可能少,同时保证字典序尽可能小
我们比较显然的会想到先一直打,再去打
但这样并不能保证总轮数最少
于是我们考虑先鲨还是
最小的总轮数显然是可以算出来的
然后我们还可以得到的最小死亡轮数
这时候考虑,如果在剩下的回合里可以打死的话,那么我们显然直接打
如果不能,就把前面一些打的轮数替换成
那么怎么替换呢? 显然是替换尽可能靠后的
于是我们就只替换一个位置,就是最大溢出伤害的位置。
而把放在前面,思考方式类似
但是要考虑字典序
如果能在相应的回合杀掉的话,那么就把的前几个位置换成(在保证打死的前提下)
如果不能,就从小往大凑,尽可能在靠前的位置打
#include <bits/stdc++.h> using namespace std; int T; const int N=1000005; long long Sum[1000010]; int hpa,hpb,atka,atkb; int main(){ cin>>T; for (int i=1;i<=N;i++) Sum[i]=Sum[i-1]+i; char ss1[N],ss[N]; while (T--){ scanf("%d%d%d%d",&hpa,&hpb,&atka,&atkb); int pos1=lower_bound(Sum+1,Sum+N+1,hpa)-Sum; int pos2=lower_bound(Sum+1,Sum+N+1,hpb)-Sum; int pos3=lower_bound(Sum+1,Sum+N+1,hpa+hpb)-Sum; int x=Sum[pos1]-hpa; int Sumb=Sum[pos3]-Sum[pos1]; long long ans1=0; ans1=1ll*pos1*1ll*atka+1ll*pos3*1ll*atkb; for (int i=1;i<=pos1;i++) ss[i]='A'; for (int i=pos1+1;i<=pos3;i++) ss[i]='B'; ss[0]=' '; ss1[0]=' '; ss[pos3+1]='\0'; if (Sumb<hpb) ss[x]='B'; int pos=lower_bound(Sum+1,Sum+N+1,Sum[pos2]-hpb)-Sum; if (Sum[pos]>Sum[pos2]-hpb) pos--; long long ans2=1ll*pos2*1ll*atkb+1ll*pos3*1ll*atka; for (int i=1;i<=pos2;i++) ss1[i]='B'; ss1[pos3+1]='\0'; for (int i=pos2+1;i<=pos3;i++) ss1[i]='A'; if (Sum[pos]+Sum[pos3]-Sum[pos2]>=hpa){ for (int i=1;i<=pos;i++) ss1[i]='A'; } else{ int nd=hpa-(Sum[pos3]-Sum[pos2]); for (int i=1;i<=pos2;i++){ if ((nd-i)>i||nd==i){ nd-=i; ss1[i]='A'; } } } if (ans1>ans2){ printf("%lld",ans2); printf("%s\n",ss1); } if (ans1<ans2){ printf("%lld",ans1); printf("%s\n",ss); } if (ans1==ans2){ printf("%lld",ans1); bool flag=false; for (int i=1;i<=pos3;i++) if (ss[i]>ss1[i]) {flag=true;break;} else if (ss[i]<ss1[i]){flag=false;break;} if (flag) printf("%s\n",ss1); else printf("%s\n",ss); } } return 0; }
C. Supreme Command
现场想到了把棋子的移动改成边界的移动,但是不太会写这个东西就润了。
赛后参考了一下网上的代码,发现思路是对的
麻了,我的代码能力太差了
因为棋子贴贴之后就不会再分开
所以不妨把棋子的移动转化成棋盘边界的移动,计算偏移量就可以表示本来点的坐标
而边界里面的点是没有被边界控制的
然后维护四条线就可以了
现在考虑询问在同一个点的点对数量
由于初始的棋盘每行每列只有一个棋子,所以其实可以发现只有四个角有可能有棋子贴贴
所以每次在推进边界的时候,顺手扫描一下四个角,组合数计算一下即可。
#include <bits/stdc++.h> using namespace std; int N,M; bool vis[300005]; int a[300005],b[300005]; long long LeftUp,RightUp,LeftDown,RightDown; long long C(long long x){ return x*(x-1)/2; } struct Node{ int pos[300005],id[300005]; int l,r,d; void Add(int x,int Pos){ pos[Pos]=x; id[x]=Pos; } int GetPos(int x){ if (x<l) return l+d; if (x>r) return r+d; return x+d; } }Line,Col; void Clear(){ Line.l=1,Line.r=N; Col.l=1,Col.r=N; Line.d=Col.d=0; } void Calc(int x){ if (vis[x]) return; if (Line.pos[x]<=Line.l && Col.pos[x]<=Col.l) { vis[x]=true; LeftUp++; }else if (Line.pos[x]>=Line.r && Col.pos[x]<=Col.l){ vis[x]=true; RightUp++; } else if (Line.pos[x]<=Line.l && Col.pos[x]>=Col.r){ vis[x]=true; LeftDown++; } else if (Line.pos[x]>=Line.r && Col.pos[x]>=Col.r){ vis[x]=true; RightDown++; } } void Lshift(Node &a,int k){ while (a.l<a.r&&a.l+a.d-k<1) Calc(a.id[++a.l]); if (a.l+a.d-k>=1) a.d-=k; else a.d=1-a.l; } void Rshift(Node &a,int k){ while (a.l<a.r&&a.r+a.d+k>N) Calc(a.id[--a.r]); if (a.r+a.d+k<=N) a.d+=k; else a.d=N-a.r; } long long GetAns(){ if (Line.l==Line.r && Col.l==Col.r){ long long x=LeftUp+RightUp+RightDown+LeftDown; return C(x); } else if (Line.l==Line.r){ long long x=LeftUp+RightUp,y=LeftDown+RightDown; return C(x)+C(y); } else if (Col.l==Col.r){ long long x=LeftUp+LeftDown,y=RightUp+RightDown; return C(x)+C(y); } else return C(LeftUp)+C(LeftDown)+C(RightUp)+C(RightDown); } int main(){ //freopen("data.in","r",stdin); //freopen("test.out","w",stdout); int T; scanf("%d",&T); while (T--){ scanf("%d%d",&N,&M); Clear(); LeftUp=RightUp=LeftDown=RightDown=0; for (int i=1;i<=N;i++) vis[i]=0; for (int i=1;i<=N;i++){ scanf("%d%d",&a[i],&b[i]); Line.Add(b[i],i);Col.Add(a[i],i); } Calc(Line.id[1]); Calc(Line.id[N]); Calc(Col.id[1]); Calc(Col.id[N]); for (int i=1;i<=M;i++){ char opt[3]; scanf("%s",opt); if (opt[0]=='!') printf("%lld\n",GetAns()); else{ int x; scanf("%d",&x); if (opt[0]=='L') Lshift(Line,x);else if (opt[0]=='R') Rshift(Line,x);else if (opt[0]=='U') Lshift(Col,x);else if (opt[0]=='D') Rshift(Col,x);else printf("%d %d\n",Col.GetPos(a[x]),Line.GetPos(b[x])); } } } return 0; }
L. Connected Subgraphs
FJWC2019子图(
分五类讨论,然后容斥一下,考虑每个三/四元环被多算的次数
虽然,但是,为什么邻接表存图跑的比vector慢,我不行了
#pragma GCC optimize(1) #pragma GCC optimize(2) #pragma GCC optimize(3,"Ofast","inline") #include<bits/stdc++.h> using namespace std; int T; int N,M; vector <int> G[400005]; long long read(){ long long ans=0; char last=' ',ch=getchar();//last????????????????????????????? while(ch<'0' || ch>'9')last=ch,ch=getchar();//???????????????????last?????ch???????? while(ch>='0' && ch<='9')ans=ans*10+ch-'0',ch=getchar();//?????????????????????? if(last=='-')ans=-ans;//??????? return ans; } inline void write(int x){ if (x < 0) x = ~x + 1, putchar('-'); if (x > 9) write(x / 10); putchar(x % 10 + '0'); } const long long fish=1e9+7; int cnt,nex[400005],las[400005],Arrive[400005],d[400005]; void jt(int x,int y){ cnt++; nex[cnt]=las[x]; las[x]=cnt; Arrive[cnt]=y; } bool cmp(int x,int y){ return (d[x]==d[y]?x>y:d[x]>d[y]); } int Pow(int x,int y){ int as=1; while (y){ if (y&1) as=1ll*as*1ll*x%fish; x=1ll*x*1ll*x%fish; y>>=1; } return as; } int inv3=333333336,inv4=250000002,inv2=500000004,inv24=41666667; int u[200005],v[200005]; int vis[200005],R3[200005],cnt4[200005],cnt3,ans4; void Get_3(){ for (int u=1;u<=N;u++){ for (auto v:G[u]) vis[v]++; for (auto v:G[u]) { for (auto w:G[v]){ if (vis[w]){ ++R3[w],++R3[u],++R3[v]; } } } for (auto v:G[u]) vis[v]=0; } for (int i=1;i<=N;i++) (cnt3+=R3[i])%=fish; cnt3=1ll*cnt3*1ll*inv3%fish; return; } void Get_4(){ for (int u=1;u<=N;u++){ for (auto v:G[u]){ if (cmp(u,v)){ for (auto w:G[v]){ if (cmp(u,w)){ cnt4[u]+=vis[w]; cnt4[v]+=vis[w]; cnt4[w]+=vis[w]; vis[w]++; } } } } for (auto v:G[u]){ if (cmp(u,v)){ for (auto w:G[v]){ if (cmp(u,w)){ cnt4[v]+=--vis[w]; } } } } } for (int i=1;i<=N;i++) (ans4+=cnt4[i])%=fish; ans4=1ll*ans4*1ll*inv4%fish; //cout<<ans4<<endl; return; } int Get(){//?????+????? int ans=0; for (int i=1;i<=N;i++){ (ans+=1ll*R3[i]*1ll*(d[i]-2)%fish)%=fish; } return ans; } void init(){ N=read();M=read(); int k; //k=read(); for (int i=1;i<=M;i++){ d[u[i]=read()]++,d[v[i]=read()]++; } for (int i=1;i<=M;i++) if (cmp(u[i],v[i])) G[u[i]].push_back(v[i]); else G[v[i]].push_back(u[i]); Get_3(); for (int i=1;i<=M;i++) if (cmp(u[i],v[i])) G[v[i]].push_back(u[i]); else G[u[i]].push_back(v[i]); Get_4(); long long ans=0; for (int i=1;i<=N;i++){ int x=d[i]; //cout<<d[i]<<endl; ans=(ans+1ll*x*1ll*(x-3)%fish*1ll*(x-1)%fish*1ll*(x-2)%fish*1ll*inv24%fish)%fish; //if(i<=10) cout<<x<<" "<<ans<<endl; }//??? for (int i=1;i<=M;i++){ int x=d[u[i]],y=d[v[i]]; //cout<<d[u[i]]<<" "<<d[v[i]]<<endl; ans=(ans+1ll*(x-2)*1ll*(x-1)%fish*1ll*inv2%fish*(y-1)+1ll*(y-2)*1ll*(y-1)%fish*inv2%fish*(x-1)%fish)%fish; }//??? ans=(ans-3ll*Get()%fish+fish)%fish; for (int u=1;u<=N;u++){ int t=0; for (auto v:G[u]){ (ans+=1ll*t*(d[v]-1)%fish)%=fish; (t+=d[v]-1)%=fish; } }//?? ans=(ans-3ll*cnt3+fish)%fish; ans=(ans-3ll*ans4%fish+fish)%fish; //cout<<ans4<<endl; write(ans); printf("\n"); return; } void Clear(){ cnt=0; for (int i=1;i<=N;i++){ R3[i]=cnt4[i]=vis[i]=d[i]=0; G[i].clear(); } ans4=0;cnt3=0; } int main(){ T=read(); while (T--){ Clear(); init(); } return 0; }
A. Goodbye, Ziyin!
没啥好说的,考虑根据度讨论即可。
D. Period
求所有可能的循环节长度
求完每次询问二分一下即可。
#include<bits/stdc++.h> using namespace std; const int maxn=1000005; int lt,lw,nex[maxn]; char t[maxn],w[maxn]; vector<int> ans; int main(){ scanf("%s",t); lt=strlen(t); for(int i=1;i<lt;i++){ int j=nex[i-1]; while(j>0&&t[i]!=t[j]) j=nex[j-1]; if(t[i]==t[j]) j++; nex[i]=j; } int nw=nex[lt-1]; while (nw){ ans.push_back(lt-nw); nw=nex[nw-1]; } sort(ans.begin(),ans.end()); int T; int y=(lt+1)/2; scanf("%d",&T); while (T--){ int x; scanf("%d",&x); if (x<=y) x=lt-x+1; auto pos=lower_bound(ans.begin(),ans.end(),x)-ans.begin(); if (pos==ans.size()) printf("%d\n",0); else printf("%d\n",ans.size()-pos); } return 0; }
F. Stone
因数,套路的考虑一下
发现取得时候,当我开始取奇数我就不能取偶数,但我取偶数则可以决策是否取奇数
现在考虑一个情况
如果一堆石头全是偶数,且当前只能取奇数了
那么显然,这个人必败(偶数-奇数=奇数,不可能一次取完)
于是就发现,此时先手的操作是把所有的堆都变成偶数
于是它任取一个小于最小奇数的奇数即可
对于开局就全是偶数的情况,这种情况下我们肯定尽量选择偶数
考虑一个偶数=
此时,我们设所有k中最小的
剩下的数有两种可能
,其中k为奇数
,显然是偶数
我们此时扔掉考虑,就是有奇数有偶数的情况了。
于是我们还是第一轮把所有的奇数变成偶数即可。
#include<bits/stdc++.h> using namespace std; int N,mn=1e9+7,cnt=0,mn2=1e9+7,mn1=1e9+7; bool flag; int main(){ scanf("%d",&N); for (int i=1;i<=N;i++){ int x; scanf("%d",&x); if (x&1){ flag=1; if (x<mn) mn=x; } else{ cnt=0; while (x%2==0){ cnt++; x=x/2; } if (cnt<mn2) mn2=cnt,mn1=x; else if (cnt==mn2) mn1=min(mn1,x); } } mn=(mn+1)/2; mn1=(mn1+1)/2; if (flag) cout<<mn; else cout<<mn1; return 0; }
赛后补题:
I. Distance
下文涉及除法不特殊说明均为下取整考虑
那么显然,能走到的话,一定是沿着质因子一直走
也就是其实我们只需要对质因子分开考虑
枚举每个质因子的贡献
其实是**(cnt_i - cnt_j)
其中表示指数为的数字的数量
然后进一步考虑优化
考虑怎么算
容斥一下,是至少个,是至少个p
两个对减一下就行了
对于大于的数字来说,每个数字只会枚举到次和次
也就是
展开一下,即
整除分块一下,发现其实要维护的是素数的前缀和
就相当于min_25筛中,f(p)=p的函数前缀
维护一下即可
对于小于的部分暴力即可
后面可能还有个min_25筛的学习笔记
#include <bits/stdc++.h> using namespace std; long long N; const long long fish=1e9+7; long long mx; const int xx=6e5+10; long long g[xx],sp[xx],h[xx],Prime[xx],w[xx]; int m,id1[xx],id2[xx],Index; bool IsPrime[xx]; void Pre(){ IsPrime[1]=true; for (long long i=2;i<=mx;i++){ if (!IsPrime[i]) Prime[++Index]=i,sp[Index]=(sp[Index-1]+i)%fish; for (int j=1;j<=Index&&1ll*i*Prime[j]<=mx;j++){ IsPrime[1ll*i*Prime[j]]=true; if (i%Prime[j]==0) break; } } } long long Pow(long long x,int y){ long long ans=1; while (y){ if (y&1) ans=1ll*ans*x%fish; x=1ll*x*x%fish; y>>=1; } return ans; } long long GetAns(long long x,int y){ if (x<=1||Prime[y]>x) return 0; int k=(x<=mx)?id1[x]:id2[N/x]; long long ans=(g[k]-h[k]-sp[y-1]+y-1+fish)%fish; if (y==1) ans+=2; for (int i=y;i<=Index&&1ll*Prime[i]*Prime[i]<=x;i++){ long long t1=Prime[i],t2=1ll*Prime[i]*Prime[i]; for (long long e=1;t2<=x;e++,t1=t2,t2*=Prime[i]){ ans=(ans+1ll*(Prime[i]^e)*GetAns(x/t1,i+1)%fish+(Prime[i]^(e+1ll))%fish)%fish; } } return ans; } int Get(long long x){ if (x>mx) return id2[N/x]; else return id1[x]; } int main(){ cin>>N; mx=sqrt(N); Pre(); long long inv=Pow(2,fish-2); for (long long i=1,j;i<=N;i=j+1ll){ j=N/(N/i);w[++m]=N/i; g[m]=1ll*w[m]%fish*1ll*((w[m]+1)%fish)%fish; g[m]=1ll*g[m]*inv%fish; g[m]=(g[m]-1ll+fish)%fish; if (w[m]<=mx) id1[w[m]]=m; else id2[j]=m; } for (int j=1;j<=Index;j++) for (int i=1;i<=m&&1ll*Prime[j]*Prime[j]<=w[i];i++){ long long k=w[i]/Prime[j]; if (k<=mx) k=id1[k];else k=id2[N/k]; (g[i]-=1ll*Prime[j]*(g[k]-sp[j-1])%fish)%=fish; } long long ans=0,Pre=g[Get(mx)]; for (long long i=mx;i;i--){ int k=Get(N/i); ans=(ans+1ll*i*(N-i)%fish*((g[k]-Pre+fish)%fish)%fish)%fish; Pre=g[k]; } for (int i=1;i<=Index;i++){ int res=0; for (long long j=1,a=Prime[i];a<=N;j++,a*=Prime[i]){ for (long long k=0,b=1;k<=j-1;k++,b*=Prime[i]){ ans+=Prime[i]*(j-k)%fish*((N/b-N/b/Prime[i])%fish)%fish*((N/a-N/a/Prime[i])%fish)%fish; } } } cout<<ans*2%fish; return 0; }
E.Chase!
如果当前数值大于换的期望就选择不换
如果不大于就选择换
换的期望可以预处理
具体操作就是每次计算上面的东西,初始的期望值是每个数和别的数各自配对一次。
#include <bits/stdc++.h> using namespace std; long long N,K,T; long long Sum,sum[100005]; int a[100005],b[100005]; double dp[100005]; const double eps=1e-4; int main(){ cin>>N>>K>>T; for (int i=1;i<=N;i++){ cin>>a[i]; Sum+=a[i]; b[i]=a[i]; } sort(a+1,a+N+1); for (int i=N;i>=1;i--){ sum[i]=sum[i+1]+a[i]; } double ress=(double)2*(N-1)*Sum/(double(N*(N-1))); dp[0]=ress; for (int i=1;i<=K;i++){ long long l=1,summ=0,Times=0; for (int r=N;r>=1;r--){ while (a[l]+a[r]<=dp[i-1]) l++; if (l>N) break; if (l<=r){ summ+=sum[l]+(N-l+1)*a[r]-2*a[r]; Times+=N-l; } else{ summ+=sum[l]+(N-l+1)*a[r]; Times+=N-l+1; } } long long res=N*(N-1)-Times; dp[i]=(double)summ/(N*(N-1))+(double)res*dp[i-1]/(double)(N*(N-1)); } printf("%.10lf\n",dp[K]); while (T--){ int x,y,c; cin>>x>>y>>c; if(c==0){ cout<<"accept\n"; continue; } c--; if (b[x]+b[y]>dp[c]) printf("accept\n"); else if (fabs(b[x]+b[y]-dp[c])<=eps) printf("both\n"); else printf("reselect\n"); } return 0; }
The 2021 CCPC Guangzhou Onsite
怎么感觉我打了一场表(
坐大牢
I. Pudding Store
打表找规律
#include <bits/stdc++.h> using namespace std; const long long fish=998244353; long long Pow(long long x,int y){ long long ans=1; while (y){ if (y&1) ans=1ll*ans*1ll*x%fish; x=1ll*x*1ll*x%fish; y>>=1; } return ans; } int main(){ int T; cin>>T; while (T--){ int N; scanf("%d",&N); if (N==1) printf("1\n"); else if (N==2) printf("2\n"); else if (N==3) printf("6\n"); else printf("%lld\n",Pow(2,N-3)*6ll%fish); } return 0; }
H. Three Integers
不妨设
暴力消一消未知数
然后讨论一下,取合适的值就行了。
#include <bits/stdc++.h> using namespace std; int T; const long long mx=1e18; int main(){ cin>>T; while (T--){ long long a,b,c,x,y,z; scanf("%lld%lld%lld",&a,&b,&c); if (a==b&&b==c&&a==0){ printf("YES\n"); printf("1 1 1\n"); continue; } if (a==b&&b==c){ printf("NO\n"); continue; } if (a>c){ long long k1=0,k3=max(0ll,(b-c)/a)+1; long long k2=max((a-b)/(k3*a+c),0ll)+1; x=a,y=k2*k3*a+k2*c+b,z=k3*a+c; if (x<=mx&&y<=mx&&z<=mx) printf("YES\n%lld %lld %lld\n",x,y,z); else printf("NO\n"); continue; }else if (b>a){ long long k1=max((c-a)/b,0ll)+1,k2=0; long long k3=max((b-c)/(k1*b+a),0ll)+1; x=k1*b+a;y=b;z=k3*x+c; if (x<=mx&&y<=mx&&z<=mx) printf("YES\n%lld %lld %lld\n",x,y,z); else printf("NO\n"); continue; }else if (c>b){ long long k3=0,k2=max(0ll,(a-b)/c)+1; long long k1=max((c-a)/(k2*c+b),0ll)+1; z=c,y=k2*c+b,x=k1*k2*c+k1*b+a; if (x<=mx&&y<=mx&&z<=mx) printf("YES\n%lld %lld %lld\n",x,y,z); else printf("NO\n"); continue; } else printf("NO\n"); } return 0; }
F. Cactus
毒瘤。
想了半天仙人掌计数怎么做,然后发现没什么想法。
看了看榜发现过了一车人,遂自闭。
人类智慧跑了前几个,写了个暴力代码一跑发现答案长得很像斐波那契数列
让队友帮忙重新算了一遍前几个,发现不太一样,遂扔进暴力代码
发现跑出来还是一样的?
于是大胆猜测没啥用,直接输出斐波那契数列。
证明好像是用拉格朗日插值法展开,不太懂.jpg
#include <bits/stdc++.h> using namespace std; int N; int f[300005]; const int fish=998244353; int main(){ cin>>N; f[1]=1,f[2]=1,f[3]=2; for (int i=3;i<=N;i++){ f[i]=(f[i-1]+f[i-2])%fish; } cout<<f[N]<<endl; }
The 2021 CCPC Guilin Onsite (Grand Prix of EDG)
感觉还行
A.A Hero Named Magnus
猛犸不上ban
签到,输出即可。记得long long
代码不放了
D. Assumption is All You Need
从前往后做。
贪心的想,对于一个位置来说,它只能和后面比它小的位置换
所以我们希望大的尽可能考前
只有,每次暴力找第一个下一个比当前位置小的数字然后交换,直到
#include <bits/stdc++.h> using namespace std; int N; int a[5005],b[5005]; vector<pair<int,int> >ans; int main(){ int T; cin>>T; while (T--){ ans.clear(); scanf("%d",&N); for (int i=1;i<=N;i++) scanf("%d",&a[i]); for (int i=1;i<=N;i++) scanf("%d",&b[i]); bool flag=false; for (int i=1;i<=N;i++){ if (a[i]==b[i]) continue; if (a[i]<b[i]) {flag=true;break;} while (a[i]>b[i]){ for (int j=i+1;j<=N;j++) if (a[i]>a[j]&&a[j]>=b[i]){ swap(a[i],a[j]); ans.push_back({i,j}); } } } if (flag){ printf("-1\n"); } else{ printf("%d\n",ans.size()); for (auto x:ans) printf("%d %d\n",x.first,x.second); } } return 0; }
G. Occupy the Cities
显然答案具有单调性,考虑二分
然后每个位置能到的最左点和最右点是可以确定的。
但是因为第一次要把变成,所以有一边会少一个
暴力判断即可。
#include <bits/stdc++.h> using namespace std; int N; char c[1000005],c1[1000005]; bool Check(int ls){ for (int i=1;i<=N;i++) c[i]=c1[i]; int lst=0; for (int i=1;i<=N;i++){ if (c[i]=='1'){ if (i-lst-1>ls) return false; if (lst>i){ int pos=i+ls; lst=max(lst,pos); if (pos>=N) return true; } else if (i-lst-1<=ls-1){ int pos=i+ls; lst=max(ls,pos); if (pos>=N) return true; } else{ int pos=i+ls-1; lst=max(lst,pos); if (pos>=N) return true; } lst=max(lst,i); } } if (lst>=N) return true;else return false; } int main(){ int T; cin>>T; while (T--){ scanf("%d",&N); for (int i=1;i<=N;i++) cin>>c1[i]; int l=0,r=N; int ans=0; while (l<=r){ int mid=(l+r)>>1; if (Check(mid)) ans=mid,r=mid-1; else l=mid+1; } cout<<ans<<'\n'; } return 0; }
K. Tax
发现只有
其实题目中最短路这个限制非常强。
考虑先跑一次最短路,然后把最短路相同的点归为一层,每层向下一层连边
对着建出来的新图跑个即可。
为什么这是对的呢?
考虑这个分层图。
每次在图上做一次行走,相当于从一层的任意一个点,走向下一层的任意一个点
也就是说是 表示第层的节点数
显然,这个式子在尽可能相等的时候最大
于是假设个点分了组,就是
算一下这个函数的最大值发现跑得过
#include <bits/stdc++.h> using namespace std; int N,M; int ans[55]; int cnt,nex[10005],Arrive[10005],qz[10005],las[10005],dis[55],cnt1,qz1[10005],w[10005],cntt[10005]; bool viss[55]; bool vis[55]; int nex1[10005],las1[10005],Arrive1[10005]; void jt(int x,int y,int c){ cnt++; nex[cnt]=las[x]; las[x]=cnt; Arrive[cnt]=y; qz[cnt]=c; } void Getdis(){ memset(dis,63,sizeof(dis)); dis[1]=0; queue<int> bfs; memset(vis,0,sizeof(vis)); vis[1]=1; bfs.push(1); while (!bfs.empty()){ int x=bfs.front(); bfs.pop(); vis[x]=false; for (int i=las[x];i;i=nex[i]){ int v=Arrive[i]; if (dis[x]+1<=dis[v]){ dis[v]=dis[x]+1; if (!vis[v]) bfs.push(v); } } } } void jt1(int x,int y,int c){ cnt1++; nex1[cnt1]=las1[x]; las1[x]=cnt1; Arrive1[cnt1]=y; qz1[cnt1]=c; } void dfs(int Now,int nw){ //cout<<Now<<endl; viss[Now]=true; ans[Now]=min(ans[Now],nw); for (int i=las1[Now];i;i=nex1[i]){ int v=Arrive1[i]; if (viss[v]) continue; int col=qz1[i]; cntt[col]++; nw=nw+w[col]*cntt[col]; dfs(v,nw); nw=nw-w[col]*cntt[col]; cntt[col]--; } viss[Now]=false; } int main(){ scanf("%d%d",&N,&M); for (int i=1;i<=M;i++) scanf("%d",&w[i]); for (int i=1;i<=M;i++){ int u,v,c; scanf("%d%d%d",&u,&v,&c); jt(u,v,c); jt(v,u,c); } Getdis(); for (int u=1;u<=N;u++){ for (int i=las[u];i;i=nex[i]){ int v=Arrive[i]; if (dis[u]+1==dis[v]){ jt1(u,v,qz[i]); } } } memset(ans,63,sizeof(ans)); dfs(1,0); for (int i=2;i<=N;i++) cout<<ans[i]<<'\n'; return 0; }
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· DeepSeek 开源周回顾「GitHub 热点速览」
· 物流快递公司核心技术能力-地址解析分单基础技术分享
· .NET 10首个预览版发布:重大改进与新特性概览!
· AI与.NET技术实操系列(二):开始使用ML.NET
· 单线程的Redis速度为什么快?