一.闲话
本来打的挺开心的,A题速度挺快的。然后。。。G题被疯狂卡精度。。。(绝望),罚时一大堆qwq。我好菜啊qwq
由于打比赛的时候,打的比较快,很多代码都比较粗糙,还请见谅~
二.题解
A.AOE还是单体?
明显是个单峰函数求极值问题(应该可以直接推式子,但是懒)~
我们三分群体攻击次数,然后计算求极值即可~
代码:
#include<bits/stdc++.h> using namespace std; #define int long long const int N=2e5+1; int a[N],n,T; inline int calc(int x){ int res=0; for(int i=1;i<=n;++i){ res+=max(0LL,a[i]-x); } return res+x*T; } signed main(){ scanf("%lld%lld",&n,&T); for(int i=1;i<=n;++i){ scanf("%lld",&a[i]); } int l=0,r=1e9,res=0; while(l<=r){ int lc=l+(r-l)/3,rc=r-(r-l)/3; int L=calc(lc),R=calc(rc); if(L<=R){ r=rc-1;res=L; }else{ l=lc+1;res=R; } } printf("%lld",res); return 0; }
B.k-size字符串
这题一开始式子推错了
首先,我们来考虑下最终字符串的大概形态:
1.abab...(其中a,b由若干个(>=1)'A''B'组成)
2.baba...
那么, 我们就把这两种情况的方案数分别计算出来
我们为了保证合法,先给每个位置的a放一个'A',每个位置的b放一个'B'
然后,我们再把剩下的'A'和'B'放入任意一个a,b里面即可
统计方案:
假设我们现在还剩x个'A',有y个a可以选择,那么方案数为:('B'直接类比即可)
因为每个'A'是等价的,所以,我们这里用隔板法来计算
我们在x个'A'中插入y+1个隔板(假装开头和末尾有一个),相邻两个隔板之间即为一个区间,第i个区间的'A'的个数就是我们放进第i个a的'A'的个数。由于可以放0个进去,所以区间长度可以为0。
那么,方案就是,我们把所有隔板放入后(不考虑开头和末尾),共x+y-1个空,每个空填'A'或者隔板,那么,方案数即为C(x+y-1,y-1)
把两个情况的答案统计一遍,再求和即可(注意判断是否有足够的'A'/'B')
代码:
#include<bits/stdc++.h> using namespace std; const int mod=1e9+7; const int N=1e5+1; int s[N]; inline int ksm(int x,int y){ int ans=1; while(y){ if(y&1){ ans=(1LL*ans*x)%mod; } x=(1LL*x*x)%mod; y>>=1; } return ans; } inline int C(int y,int x){ int res=1; for(int i=y+1;i<=x;++i){ res=(1LL*res*i)%mod; } for(int i=(x-y);i;--i){ res=(1LL*res*s[i])%mod; } return res; } int main(){ int n,m,k; scanf("%d%d%d",&n,&m,&k); s[0]=s[1]=1; for(int i=2;i<=max(n,m);++i){ s[i]=ksm(i,mod-2); } //选k个然后依次插入即可 int ans=0; //先a后b int mid1=(k+1)/2,mid2=k/2; if(n>=mid1&&m>=mid2){ int A=n-mid1,B=m-mid2,res=1; res=(1LL*res*C(mid1-1,n-1))%mod; res=(1LL*res*C(mid2-1,m-1))%mod; ans=(ans+res)%mod; } if(n>=mid2&&m>=mid1){ int A=n-mid2,B=m-mid1,res=1; res=(1LL*res*C(mid1-1,m-1))%mod; res=(1LL*res*C(mid2-1,n-1))%mod; ans=(ans+res)%mod; } printf("%d",ans); return 0; }
C.白魔法师
明显的一道树形dp问题。
我们设dp[i][0],dp[i][1],dp[i][2]
分别表示以i为根的子树中,没有修改/i修改/子树中有一个修改
那么,我们有转移方程:
前两个很好递推,第三个怎么求?我们注意到,因为1和2一共最多取一个(因为最多修改1次),所以,我们先算出所有儿子中j=0的和,再从j=1/2减去j=0的差值中取最大加上去就好了
最后的答案就是
代码:
#include<bits/stdc++.h> using namespace std; const int N=1e5+1; struct node{ int v,nex; }t[N<<1]; int len,las[N]; bool a[N]; int dp[N][3]; inline void add(int u,int v){ t[++len]=(node){v,las[u]},las[u]=len; } inline void dfs(int now,int fa){ dp[now][0]=dp[now][2]=a[now];dp[now][1]=1; int maxe=0; for(int i=las[now];i;i=t[i].nex){ int v=t[i].v; if(v!=fa){ dfs(v,now); if(a[now]){ dp[now][0]+=dp[v][0];dp[now][2]+=dp[v][0]; maxe=max(maxe,max(dp[v][1],dp[v][2])-dp[v][0]); } dp[now][1]+=dp[v][0]; } } dp[now][2]+=maxe; } int main(){ int n; scanf("%d",&n); string x; cin>>x; for(int i=1;i<=n;++i){ a[i]=(x[i-1]=='W'); } for(int i=1;i<n;++i){ int u,v; scanf("%d%d",&u,&v); add(u,v),add(v,u); } dfs(1,1); int ans=0; for(int i=1;i<=n;++i){ ans=max(ans,max(dp[i][0],dp[i][1])); ans=max(ans,dp[i][2]); } printf("%d",ans); return 0; }
D.抽卡
简单概率。
因为正着算很麻烦,所以我们直接算反面,即抽不到想要的卡的概率。然后用1-抽不到的概率即为抽得到的概率了。
代码:
#include<bits/stdc++.h> using namespace std; const int N=1e5+1,mod=1e9+7; inline int ksm(int x,int y){ int ans=1; while(y){ if(y&1){ ans=(1LL*ans*x)%mod; } x=(1LL*x*x)%mod; y>>=1; } return ans; } int a[N],b[N]; int main(){ int n; scanf("%d",&n); for(int i=1;i<=n;++i){ scanf("%d",&a[i]); } int ans=1; for(int i=1;i<=n;++i){ scanf("%d",&b[i]); b[i]=a[i]-b[i]; ans=(1LL*ans*b[i])%mod; ans=(1LL*ans*ksm(a[i],mod-2))%mod; } ans=1-ans; ans=(ans%mod+mod)%mod; printf("%d",ans); return 0; }
E.点击消除
估计正解应该是删去字符串中的所有回文串,不过太懒了,就直接模拟了一遍。。。
代码:
#include<bits/stdc++.h> using namespace std; int flag[300001]; int main(){ string x; cin>>x; int tim=0; while(true){ ++tim;int len=x.size();bool f=0; for(int i=1;i<len;++i){ if(x[i]==x[i-1]){ flag[i]=flag[i-1]=tim;f=1;++i; } } if(!f){ break; } string y=""; for(int i=0;i<len;++i){ if(flag[i]!=tim){ y+=x[i]; } } x=y; } if(x.size()==0){ puts("0"); return 0; } cout<<x; return 0; }
update:补充一个正解,类似括号匹配问题,把合法的匹配删掉即可~
update2:应大佬要求,来补充下。qwq
我们注意到,我们删除的串一定是这样的:
ABC....TT....CBA【间隔若干个字符】UVW...ZZ...WVU【间隔若干个字符】...
(每个大写字母代表一个字符,不同大写字母代表的字符可能一样,大写字母的数量也可能不一样,只是为了方便说明qwq)
也即是说,我们一定是删除若干个回文串(长度为偶数),那么如果我们把这些字母换成我们常见的括号会怎么样呢?
([{...}])
我们会发现,我们每次删除的,其实就是一个合法的括号的匹配
那么,我们直接类似括号匹配问题,开一个栈,每成功匹配了,就把匹配成功的删去,那么栈里面剩下的就是我们删除之后的字符串辣!
代码:
#include<bits/stdc++.h> using namespace std; char sta[300001];int top; int main(){ string x; cin>>x; int len=x.size(); for(int i=0;i<len;++i){ if(top&&x[i]==sta[top]){ --top; continue; } sta[++top]=x[i]; } if(!top){ puts("0"); return 0; } for(int i=1;i<=top;++i){ cout<<sta[i]; } return 0; }
F.疯狂的自我检索者
最小情况就是剩余m个人都只给1分,最大情况就是剩余m个人都给5分。
分别计算即可
代码:
#include<bits/stdc++.h> using namespace std; int main(){ int n,m; scanf("%d%d",&n,&m); int sum=0; for(int i=1;i<=n-m;++i){ int x; scanf("%d",&x); sum+=x; } double ming=sum+m,maxe=sum+5*m; ming/=double(n),maxe/=double(n); printf("%.5lf %.5lf",ming,maxe); return 0; }
G.解方程
这题精度把我整惨了qwq(不知道各位大佬怎么弄得(感觉是我人丑所以代码丑))
观察后不难发现,该式是明显的一个递增函数,而且x趋近0的时候,值趋近0,c>=1,所以必有解,直接二分就好了。
然而,二分会挂(应该是我自己的锅qwq)
所以,我们考虑玄学的做法:
我们把整数部分和小数部分分开二分做,这样,就不怕精度了qwq(别跟我说long double,我long double也出锅了qwq,气死)
代码:
#include<bits/stdc++.h> using namespace std; const double eps=1e-10; int a,b,c; inline double power(double x,int y){ double res=1; while(y--){ res*=x; } return res; } int main(){ scanf("%d%d%d",&a,&b,&c); int L=0,R=1e9,po=0; while(L<=R){ int mid=(L+R)>>1; double res=power(mid,a)+b*log(mid)-c; if(res<=0){ L=mid+1; }else{ R=mid-1; } } po=min(L,R); double l=eps,r=1; while(r-l>=eps){ double mid=(l+r)/2.0; double res=power(mid+po,a)+b*log(mid+po)-c; if(res>=0){ r=mid; }else{ l=mid; } } printf("%.14lf",l+po); return 0; }
H.神奇的字母(二)
直接读入,然后统计个数即可
代码:
#include<bits/stdc++.h> using namespace std; int tim[26]; int main(){ string x; while(cin>>x){ int len=x.size(); for(int i=0;i<len;++i){ ++tim[x[i]-'a']; } } int maxe=0,id=0; for(int i=0;i<26;++i){ if(tim[i]>maxe){ maxe=tim[i];id=i; } } cout<<char(id+'a'); return 0; }
I.十字爆破
一个很简单的统计问题
我们设l[i]表示第i列的所有数的和,h[i]表示第i行所有数的和,a[i]表示第i个数(编号后)的值,那么第i行和第j列的所有数的和即为
维护好数组后,按公式输出即可
代码:
#include<bits/stdc++.h> using namespace std; const int N=1e6+1; #define int long long int l[N],h[N],a[N]; signed main(){ int n,m; scanf("%lld%lld",&n,&m); for(int i=1;i<=n;++i){ for(int j=1;j<=m;++j){ int x; scanf("%lld",&x);h[i]+=x,l[j]+=x; a[(i-1)*m+j]=x; } } for(int i=1;i<=n;++i){ for(int j=1;j<=m;++j){ printf("%lld ",h[i]+l[j]-a[(i-1)*m+j]); } puts(""); } return 0; }
J.异或和之和
还是上套路:按位统计
假设有k个二进制第i位为1的数,那么第i位的贡献为:
直接每位都统计下贡献,再加起来即可
代码:
#include<bits/stdc++.h> using namespace std; const int mod=1e9+7; int tot[60]; inline long long C(int x,int y){ if(x<y){ return 0; } y=x-y; long long res=1; for(int i=y+1;i<=x;++i){ res=(1LL*res*i); } for(int i=2;i<=x-y;++i){ res/=i; } return res%mod; } int main(){ int n; scanf("%d",&n); for(int i=1;i<=n;++i){ long long x; cin>>x; for(int j=0;j<=59;++j){ tot[j]+=(x&1); x>>=1; } } long long ans=0,now=1; for(int i=0;i<=59;++i){ //1个1 or 3个1 long long res=(1LL*C(tot[i],1)*C(n-tot[i],2))%mod+C(tot[i],3); res%=mod; ans+=(1LL*res*now)%mod; ans%=mod; now=(2LL*now)%mod; } printf("%d",ans); return 0; }