[NOIP模拟测试38]题解
来自达哥的问候……
A.金
显然本题的考察点在于高精而不是裴蜀定理
根据裴蜀定理易得答案为Yes当且仅当$gcd(n,m)=1$,那么考虑怎么在高精度下判互质。
如果$n,m$都能被2整除,那么显然不互质。
如果其中一个可以而另一个不可以(以n能被2整除为例),$gcd(n,m)$就可以转化为$gcd(\frac{n}{2},m)$
如果两个数都不是2的倍数,根据更相减损术得到$gcd(n,m)=gcd(n,|n-m|)$
重复这个过程即可。因为奇数减奇数一定是偶数,所以第三种操作不会连续进行两次,复杂度是log的。
#include<cstdio> #include<iostream> #include<cstring> using namespace std; typedef long long ll; const int N=505; int T,p[N],q[N]; ll n,m; ll gcd(ll x,ll y) { if(!y)return x; return gcd(y,x%y); } void print(int a[]) { for(int i=a[0];i;i--) cout<<a[i]; cout<<endl; } void ini(int a[]) { char s[N]; scanf("%s",s); a[0]=strlen(s); for(int i=1;i<=a[0];i++) a[i]=s[a[0]-i]-'0'; } ll turnn(int a[]) { ll res=0; for(int i=a[0];i;i--) res=res*10+a[i]; return res; } int cmp(int a[],int b[]) { if(a[0]>b[0])return 1; if(a[0]<b[0])return 0; for(int i=a[0];i;i--) { if(a[i]>b[i])return 1; if(a[i]<b[i])return 0; } return -1; } void jian(int a[],int b[]) { int fl=cmp(a,b); if(fl==-1) { a[0]=0; return ; } if(fl==1) { for(int i=1;i<=a[0];i++) { if(a[i]<b[i]) { a[i+1]--; a[i]+=10; } a[i]-=b[i]; } while(a[0]>0&&a[a[0]]==0)a[0]--; return ; } } bool is2(int a[]) { if(a[1]==2||a[1]==0||a[1]==4||a[1]==6||a[1]==8)return 1; return 0; } bool is3(int a[]) { ll res=0; for(int i=a[0];i;i--) res+=a[i]; if(res%3==0)return 1; return 0; } void divi(int a[],int b) { int x=0; for(int i=a[0];i;i--) { int old=(x*10+a[i])/b; x=(x*10+a[i])%b; a[i]=old;//cout<<a[i]<<endl; } while(a[0]>0&&a[a[0]]==0)a[0]--; return ; } bool leg(int a[],int b[]) { while(a[0]&&b[0]) { //print(a);print(b); //cout<<a[0]<<' '<<b[0]<<endl; if(is2(a)&&is2(b)){return 0;} if(is2(a))divi(a,2); else if(is2(b))divi(b,2); else { if(cmp(a,b))jian(a,b); else jian(b,a); } } if(a[0]==a[1]&&a[0]==1)return 1; if(b[0]==b[1]&&b[0]==1)return 1; return 0; } void qj1() { if(m==1) { puts("Yes");return ; } if(m==0||n==0) { puts("No");return ; } if(gcd(n,m)==1) { puts("Yes"); } else puts("No"); return ; } void work() { ini(p);ini(q); if(p[0]<=18&&q[0]<=18) { n=turnn(p);m=turnn(q); qj1(); return ; } if(p[0]==1&&p[1]==0) { puts("No"); return ; } if(q[0]==1&&q[1]==0) { puts("No"); return ; } if(q[0]==1&&q[1]==1) { puts("Yes"); return ; } if(q[0]==1&&(q[1]==2||q[1]==3)) { m=turnn(q); if(m==2) { if(is2(p))puts("No"); else puts("Yes"); } if(m==3) { if(is3(p))puts("No"); else puts("Yes"); } return ; } if(leg(p,q))puts("Yes"); else puts("No"); return ; } int main() { //while(1)ini(p),divi(p,2),print(p),cout<<p[0]<<endl; scanf("%d",&T); while(T--)work(); return 0; }
B.斯诺
首先考虑序列中只有0和1的情况,这时区间合法当且仅当区间中0的个数等于区间中1的个数。开桶维护,下标为区间中0的个数和1的个数的差值。直接利用前缀和扫一遍即可,复杂度$O(n)$,结合暴力可以得到60分。
#include<cstdio> #include<iostream> #include<cstring> using namespace std; typedef long long ll; const int N=5e6+5; int n,a[N],sum[4][N]; ll ans,cnt,bu1[N],bu2[N]; char s[N]; void qj1() { for(int i=1;i<=n;i++) for(int j=i;j<=n;j++) { int len=j-i+1,ok=1; for(int k=0;k<=2;k++) if((sum[k][j]-sum[k][i-1])*2>len) { ok=0;break; } if(ok)ans++; } cout<<ans<<endl; } int main() { scanf("%d",&n); scanf("%s",s+1); for(int i=1;i<=n;i++) { a[i]=s[i]-'0'; for(int k=0;k<=2;k++) sum[k][i]=sum[k][i-1]+(a[i]==k); if(sum[0][i]==sum[1][i])cnt++; } if(n<=3000) { qj1(); return 0; } for(int i=1;i<=n;i++) { ll m; if(sum[0][i]>=sum[1][i]) { ans+=bu1[sum[0][i]-sum[1][i]]; bu1[sum[0][i]-sum[1][i]]++; if(sum[0][i]==sum[1][i])ans++; } else { ans+=bu2[sum[1][i]-sum[0][i]]; bu2[sum[1][i]-sum[0][i]]++; } } cout<<ans<<endl; return 0; }
那么对于所有情况,显然区间中最多有1个数字数量超过一半,那么合法区间数就是总区间数($\frac{n\times (n+1)}{2}$)减去0超过一半的区间数、1超过一半的区间数和2超过一半的区间数。
对于每种数字维护前缀和,把多一个这种数字看作-1,多一个其它数字看作+1,那么问题转化为求前缀和数组的逆序对个数。可以上树状数组,注意序列要扫到0(因为前缀和的柿子是$sum[r]-sum[l-1]$)。另外,为了防止下标负数要集体加上一个值。复杂度$O(n\ log\ n)$,可以得到90分。
#include<cstdio> #include<iostream> #include<cstring> using namespace std; typedef long long ll; const int N=5e6+5; int n,a[N],sum[3][N],c[3][N],minn[3]={0x3f3f3f3f,0x3f3f3f3f,0x3f3f3f3f}; ll ans; char s[N]; int lb(int x){return x&-x;} void add(int k,int x,int val) { for( ;x<=n;x+=lb(x)) c[k][x]+=val; } ll query(int k,int x) { ll res=0; for( ;x;x-=lb(x)) res+=c[k][x]; return res; } void show(int k) { puts(" "); for(int i=1;i<=n;i++) cout<<sum[k][i]<<' '; puts(" "); } int main() { scanf("%d",&n); scanf("%s",s+1); for(int i=1;i<=n;i++) { a[i]=s[i]-'0'; for(int k=0;k<=2;k++) sum[k][i]=sum[k][i-1]+(a[i]==k?-1:1),minn[k]=min(minn[k],sum[k][i]); } for(int k=0;k<=2;k++) { for(int i=0;i<=n;i++) sum[k][i]+=minn[k]<0?(-minn[k]+1):1; for(int i=n;i>=0;i--) ans+=query(k,sum[k][i]-1),add(k,sum[k][i],1); //cout<<ans<<endl; //show(k); } cout<<1LL*n*(n+1)/2-ans<<endl; return 0; }
注意到这个前缀和数组有特殊性:相邻两项的差值最大为1。因此,相邻两项对答案的贡献也最多相差一种值的个数,开桶维护即可。复杂度$O(n)$。
#include<cstdio> #include<iostream> #include<cstring> using namespace std; typedef long long ll; const int N=5e6+5; int n,a[N],sum[3][N],bu[3][N],minn[3]={0x3f3f3f3f,0x3f3f3f3f,0x3f3f3f3f}; ll ans; char s[N]; int main() { scanf("%d",&n); scanf("%s",s+1); for(int i=1;i<=n;i++) { a[i]=s[i]-'0'; for(int k=0;k<=2;k++) sum[k][i]=sum[k][i-1]+(a[i]==k?-1:1),minn[k]=min(minn[k],sum[k][i]); } for(int k=0;k<=2;k++) { for(int i=0;i<=n;i++) sum[k][i]+=minn[k]<0?(-minn[k]+1):1; ll last=0; for(int i=n;i>=0;i--) { if(i!=n) { if(sum[k][i]==sum[k][i+1]+1)ans+=last+bu[k][sum[k][i+1]],last+=bu[k][sum[k][i+1]]; else if(last&&sum[k][i]==sum[k][i+1]-1)ans+=last-bu[k][sum[k][i]],last-=bu[k][sum[k][i]]; else ans+=last; } bu[k][sum[k][i]]++; } } cout<<1LL*n*(n+1)/2-ans<<endl; return 0; }
C.赤
竟然是wqs二分套wqs二分,$n^3$硬生生砍成$n\ log^2 n$,太残暴了QAQ。
首先暴力dp应该很好想,$dp[i][j][k]$表示遇到了i只猫,丢了j包干脆面和k包豆干。直接三层循环转移即可,边界稍微修剪一下可以得到50分。(然而考场上写着写着莫名其妙丢了一个方程)
#include<cstdio> #include<iostream> #include<cstring> using namespace std; int n,a,b; double dp[3][2005][2005],p[100005],q[100005]; void work() { memset(dp,0,sizeof(dp)); for(int i=1;i<=n;i++) scanf("%lf",&p[i]); for(int i=1;i<=n;i++) scanf("%lf",&q[i]); double ans=0; //dp[1][1][0]=p[1];dp[1][0][1]=q[1];dp[1][1][1]=p[1]+q[1]-p[1]*q[1]; int now=0,pre=1; for(int i=1;i<=n;i++) { now^=1,pre^=1; for(int j=0;j<=min(i,a);j++) for(int k=0;k<=min(i,b);k++) dp[now][j][k]=0; for(int j=0;j<=min(i,a);j++) { for(int k=0;k<=min(i,b);k++) { dp[now][j][k]=max(dp[pre][j][k],dp[now][j][k]);//就是它! if(j>0)dp[now][j][k]=max(dp[pre][j-1][k]+p[i],dp[now][j][k]); if(k>0)dp[now][j][k]=max(dp[pre][j][k-1]+q[i],dp[now][j][k]); if(j>0&&k>0)dp[now][j][k]=max(dp[pre][j-1][k-1]+p[i]+q[i]-p[i]*q[i],dp[now][j][k]); //cout<<i<<' '<<j<<' '<<k<<' '<<dp[now][j][k]<<endl; } } } for(int i=0;i<=min(n,a);i++) for(int j=0;j<=min(n,b);j++) ans=max(ans,dp[now][i][j]); printf("%.3lf\n",ans); } int main() { while(scanf("%d%d%d",&n,&a,&b)!=EOF) work(); return 0; }
观察这个转移发现是很套路的wqs?不过辣鸡博主根本没写过wqs当然看不出来了。
具体分析就直接放达哥官方题解好了(逃
我们首先不考虑 b 的限制,假设豆干可以任意使用,定义 $f[i][j]$表示前 i 只猫使用 j 包干脆面
和若干包豆干得到的最大收益。直接这么 DP 会 n 只猫都用豆干,可能会超出 b 的限制。
为了减少豆干的使用,我们可以假定使用一个豆干需要额外付出 cost 的代价(也就是在转移
的时候如果一只猫用了豆干,它对期望值的贡献要减去 cost)。在 DP 的时候,求解出 $f[i][j]$的最大值,并记录最优方案中豆干使用的数目 x,那么此时真实的期望是 $f[i][j]$的最优值加上
$x*cost$,这个结果必然也是用了 x 个豆干时能够得到的最优结果。
但是,如果我们随便假定一个 cost 去跑 DP,得到的方案并不一定把所有豆干都用完,我们需
要一个能把所有豆干都用完的 cost 的值。
显然豆干使用量随着 cost 的变化是单调变化的,我们可以二分 cost 的数值.
这样dp数组就减少了一维,反映在时间复杂度上就是少了个n多了个log。同样地,我们还可以再砍掉一维,然后二分套二分解决。复杂度$O(n\ log^2 \ n)$。
#define hzoj #include<cstdio> #include<iostream> #include<cstring> using namespace std; const double eps=1e-9; int n,a,b,na[100005],nb[100005]; double dp[100005],p[100005],q[100005]; int check(int op,double c1,double c2) { for(int i=1;i<=n;i++) { dp[i]=dp[i-1]; na[i]=na[i-1];nb[i]=nb[i-1]; if(dp[i-1]+p[i]-c1>dp[i])dp[i]=dp[i-1]+p[i]-c1,na[i]=na[i-1]+1,nb[i]=nb[i-1]; if(dp[i-1]+q[i]-c2>dp[i])dp[i]=dp[i-1]+q[i]-c2,nb[i]=nb[i-1]+1,na[i]=na[i-1]; if(dp[i-1]+p[i]+q[i]-p[i]*q[i]-c1-c2>dp[i])dp[i]=dp[i-1]+p[i]+q[i]-p[i]*q[i]-c1-c2,na[i]=na[i-1]+1,nb[i]=nb[i-1]+1; } if(op==1)return nb[n]; else return na[n]; } void work() { #ifdef luogu scanf("%d%d%d",&n,&a,&b); #endif memset(dp,0,sizeof(dp)); for(int i=1;i<=n;i++) scanf("%lf",&p[i]); for(int i=1;i<=n;i++) scanf("%lf",&q[i]); double l1=0.0,r1=1.0,l2,r2,mid1,mid2; while(r1-l1>eps) { mid1=(l1+r1)/2; l2=0.0,r2=1.0; while(r2-l2>eps) { mid2=(l2+r2)/2; if(check(1,mid1,mid2)>b)l2=mid2; else r2=mid2; } if(check(2,mid1,r2)>a)l1=mid1; else r1=mid1; } //check(0,r1,r2); printf("%.3lf\n",dp[n]+1.0*b*r2+1.0*a*r1); } int main() { #ifdef hzoj while(scanf("%d%d%d",&n,&a,&b)!=EOF) #endif work(); return 0; }