9月8日思考题
[USACO26JAN1] COW Traversals G
题意
现在有一个 \(n\) 个点 \(n\) 条边的有向图,每个点的出度恰好为 \(1\) ,最开始所有的点均未被染色。
现在有 \(m\) 次操作,每次操作将一个点染色,颜色是 \(C , O , W\) 中的一种。
一个点被归属的类别是一直沿着其出边向前走,直到第一个被染色的点,它的类别就是这个被染色的点的颜色。如果走不到被染色的点,那么它不归属于任何类别。
对于每一次操作后,求出每一个类别的点的数量。
其中, \(1 \leq n , m \leq 2 \times 10 ^ 5\) 。
解法
容易发现,如果一个点被染色,那么它的出边是没有任何所用的,因为其他点到它这里就停了。
又因为恰好有 \(n\) 条边,且每个点的出度恰好为 \(1\) 。于是染色可以看做删边操作。
但是删边操作是非常不好完成的,于是我们考虑倒着做,先算出最终的结果和图,把删边变为加边,直接用并查集即可。
时间复杂度 \(O(n \alpha(n))\) 。
代码
#include<bits/stdc++.h>
using namespace std;
const int N=2e5+100;
int n,m,s[N],type[N],sz[N],fa[N],sum[4],ans[N][4];bool is[N];
struct ask{
int u,v,f;
}q[N];
void init(int x){for(int i=1;i<=x;i++)s[i]=i,sz[i]=1;}
int find(int x){return x==s[x]?x:s[x]=find(s[x]);}
int main(){
scanf("%d",&n);
init(n+1);
for(int i=1,x;i<=n;i++){
scanf("%d",&x);
fa[i]=x;
}
scanf("%d",&m);
for(int i=1;i<=m;i++){
int u,v;char cc;
scanf("%d %c",&u,&cc);
if(cc=='C')v=1;
else if(cc=='O')v=2;
else v=3;
q[i]={u,type[u],fa[u]};
fa[u]=u;
type[u]=v;
}
for(int i=1,u,v;i<=n;i++){
u=i,v=fa[i];
u=find(u),v=find(v);
if(u==v)continue;
s[u]=v;
sz[v]+=sz[u];
}
for(int i=1;i<=n;i++)is[find(i)]=1;
for(int i=1;i<=n;i++)if(is[i])sum[type[i]]+=sz[i];
for(int i=m,u,v;i>=1;i--){
for(int j=1;j<=3;j++)ans[i][j]=sum[j];
u=q[i].u,v=q[i].f;
u=find(u),v=find(v);
sum[type[u]]-=sz[u];
type[u]=q[i].v;
sum[type[v]]+=sz[u];
if(u==v)continue;
s[u]=v;
sz[v]+=sz[u];
}
for(int i=1;i<=m;i++)printf("%d %d %d\n",ans[i][1],ans[i][2],ans[i][3]);
}
[USACO26JAN1] Milk Buckets G
题意
给定长度为 \(n\) 的序列 \({a_i}\) ,你可以花费 \(1\) 点代价交换两个相邻的数,求最小的花费使得这个序列的价值最大。
一个序列的价值定义为执行 \(n - 1\) 次合并两个相邻的数变成这两个数的平均数后,剩下的这个数的最大值。
其中, \(1 \leq n \leq 2 \times 10 ^ 5\) , \(\forall 1 \leq i \leq n\) ,有 \(0 \leq a_i \leq 10 ^ 9\) 。
解法
先考虑只有 \(3\) 个数的情况,假设为 \(a_1 , a_2 , a_3\) ,并且 \(a_1 \leq a_2 \leq a_3\) 。
如果 \(a_3\) 放在最左边或者最右边,价值最大为 \(\frac {\frac {a_1 + a_2} {2} + a_3} {2}\) 。如果 \(a_3\) 放在中间,价值最大为 \(\frac {\frac {a_1 + a_3} {2} + a_2} {2}\) 。
显然 \(a_3\) ,放在中间是不优的。
现在把这个推广到更多元的情况,发现最大值放在中间是不好的,所以最大值应放在两侧。
现在问题变成了一个子问题,每次取出最大值判断放在左侧还是右侧,选交换次数少的一侧。
左侧交换次数是左侧比这个数小的数的个数,右侧的交换次数是右侧比这个数小的个数,有树状数组维护。
在代码实现时,无需从最大开始考虑,因为大的数对小的数没有影响。
时间复杂度 \(O(n \log n)\)
代码
#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
const int N=5e5+100;
int n,a[N],mp[N],sum1[N],sum2[N];ll ans;
struct BIT{
int t[N],n;
void init(int x){memset(t,0,sizeof t);n=x;}
int lowbit(int x){return x&(-x);}
void update(int x,int k){for(int i=x;i<=n;i+=lowbit(i))t[i]+=k;}
int query(int x){int res=0;for(int i=x;i;i-=lowbit(i))res+=t[i];return res;}
}bit1,bit2;
int main(){
int t;
scanf("%d",&t);
while(t--){
scanf("%d",&n);
bit1.init(n),bit2.init(n);ans=0;
for(int i=1;i<=n;i++){scanf("%d",&a[i]);mp[i]=a[i];}
sort(mp+1,mp+n+1);
for(int i=1;i<=n;i++)a[i]=lower_bound(mp+1,mp+n+1,a[i])-mp;
for(int i=n;i>=1;i--){
bit2.update(a[i],1);
}
for(int i=1;i<=n;i++){
bit1.update(a[i],1),bit2.update(a[i],-1);
ans+=min(bit1.query(a[i]-1),bit2.query(a[i]-1));
}
printf("%lld\n",ans);
}
}
[USACO26JAN1] Supervision G
题意
有 \(n\) 个人,其中有一些是教练,有一些是选手。第 \(i\) 个人有一个站位 \(p_i\) ,有 \(p_i\) 单调递增。
现在要选出一些人使得每一个选手左侧 \(D\) 单位距离内至少有一个教练。
求有多少中选法,答案对 \(10 ^ 9 + 7\)取模。
其中, \(1 \leq n \leq 10 ^ 6\) ,\(\forall 1 \leq i \leq n\) ,都有 \(1 \leq p_i \leq 10 ^ 9\)。
解法
我们对教练做dp。
设 \(f_i\) 表示只考虑前 \(i\) 个人,并且选了第 \(i\) 个人的方案数。
那么有转移 \(f_i = \sum_{j<i} f_j + 2 ^ {cnt_j}\) ,其中 \(cnt_j\) 是 \(i\) 到 \(j\) 之间营员的数目。
朴素转移是 \(O(n^2)\) 的,考虑优化。
转移可以分为两段,分别是 \(p_i - p_j \leq D\) 时和 \(p_i - p_j \gt D\) 时。
用双指针优化即可做到 \(O(n)\) ,可以通过本题。
代码
#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
const int N=1e6+100,mod=1e9+7;
int n,d,p[N],cnt[N];ll pw2[N],f[N],ans,sum;bool is[N];
int main(){
scanf("%d %d",&n,&d);pw2[0]=1;
for(int i=1;i<=n;i++)pw2[i]=pw2[i-1]*2%mod;
for(int i=1;i<=n;i++)scanf("%d %d",&p[i],&is[i]);
for(int i=1,j=1,k=0;i<=n;i++){
if(is[i])f[i]=ans+1,ans=(ans+f[i])%mod,sum=(sum+f[i])%mod;
else{
while(p[j]+d<p[i])sum=(sum-f[j]*pw2[k]%mod+mod)%mod,k-=cnt[++j];
ans=(ans+sum)%mod,sum=sum*2%mod,k++,cnt[i+1]++;
}
}
printf("%lld\n",ans);
}

浙公网安备 33010602011771号