一些NOI.AC模拟赛的题

1.小奇挖矿 (一道调了n久的线段树)

难点:如何把问题转化成几个可以用线段树修改的问题。

于是我们就有了几个函数:update, 区间求和,单点求和,找最后一个某个数i在第几个位置。

逐一实现即可

较难的就是最后一个操作,我们需要知道min,max才能做到,运用二分的思想。若是一个以x为根的子树里最大的数都小于要求的树,那就舍弃这个子树。若是最小值比这个数要小并最大值比这个数要大,那我们就继续排查这个子树。

大致程序就是这样的

inline int query_num(int root,int l,int r,int x)//第几大 
{
	if(mn[root]>=x) return r-l+1;
	if(mx[root]<x) return 0;
	int mid=(l+r)>>1;push_down(root,l,r);
	return query_num(root<<1,l,mid,x)+query_num(root<<1|1,mid+1,r,x);
}

2.一分为二(一道有趣的我调了2个小时的dp题)

并不是非常难,先排序(这样我们就可以知道两个数的大小关系了),然后dp[i][j]表示前i个里面第一组选了j个。难点是转移。我们不妨打破一些东西,考虑每个元素对答案的贡献(正还是负)然后就好啦啦啦

还有就是dp方程尽量正着推,这样好搞

#include <bits/stdc++.h>

using namespace std;
typedef long long ll;
const int maxn=3005;
int n,m,a[maxn];ll f[maxn][maxn]; 
int main()
{
//	freopen("split1.in","r",stdin);
	scanf("%d%d",&n,&m);
	if(m>n-m) m=n-m;
	for(int i=1;i<=n;i++)
	{
		scanf("%d",&a[i]);
	}
	sort(a+1,a+1+n);
	memset(f,0x3f,sizeof(f));
	//直接考虑单个a的贡献 
	f[0][0]=0;
	for(int i=0;i<=n;i++)
	{
		for(int j=0;j<=min(i,m);j++)
		{
			f[i+1][j]=min(f[i][j]+1ll*a[i+1]*(2*j-m),f[i+1][j]);
			f[i+1][j+1]=min(f[i+1][j+1],f[i][j]+1ll*((i-j)-(n-m-i+j))*a[i+1]);
		}	
	}
	printf("%lld",f[n][m]);
	return 0;
} 

 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章