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);
}
posted @ 2026-09-08 20:29  _lipeiran  阅读(5)  评论(0)    收藏  举报