2018.8.21提高AB组模拟考试
为什么没有8.20的考试?因为博主太蒟蒻不会NTT或是FFT。
T1 题意简述:jzoj5838
解题思路:求最大子矩阵。
每一列的元素求前缀和后求最大子段和即可。
#include<iostream> #include<cstdio> #include<cstring> #include<cstdlib> #include<cmath> #include<algorithm> #define ll long long using namespace std; ll n,m,ans,a[201][201],sum[201][201],dp[201],mns[201]; ll sol() { ll tmp=0; memset(dp,0,sizeof(dp)); for(ll i=1;i<=m;i++) dp[i]=max(dp[i-1]+mns[i],mns[i]),tmp=max(tmp,dp[i]); return tmp; } int main() { scanf("%lld%lld",&n,&m); for(ll i=1;i<=n;i++) for(ll j=1;j<=m;j++) scanf("%lld",&a[i][j]); for(ll i=1;i<=n;i++) for(ll j=1;j<=m;j++) sum[i][j]=sum[i-1][j]+a[i][j]; for(ll i=1;i<=n;i++) for(ll j=i;j<=n;j++) { for(ll k=1;k<=m;k++) mns[k]=sum[j][k]-sum[i-1][k]; ans=max(ans,sol()); } printf("%lld\n",ans); return 0; }
T2 题意简述:jzoj4737
解题思路:二分答案。
横纵坐标分开计算。输入时记录下物体输入顺序。
把物体按坐标排序,然后按照i*a[i]-sum[i-1]统计距离。
发现这样是O(nlog^2(n)),没法过全部测试点。
出题人提供了一个特鬼畜的方法:
在二分答案前先把物体按坐标排序,记录下坐标排名。
二分答案时按照排名直接O(n)排序。此法叫做小学生排序(真的有这个排序)。
咳咳咳...做的时候才发现出题人的方法好像会T...吸氧才能过...
GZZ大佬提供了一个4个树状数组的方法。由于博主太懒所以没改。
#pragma GCC optimize(3) #include<iostream> #include<cstdio> #include<cstring> #include<cstdlib> #include<cmath> #include<algorithm> #define ll long long using namespace std; ll n,m,ans,xar[600001],yar[600001],xno[600001],yno[600001],hlp[600001]; bool cmpx(ll x,ll y) { return xar[x]<xar[y]; } bool cmpy(ll x,ll y) { return yar[x]<yar[y]; } ll chk(ll x) { ll tmp=0,cnt=0; memset(hlp,0,sizeof(hlp)); for(ll i=1;i<=x;i++) tmp+=(x-1)*(xar[i]+yar[i]); for(ll i=1;i<=x;i++) hlp[xno[i]]=i; for(ll i=1;i<=n;i++) if(hlp[i]) cnt++,tmp-=2*xar[hlp[i]]*(x-cnt); cnt=0,memset(hlp,0,sizeof(hlp)); for(ll i=1;i<=x;i++) hlp[yno[i]]=i; for(ll i=1;i<=n;i++) if(hlp[i]) cnt++,tmp-=2*yar[hlp[i]]*(x-cnt); return (tmp>m); } int main() { scanf("%lld%lld",&n,&m); for(ll i=1;i<=n;i++) { scanf("%lld%lld",&xar[i],&yar[i]); xno[i]=i,yno[i]=i; } sort(xno+1,xno+1+n,cmpx); sort(yno+1,yno+1+n,cmpy); for(ll i=1;i<=n;i++) hlp[xno[i]]=i; memcpy(xno,hlp,sizeof(hlp)); for(ll i=1;i<=n;i++) hlp[yno[i]]=i; memcpy(yno,hlp,sizeof(hlp)); ll l=1,r=n; while(l<=r) { ll mid=(l+r)>>1; if(chk(mid)) ans=mid,r=mid-1; else l=mid+1; } if(l>n) printf("-1\n"); else printf("%lld\n",ans); return 0; }
T3 题意简述:jzoj4738
解题思路:dp。
设dp[i]表示枚举到第i个人的总方案数,sum[i]表示前i个人中0的个数-1的个数。
转移方程为dp[i]+=dp[j] (abs(sum[i]-sum[j])<=k)
O(n^2)过不了,可以用树状数组或线段树优化。这里介绍线段树做法。
每次转移时统计[sum[i]-k,sum[i]+k]的方案数,然后把dp[i]加到线段树的sum[i]处。
#include<iostream> #include<cstdio> #include<cstring> #include<cstdlib> #include<cmath> #include<algorithm> #define ll long long #define MOD 1000000007 using namespace std; ll n,k,a[100001],sum[100001],dp[100001]; ll tree[800001]; void revise(ll now,ll l,ll r,ll pos,ll num) { if(l==r) {(tree[now]+=num)%=MOD;return;} ll mid=(l+r)>>1; if(pos<=mid) revise(now<<1,l,mid,pos,num); else revise(now<<1|1,mid+1,r,pos,num); tree[now]=tree[now<<1]+tree[now<<1|1]; } ll query(ll now,ll l,ll r,ll L,ll R) { if(L<=l&&r<=R) return tree[now]%MOD; ll mid=(l+r)>>1; if(R<=mid) return query(now<<1,l,mid,L,R)%MOD; else if(L>mid) return query(now<<1|1,mid+1,r,L,R)%MOD; else return (query(now<<1,l,mid,L,R)%MOD+query(now<<1|1,mid+1,r,L,R)%MOD)%MOD; } int main() { scanf("%lld%lld",&n,&k); for(ll i=1;i<=n;i++) { scanf("%lld",&a[i]); sum[i]=sum[i-1]; if(!a[i]) sum[i]++; else sum[i]--; } dp[0]=1; revise(1,0,n<<1,n,1); for(ll i=1;i<=n;i++) { dp[i]=query(1,0,n<<1,max(sum[i]+n-k,0ll),min(sum[i]+n+k,n<<1)); revise(1,0,n<<1,sum[i]+n,dp[i]); } // for(ll i=1;i<=n;i++) // for(ll j=0;j<i;j++) // if(abs(sum[i]-sum[j])<=k) (dp[i]+=dp[j])%=MOD; printf("%lld\n",dp[n]%MOD); return 0; }