10.2 USACO练习
T1
解析
首先分析一下题意,一种领头羊选择 \((x,y)\) 是合法的当且仅当 \(cnt_x+cnt_y \ge maxcnt\)(\(cnt_i\) 指选 \(i\) 的人,条件显然),然后我们就可以维护大小相同的 \(cnt\) 最左侧和最右侧的位置,用双指针维护上面的条件,复杂度 \(O(nq)\)
这时我们发现,存在的 \(cnt\) 种类是 \(\le \sqrt n\) 的,因此我们做双指针时也只需维护这些数即可,复杂度 \(O(q \sqrt n)\)
#include<bits/stdc++.h>
using namespace std;
const int N=2e5+5;
set<int> s,mx[N];
int cnt[N],a[N],n,q;
int main(){
ios::sync_with_stdio(0),cin.tie(0),cout.tie(0);
cin>>n>>q;
for(int i=1;i<=n;i++){cin>>a[i];cnt[a[i]]++;}
for(int i=1;i<=n;i++) mx[cnt[a[i]]].emplace(a[i]),s.emplace(cnt[a[i]]);
while(q--){
int p,x;cin>>p>>x;
mx[cnt[a[p]]].erase(a[p]);if(mx[cnt[a[p]]].size()==0) s.erase(cnt[a[p]]);
mx[cnt[x]].erase(x);if(mx[cnt[x]].size()==0) s.erase(cnt[x]);
mx[--cnt[a[p]]].emplace(a[p]),mx[++cnt[x]].emplace(x);
s.emplace(cnt[a[p]]),s.emplace(cnt[x]),a[p]=x;
int ans=0,m=*s.rbegin(),R=0;
vector<int> e(s.begin(),s.end());
for(int l=e[0]?0:1,r=e.size()-1;~r&&l<e.size();l++){
while(e[l]+e[r]>=m&&~r&&e[r])
R=max(R,*mx[e[r]].rbegin()),r--;
ans=max(ans,R-*mx[e[l]].begin());
}
cout<<ans<<"\n";
}
return 0;
}
反思/启发
- 想到了条件,却没有想到用双指针维护
- 一个序列不同的 \(cnt\) 大小不超过 \(\sqrt n\)
T2
解析
贪心加模拟
首先 \(x^2 \rarr (x+1)^2\) 就是加了 \(2x\),因此贪心就是每次选最大的加/减
加是显然的,直接选前 \(a\) 大就行
减的操作较复杂,简单来讲分为两部分:1.尽量减到所有数相等;2.所有数整体循环减。具体细节看代码
#include<bits/stdc++.h>
using namespace std;
#define int long long
const int N=1e5+5,mod=1e9+7;
int n,d,a,b,t1[N],t2[N],del[N],sum[N],p,res[N],ss,dd[N];
signed main(){
cin>>n>>d>>a>>b;
for(int i=1;i<=n;i++) cin>>t1[i];
sort(t1+1,t1+n+1);
for(int i=n-a+1;i<=n;i++) t1[i]+=d,t2[i+a-n]=t1[i];
for(int i=1;i<=a;i++) sum[i]=sum[i-1]+t2[i];
for(int i=1;i<=a;i++){
int t=upper_bound(t2+i+1,t2+a+1,t2[i]+d)-t2;
del[i]=(a-t+1)*d+sum[t-1]-sum[i]-t2[i]*(t-i-1);
if(del[i]<=1ll*d*b){p=i;break;}
}
int ss=d*b;
for(int i=p;i<=a;i++) res[i]=d-min(d,t2[i]-t2[p]),t2[i]-=(d-res[i]),ss-=(d-res[i]);
for(int i=p;i<a;i++) res[i]-=res[i+1];
for(int i=a;i>=p;i--){
if(ss>=res[i]*(i-p+1)) dd[i]+=res[i],ss-=res[i]*(i-p+1);
else{dd[i]+=ss/(i-p+1),ss%=(i-p+1),dd[p+ss-1]++;break;}
}
for(int i=a;i>=p;i--) dd[i]+=dd[i+1],t2[i]-=dd[i];
for(int i=n-a+1;i<=n;i++) t1[i]=t2[i+a-n];
int sum=0;
for(int i=1;i<=n;i++) sum=(sum+t1[i]*t1[i]%mod)%mod;
cout<<sum;
return 0;
}
T3
解析
反思/启发
拓扑序不止可以反向循环求,还可以dfs正向求
T4
解析
结论:一个合法的数组一定满足对于任意 \(i<j,a_i>a_j\),都有 \(a_i=a_j+1\)(证明:排序后序列的方差一定小于等于原序列,只有上述情况时取等)
那么我们就可以进行dp,设 \(f_{i,0/1}\) 为当前遍历到 \(c_i\),前缀最大值是 \(v_i+0/1\),那么有转移 \(f_{i,x}=f_{i-1,y}+calc(c_i-c_{i-1}-1,v_{i-1}+x-v_i-y,x=1\land v_i+x \neq v_{i-1}+y)\)
其中 \(calc(x,y,z)\) 是计算中间的方案数,代表有 \(x\) 个数,前缀最大值总共变大了 \(y\),\(z\) 是 \(x\) 个数中是否有前缀最大值,\(calc\) 可以用公式表示 \(calc= \begin{cases} \sum_{i=1}^n\tbinom{n}{i} \tbinom{v}{i} 2^{n-i} \ \ , z=0 \\ \sum_{i=1}^n\tbinom{n}{i} \tbinom{v-1}{i-1} 2^{n-i}\ \ ,z=1 \end{cases}\)(\(i\) 是枚举前缀最大值的个数,第一个组合数是选前缀最大值,第二个是确定前缀最大值的值,\(z=1\) 时已经确定最后一个,第三个是非前缀最大值每个有两种选择:\(v_i=v_{i-1}/v_{i-1}-1\)),但由于 \(v\) 较大,因此需要循环求组合数
#include<bits/stdc++.h>
using namespace std;
#define int long long
const int N=5e6+5,mod=1e9+7,M=105;
int c[M],v[M],dp[M][2],pw[N],inv[N],n,q;
int qpow(int a,int b){
int res=1;
while(b){
if(b&1) res=res*a%mod;
a=a*a%mod,b>>=1;
}return res;
}
void init(){
pw[0]=1;
for(int i=1;i<=n;i++) inv[i]=qpow(i,mod-2),pw[i]=pw[i-1]*2%mod;
}
int calc(int cnt,int del,int p){
if(del<0) return 0;
int res=0,c1=p?cnt:1,c2=1;
for(int i=p;i<=cnt;i++){
res=(res+c1*c2%mod*pw[cnt-i]%mod)%mod,c1=c1*(cnt-i)%mod*inv[i+1]%mod,c2=c2*(del-i)%mod*inv[i+!p]%mod;
}
return res;
}
signed main(){
cin>>n>>q;init();
for(int i=1;i<=q;i++) cin>>c[i]>>v[i];
dp[1][0]=1;
for(int i=2;i<=q;i++)
for(int x=0;x<=1;x++)
for(int y=0;y<=1;y++) dp[i][y]=(dp[i][y]+dp[i-1][x]*calc(c[i]-c[i-1]-1,v[i]+y-v[i-1]-x,y&&v[i-1]+x!=v[i]+y)%mod)%mod;
cout<<(dp[q][0]+dp[q][1])%mod;
return 0;
}

浙公网安备 33010602011771号