USACO 银组速通攻略

T1

题目大意

        有两个长为N的序列A和B,让你反转A的一段区间[l,r],贡献F(l,r)为A与B相同的位数。

        你要求对于每一个可能的区间[l,r]的F(l,r)之和。

        N<=5e5 1<=Ai,Bi<=N

算法分析

        不枚举每个区间怎么求?枚举一个端点?

        我们先来发现一个性质,当反转的区间和序列中的两个元素中心点一样,那么这两个元素将会交换。

        我在比赛中是这样做的:

                枚举i从1-N,统计Ax=By=i的贡献

                考虑有 Ax=By

                当x=y,贡献为 min(x,n-x+1) + x*(x-1)/2 + (n-x)*(n-x+1)/2

                即与x不交的区间数加上以x为中心点的区间数。

                当x<y,贡献为 min(x,n-y+1)

                当x>y 贡献 min(y,n-x+1)

这样当出现大量相同数时,容易卡到 N方。

于是我们可以对于每个Ai,两次二分,先二分出Ai=Bj且 j>i 的所有元素,顺带可以求出Ai=Bj且j<=i的元素。

对于Ai=Bj且j>i的所有元素再次二分出 n-y+1<=x的元素,这一段直接用前缀和算贡献

对于Ai=Bj且j<i同理

对于Ax=Bx输入时加上x*(x-1)/2 + (n-x)*(n-x+1)/2即可。

绿题差不多吧

赛时代码

#include <iostream>
#include <vector>
using namespace std;
#define int long long
const int NR=5e5+5;
int n,a[NR],b[NR],pre[NR],pg[NR],ans=0;
vector<int> e[NR],e2[NR];
signed main(){
	cin>>n;
	for(int i=1;i<=n;i++){
		cin>>a[i];
		e[a[i]].push_back(i);
	}
	for(int i=1;i<=n;i++){
		cin>>b[i];
		if(a[i]==b[i])
			ans+=i*(i-1)/2+(n-i)*(n-i+1)/2;
		e2[b[i]].push_back(i);
	}
	for(int i=1;i<=n;i++){
		if(e[i].empty()||e2[i].empty())continue;
		int len=e2[i].size();
		pre[0]=e2[i][0];
		for(int j=1;j<len;j++)pre[j]=pre[j-1]+e2[i][j];
		pg[len]=0;
		for(int j=len-1;j>=0;j--)pg[j]=pg[j+1]+n-e2[i][j]+1;
		for(int j:e[i]){
			int l=0,r=len-1;
			while(l<r){
				int mid=(l+r+1)>>1;
				if(e2[i][mid]<=j) l=mid;
				else r=mid-1;
			}
			if(e2[i][l]<=j){
				int L=0,R=l;
				while(L<R){
					int mid=(L+R+1)>>1;
					if(e2[i][mid]<=n-j+1) L=mid;
					else R=mid-1;
				}
				if(e2[i][L]<=n-j+1){
					ans+=pre[L]+(n-j+1)*(l-L);
					}
				else
					ans+=(n-j+1)*(l+1);
			} else l--;
			if(l+1<len){
			int ll=l+1,rr=len-1;	
			while(ll<rr){
				int mid=(ll+rr)>>1;
				if(n-e2[i][mid]+1<=j)rr=mid;
				else ll=mid+1;
			}
			if(n-e2[i][ll]+1<=j){
				ans+=pg[ll]+j*(len-1-l-(len-ll));
			} else ans+=j*(len-1-l);
			}
		}
	}
	cout<<ans;
	return 0;
}

T2

题目大意

有一个长为N的序列A,定义一次操作为选定一个1<=i<=N,让Ai+1或-1。

求最小的操作使得A序列 mod M同余。

算法分析

先把整个A序列全部 mod M,然后事情变成了环形的输油管道问题。

但环形问题和线性问题不同的一点在于:点点平等(每个点都可能是中心点)

于是枚举每一个点算一下与其他点的总距离和取最小值即可。

算总距离用双指针就行了,要注意维护|Ai-Aj|>M/2的数特殊算贡献。

赛时代码

#include <iostream>
#include <algorithm>
using namespace std;
#define int long long
const int NR=5e5+5,inf=1e18+7;
int T,n,m,a[NR],pre[NR];
int qry(int x,int y){
	if(x>y)return 0;
	return pre[y]-pre[x-1];
}
signed main(){
	cin>>T;
	while(T--){
		cin>>n>>m;
		for(int i=1;i<=n;i++)cin>>a[i];
		for(int i=1;i<=n;i++){a[i]%=m;}
		sort(a+1,a+n+1);
		for(int i=1;i<=n;i++)pre[i]=pre[i-1]+a[i];
		int l=1,r=1,ans=inf;
		for(int i=1;i<=n;i++){
			while(a[i]-a[l]>m/2) l++;
			while(a[r]-a[i]<=m/2&&r<=n) r++;
			int sum=a[i]*(i-l)-qry(l,i-1)+(m-a[i])*(l-1)+qry(1,l-1);
			sum+=qry(i+1,r-1)-a[i]*(r-i-1)+(m+a[i])*(n-r+1)-qry(r,n);
			ans=min(ans,sum);
		}
		cout<<ans<<'\n';
	}
	return 0;
}

T3

题目大意

不想说了,到时候等锣鼓吧。

思路

纯思维题警告!!!

先枚举2所在的位置,然后发现最小的行和最小的列的交叉点就是2,于是有开开心心地填了2N-1个空位,其他的点的大小都只会受到最小的行和最小的列的限制,注意尽量往小了填。

我的结论全是猜的,请勿学习,在我心里这题直接想出来思维难度还是很高的。

赛时代码

#include <iostream>
#include <cstring>
#include <vector>
using namespace std;
const int NR=3005,inf=1e9+7;
int n,a[NR][NR],cnt[NR*3],b[NR][NR],c[NR][NR],daan[NR],mxb[NR],mnb[NR],mxc[NR],mnc[NR];
int ans[NR][NR],us[NR],ot[NR][NR];
int main(){
	cin>>n;
	for(int i=1;i<=n;i++)
		for(int j=1;j<=n;j++){
			cin>>a[i][j];
			cnt[a[i][j]]++;
			mnb[i]=mnc[i]=ot[i][j]=inf;
		}
	for(int i=1;i<=n;i++){
		for(int j=1;j<=n;j++){
			int h=a[i][j],g=cnt[a[i][j]]+1;
			int g2=2*n+2-g;
			c[i][j]=g2;b[i][j]=g;
		}
	}
	for(int i=1;i<=n;i++){
		for(int j=1;j<=n;j++){
			if(b[i][j]==2){
				memset(ans,0,sizeof(ans));
				memset(mxb,0,sizeof(mxb));
				memset(us,0,sizeof(us));
				for(int k=1;k<=n;k++) ans[i][k]=b[i][k];
				for(int k=1;k<=n;k++) ans[k][j]=b[k][j];
				for(int l=1;l<=n;l++){
					for(int h=1;h<=n;h++){
						if(l!=i&&j!=h)
						mxb[a[l][h]]=max(mxb[a[l][h]],max(ans[l][j],ans[i][h]));
					}
				}
				for(int l=1;l<=n;l++){
					for(int h=1;h<=n;h++){
						if(l==i||h==j)continue;
						if(mxb[a[l][h]]<b[l][h]&&(us[b[l][h]]==0||us[b[l][h]]==a[l][h])){
							ans[l][h]=b[l][h]; us[b[l][h]]=a[l][h];
						}
						else {
							us[c[l][h]]=a[l][h];
							ans[l][h]=c[l][h];	
						}
					}
				}
				for(int i=1;i<=n;i++){
					int f=0;
					for(int j=1;j<=n;j++){
						if(ans[i][j]<ot[i][j]){
							f=1;memcpy(ot,ans,sizeof(ot));break;
						}else if(ans[i][j]>ot[i][j]){
							f=1;break;
						}
					}
					if(f)break;
				}
			}
		}
	}
	for(int i=1;i<=n;i++){
	for(int j=1;j<=n;j++){
		cout<<ot[i][j];
		if(j!=n)cout<<' ';
	}if(i!=n)cout<<'\n';}
	return 0;
}

好吧,其实代码难度也不小。

总结

这场比赛是三道养生思维题的集合。也是我这种护肝的养生选手的福音。

大家想要AK这种比赛,要大胆地去猜结论哦!

难度预测:绿-橙-蓝

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值