题目总链接:https://codeforces.com/contest/1096
A. Find Divisible
题意:
给出l,r,在[l,r]里面找两个数x,y,使得y%x==0,保证有解。
题解:
直接输出l,2*l就好啦,但我还是写了个循环...
代码如下:
#includeusing namespace std; typedef long long ll; const int N = 10; int T; ll l,r; int main(){ cin>>T; while(T--){ scanf("%I64d%I64d",&l,&r); for(ll i=l;i<=r;i++){ if(i*2<=r){ printf("%I64d %I64d\n",i,2*i); break ; } } } return 0; }
B. Substring Removal
题意:
给出一个字符串,现在要你删掉一个子串,使得剩下的串有不多于一个的字符。
题解:
从两端找连续的相同字符串并且统计个数,然后算算就可以了。
注意一下所有字符串都相等的情况。
还有一种情况就是两段连续的字符串字符都相等,那么这时就可以删掉中间的,这个个数也比较好统计。
具体见代码(注意中间计算过程不要爆int):
#includeusing namespace std; typedef long long ll; const int N = 2e5+5,MOD = 998244353; int n; char s[N]; int main(){ scanf("%d",&n); scanf("%s",s); char fir = s[0]; int i; ll cnt1=1,cnt2=1; for(i=1;i ){ if(s[i]==fir) cnt1++; else break ; } char last = s[n-1]; for(i=n-2;i>=0;i--){ if(s[i]==last) cnt2++; else break ; } ll ans = (cnt1+cnt2+1)%MOD; if(last==fir){ ans=(ans+cnt1*cnt2)%MOD; } cout< endl; return 0; }
C. Polygon for the Angle
题意:
给出一个角度(0<=angle<180),求出最小的正多边形,满足其中存在一个内接三角形的内角与给出的角度相等。
题解:
我的做法比较暴力了,先说说我的吧:
n边形的内角和为(n-2)*180,然后可以知道其每个内角为(n-2)*180/n,利用外接圆+一些圆的性质(圆心角等于二倍圆周角)可以知道一个正n边形的内接三角形的最小角为180/n。
之后只要判断angle%(180/n)是否为0就是了。注意最小角可以为小数,所以我直接是暴力循环...
正解是这样的,首先推出最小角为180/n,然后如果满足angle%(180/n)==0的话,则有n*angle=t*180。
由于180和angle为已知量,我们就可以求出g=gcd(angle,180),然后等式则有n*angle/g = t*180/g。
由这可以知道n=x*180/g , t=y*angle/g,我们知道,这里的t满足t*180/n=angle,当t=n-2时,这时就是正多边形的内角,所以有限制条件:1<=t<=n-2。
我们取x=y=1,这里的n就是答案了(贪心),但是可能会存在这种情况:t=n-1,这时不符合限制条件的,这里我们只要取x=2就行了。
可以证明,当x>=2时,t<=n-2恒成立。
给出我的暴力代码....
#includeusing namespace std; typedef long long ll; int T; int ang; int main(){ cin>>T; while(T--){ scanf("%d",&ang); double ans ; int flag; for(int i=3;i<=360;i++){ ans = (double)180/i; flag=0; if(180%i!=0) for(int j=1;;j++){ if(j*ans>ang){ flag=1; break ; }else if((double)j*ans==(double)ang){ cout<endl; flag=2; break ; } } if(flag==1) continue ; else if(flag==2) break ; if(ang%(int)ans==0&&ans*(i-2)>=ang){ cout<endl; break ; } } } return 0; }
D. Easy Problem
题意:
给出一个字符串,现在要求删去一些字符,使得字符串中不含"hard"子序列,删去一个字符有相应代价,问最小代价。
题解:
如果不存在"hard"子序列的话,我们只需要将其中一个字符删完就行。
我们考虑用动态规划来做,dp(i,j)表示长度为i的字符串中,将第j个删完的最小代价。因为对于每一个字符都有删与不删两种选择。
那么转移方程就为:if(s[j]=='r')(举例)dp(i,j)=min(dp(i,j-1),dp(i-1,j)+a[j]) ; else dp(i,j)=dp(i-1,j).
意思第一个方程的意思即为要么将"r"字符删完,要么将之前的"h"或者"a"的字符删完,求两者的最小代价。
最后求min(dp(n,0),dp(n,1),dp(n,2),dp(n,3))即可。
代码如下:
#includeusing namespace std; typedef long long ll; const int N = 1e5+5; ll dp[N][5]; //dp(i,j):前i个位置,将第j个删完的最小代价。 char s[N]; ll a[N]; int n; int main(){ scanf("%d",&n); scanf("%s",s+1); for(int i=1;i<=n;i++) scanf("%d",&a[i]); for(int i=1;i<=n;i++){ dp[i][0]=dp[i-1][0]+(s[i]=='h')*a[i]; dp[i][1]=min(dp[i-1][0],dp[i-1][1]+(s[i]=='a')*a[i]); dp[i][2]=min(dp[i-1][1],dp[i-1][2]+(s[i]=='r')*a[i]); dp[i][3]=min(dp[i-1][2],dp[i-1][3]+(s[i]=='d')*a[i]); } cout< 3],dp[n][2],dp[n][1],dp[n][0]}); return 0; }
E. The Top Scorer
题意:
给出p,s,r,p即人头数,s即每个人得分的总和,r即主人公的分数下限。
现在每个人都忘了自己的分数是多少,只有主人公知道自己的分数下限,问最后主人公获胜的概率是多少。
ps:当有多个人分数相同时,获胜概率为其除以相应人数。
题解:
这题全场A的人数最少,但也的确有点难度。
通过每个人去划分s,可以联想到“插板法”的计算方法。
我们可以看看一个类似的问题:x1+x2+x3+x4+x5=20,0<=xi<=5,问解的情况有多少种。
这实际上是有上界的问题,如果存在的话应该用容斥原理去解。
我们也可以将这个题限定一个上界,设g(s,p,m):p个人,分数和为s,每个人分数不超过m的情况数,这就类似于刚才说的问题。
主要问题是g的计算,我们首先算出总情况:C(s-1+p,p-1)。在这些情况中会有些不符合要求的情况,那么我们减去至少有一个人分数大于m的情况:C(p,1)*C(s-1+p-1*(m+1),p-1)。
这里会多减去一些情况(可以将每个人看做一个独立的集合),然后再加回来....
然后观察一下数据范围,发现可以枚举,那么我们就枚举主人公的得分,再枚举有多少个人分数与之相同,最后计算一下就OK。
本体细节有点多,写的时候注意下,提示:组合。
代码如下:
#includeusing namespace std; typedef long long ll; const int N = 5005,MOD = 998244353; ll p,s,r; ll fac[N+N]; ll qp(ll a,ll b){ ll ans = 1; while(b){ if(b&1) ans=ans*a%MOD; a=a*a%MOD; b>>=1; } return ans ; } ll inv(ll x){ return qp(x,MOD-2); } ll C(ll n,ll m){ if(n==m) return 1; if(m>n) return 0; return fac[n]*inv(fac[m]*fac[n-m]%MOD)%MOD; } ll g(ll S,ll P,ll m){ ll ans=0,tmp; if(!S && !P) return 1; //!! for(int i=0;i<=P;i++){ tmp = C(P,i)*C(S+P-1-i*(m+1),P-1)%MOD; if(i&1) ans-=tmp; else ans+=tmp; ans%=MOD; if(ans<0) ans+=MOD; } return ans ; } int main(){ cin>>p>>s>>r; fac[0]=1; for(int i=1;i<=1e4;i++) fac[i]=fac[i-1]*i%MOD; ll w=0; for(int t=r;t<=s;t++){ for(int q=1;q<=p;q++){ if(s-q*t<0) continue ; w=(w+(C(p-1,q-1)*g(s-q*t,p-q,t-1)%MOD)*inv(q)%MOD)%MOD; } } cout< 1-r+p,p-1))%MOD; return 0; }
F. Inversion Expectation
题意:
给你一个1-n的排列,但现在缺失了一些数字,现在要求逆序对个数的期望值。
题解:
注意这里是期望,期望是线性的,可以分开来算。
这个题主要考虑四种情况:(假设-1的个数为cnt)
1.未知与未知。这种情况的期望值为(cnt-1)*cnt/4,因为对于一个排列,都会存在另一个与之相反的排列,他们的总逆序对个数为(cnt-1)*cnt/2。我们就可以看作每个排列的逆序对个数为(cnt-1)*cnt/4。
2.左边未知与右边已知;
3.左边已知与右边未知。
上面两种情况思考的方式都是一样的,拿第二种情况来说吧,我们只需要统计未出现的数字中有多少个比当前这个已知数大(假设为num),以及当前已知数左边有多少个位置为-1(假设为len)。
那么对于每个位置,产生一个逆序对的概率为num/cnt,其期望也为num/cnt,那么对于当前这个已知数,它对答案的贡献为len*num/cnt。
另外一种情况同样的求法。
4.已知与已知。这时的期望值就是逆序对的个数,用树状数组或者归并排序求一下就好了。
代码如下:
#includeusing namespace std; typedef long long ll; const int N = 2e5+5,MOD = 998244353; int n; int a[N],c[N],large[N],vis[N]; int lowbit(int x){ return x&(-x); } ll qp(ll A,ll B){ ll ans=1; while(B){ if(B&1) ans=ans*A%MOD; A=A*A%MOD; B>>=1; } return ans ; } ll inv(ll x){ return qp(x,MOD-2); } void update(int p,int x){ for(;p<=n;p+=lowbit(p)) c[p]+=x; } ll getsum(int p){ ll cnt = 0; for(;p>0;p-=lowbit(p)) cnt+=c[p]; return cnt ; } int main(){ scanf("%d",&n); ll cnt = 0,ans =0,num; for(int i=1;i<=n;i++){ scanf("%d",&a[i]); if(a[i]==-1) cnt++; else vis[a[i]]=1; } int cur = 0; for(int i=n;i>=1;i--){ large[i]=cur; cur+=!vis[i]; } //Case1: ans=(ans+(cnt-1)*cnt%MOD*inv(4)%MOD)%MOD; //Case2: num = 0; for(int i=1;i<=n;i++){ if(a[i]==-1) num++; else{ ll len = large[a[i]]; ans=(ans+num*len%MOD*inv(cnt)%MOD)%MOD; } } //Case3: num = 0; for(int i=n;i>=1;i--){ if(a[i]==-1) num++; else{ ll len = cnt-large[a[i]]; ans=(ans+num*len%MOD*inv(cnt)%MOD)%MOD; } } //Case4: for(int i=n;i>=1;i--){ if(a[i]==-1) continue ; ans=(ans+getsum(a[i]))%MOD; update(a[i],1); } cout<<ans; return 0; }
最近事情好多啊~马上又要期末考了,也要稍微准备下了,再不复习就滑铁卢了啊啊啊。