大概是需要前缀和优化DP,和记录左右范围。
还有一道题,没有下手,等做完了,再来总结。
1,数组分拆: 给定数组,问有多少种拆分法,使得每一段和不为0。 (1e5)
(用map优化DP)
#include<map> #include<cstdio> #include<cstdlib> #include<iostream> #include<cstring> #include<algorithm> using namespace std; const int Mod=1e9+7; const int maxn=100010; map<int,int>mp; int dp[maxn],sum[maxn],Now=1; int main() { int n,i,j; Now=1; mp[0]=1; scanf("%d",&n); for(i=1;i<=n;i++) scanf("%d",&sum[i]),sum[i]+=sum[i-1]; for(i=1;i<=n;i++){ dp[i]=Now; int tmp=0; if(mp.find(sum[i])!=mp.end()) tmp=mp[sum[i]]%Mod; dp[i]=((dp[i]-tmp)%Mod+Mod)%Mod; Now=(Now+dp[i])%Mod; mp[sum[i]]=(mp[sum[i]]+dp[i])%Mod; } printf("%d ",dp[n]%Mod); return 0; }
2,数组区间: 求所有区间前k大的和。 (1e5,k<=50)
(枚举每个数的的左右端点,我用的暴力,还可以优化)
#include<cstdio> #include<cstdlib> #include<cstring> #include<cmath> #include<iostream> #include<algorithm> using namespace std; #define ll long long const int maxn=2000010; ll ans,x[maxn],n,k,L[60],R[60]; void getL(ll u) { memset(L,0,sizeof(L)); ll tmp=1; L[tmp]=u; for(ll i=u-1;i>=1;i--){ if(x[i]<x[u]) L[tmp]=i; else L[++tmp]=i; if(tmp>51) break; } } void getR(ll u) { memset(R,0,sizeof(R)); ll tmp=1; R[tmp]=u; for(ll i=u+1;i<=n;i++){ if(x[i]<x[u]) R[tmp]=i; else R[++tmp]=i; if(tmp>51) break; } } int main() { scanf("%lld%lld",&n,&k); for(ll i=1;i<=n;i++) scanf("%lld",&x[i]); for(ll i=1;i<=n;i++){ getL(i); getR(i); for(ll j=1;j<=50&&L[j];j++) for(ll p=1;p<=50&&R[p];p++){ if(j+p-2<k){ ll tmp1=L[j-1],tmp2=R[p-1]; if(tmp1==0) tmp1=i+1; if(tmp2==0) tmp2=i-1; ans+=(ll)(tmp1-L[j])*(R[p]-tmp2)*x[i]; } } } printf("%lld ",ans); return 0; }
3,数组分拆II:给定数组,问有多少种拆法,使得每一段不出现重复的数字,且要保证分组数最少。(1e5)
(和第一题有些像,只要快速找到前面的最大限制即可)
#include<cstdio> #include<cstdlib> #include<iostream> #include<algorithm> using namespace std; const int maxn=100010; const int Mod=1000000007; int a[maxn],d[maxn],dp[maxn],sum[maxn]; int Laxt[maxn],pre[maxn],Fir[maxn],L,Now; int main() { int i,j,n; scanf("%d",&n); for(i=1;i<=n;i++){ scanf("%d",&a[i]); if(Laxt[a[i]]) pre[i]=Laxt[a[i]]; Laxt[a[i]]=i; } for(i=1;i<=n;i++){ if(pre[i]>L) L=pre[i]; d[i]=d[L]+1; if(!Fir[d[i]]) Fir[d[i]]=i; } L=0; Now=0; for(i=1;d[i]==1;i++) dp[i]=1,sum[i]=i; for(;i<=n;i++){ if(d[i]!=Now) { L=Fir[Now]; Now=d[i]; } L=max(L,pre[i]); dp[i]=((sum[Fir[Now]-1]-sum[L-1])%Mod+Mod)%Mod; sum[i]=(sum[i-1]+dp[i])%Mod; } printf("%d %d ",d[n],dp[n]); return 0; }
4,有趣的子区间:给定a<=b求,问有多少对p,q,满足a<=p<=q<=b,使得区间[p,q]是有趣区间。有趣区间是指包含偶数个回文数。(1e10)
5,区间价值: 区间的价值定理为有多少对相同元素,问所有区间里,第k小区间价值是多少。
(利用单调性可以使用二分法,check的时候想双指针一样扫描)
#include<cstdio> #include<cstdlib> #include<cstring> #include<iostream> #include<algorithm> using namespace std; #define ll long long const int maxn=200010; ll a[maxn],b[maxn],vis[maxn],cnt,N,K; bool check(ll x) { memset(vis,0,sizeof(vis)); ll sum=0, ans=0; for(int tail=1,head=1;head<=N;head++){ while(tail<=N&&sum+vis[a[tail]]<=x){ sum+=vis[a[tail]]; vis[a[tail]]++; tail++; } ans+=tail-head; if(ans>=K) return true; vis[a[head]]--; sum-=vis[a[head]]; } return false; } int main() { ll T,L,R,Mid,ans; scanf("%lld",&T); while(T--){ scanf("%lld%lld",&N,&K); for(int i=1;i<=N;i++){ scanf("%d",&a[i]); b[i]=a[i]; } sort(b+1,b+N+1); cnt=unique(b+1,b+N+1)-(b+1); for(int i=1;i<=N;i++) a[i]=lower_bound(b+1,b+cnt+1,a[i])-b; L=0; R=N*(N-1)/2; while(L<=R){ Mid=(L+R)/2; if(check(Mid)) ans=Mid,R=Mid-1; else L=Mid+1; } printf("%lld ",ans); } return 0; }