找区间内第一个比x小(大)的数(线段树/单调栈)(ACM-ICPC 2018 南京赛区网络预赛 B G题解)

版权声明:本文为博主原创文章,未经博主允许不得转载。 https://blog.csdn.net/qq_37025443/article/details/82315620

ACM-ICPC 2018 南京赛区网络预赛  G Lpl and Energy-saving Lamps

题目链接

题意:

有n个房间,每个房间有k[i]栈台灯。现在你要把所有房间的台灯换成新的节能台灯

你在每个月一开始会买m栈节能台灯。

换台灯的规则是先从列表上的第一个房间开始,如果当前房间的台灯数量<你拥有的节能台灯的数量,

那么就全部把他换掉,同时把这个房间从列表上清除。否则的话就跳过这个房间,进入下一个房间。

直到你手里的节能台灯数量为0,或者是所有在列表上的房间都遍历过一遍,那么这个月的任务就结束。

你剩余的节能台灯在下一个月还可以使用。如果某个月结束后,所有房间都换过了,那么你就不会再取买

节能台灯了

q个询问,每一个询问一个di,问你在di月结束的时候,你换了几个房间,手里剩余多少节能台灯

n<=1e5,m<=100,di<=1e5,ki<=1e4,q<=1e5

解析:

这里我是直接正面求解,模拟每一次换台灯的过程。

换完一个房间j,换下一个房间,就是在[j+1,n]里面找第一个小于当前节能台灯数量money的房间,

然后我就是用线段树来模拟的,维护区间最小值,查询的时候加一些剪枝(不然会T)

因为每一个房间只会被删除一次,那么时间复杂度接近为O(nlogn)

#include <cstdio>
#include <cstring>
#include <algorithm>
#include <vector>
using namespace std;
#define Min(a,b) a>=b?b:a;
#define Max(a,b) a>=b?a:b;
const int MAXN = 1e5+100;
const int INF = 0x3f3f3f3f;

int b[MAXN];
char str[10];
#define lch root<<1
#define rch (root<<1)|1
typedef long long ll;

 

typedef struct node
{
	int ans;
	int minn;
	int pos;
}node;

node Btree[MAXN*5];
int res[MAXN][2];

inline void push_up(int root)
{
	/*if(Btree[lch].minn>Btree[rch].minn)
	{
		Btree[root].minn=Btree[rch].minn;
		Btree[root].pos=2;
	}
	else if(Btree[lch].minn<Btree[rch].minn)
	{
		Btree[root].minn=Btree[lch].minn;
		Btree[root].pos=1;
	}
	else
	{
		Btree[root].minn=Btree[lch].minn;
		Btree[root].pos=3;
	}*/
	Btree[root].minn=Min(Btree[lch].minn,Btree[rch].minn);
}

 

void build(int l,int r,int root)  //l,r表示他们在stu中的下标,root表示他们在线段树中的坐标
{
	if(l>r)return;
	if(l==r)
	{
		Btree[root].ans=l;  //下标
		//Btree[root].pos=0;
		Btree[root].minn=b[l];
		return ;
	}
	int mid=(l+r)>>1;
	build(l,mid,root<<1);
	build(mid+1,r,(root<<1)|1);
	push_up(root); 

}

 


void updateone(int root,int s1,int e1,int ind)    //将b[ind]置为INF,即将b[ind]删除
{

	if(s1>e1)return;
	if(s1==e1&&s1==ind)
	{
		Btree[root].minn=INF;
		return;

	}
	//pushDown(root);
	int mid=(s1+e1)>>1;
	if(ind<=mid)
		updateone(root<<1,s1,mid,ind);
	else 
		updateone((root<<1)|1,mid+1,e1,ind);
	push_up(root);

}

 

int query(int root,int s1,int e1,int l,int r,int val)  //查询[l,r]内第一个小于val的数的下标,从左往右
{
	
	if(s1>e1)return 0;
	if(s1==e1)
	{
		return Btree[root].minn<=val?Btree[root].ans:0;
	}
	//pushDown(root);
	int mid=(s1+e1)>>1;
	int pl,pr;
	pl=pr=0;
	if(l<=s1&&e1<=r&&Btree[root].minn<=val)
	{
		if(Btree[lch].minn<=val)                    //左边有最小值小于val
			return query(root<<1,s1,mid,l,r,val);
		else 
			return query((root<<1)|1,mid+1,e1,l,r,val);
	}
	else
	{
		if(l<=mid&&Btree[lch].minn<=val) pl=query(root<<1,s1,mid,l,r,val);
		if(pl) return pl;             //左边有最小值小于val
		if(r>mid&&Btree[rch].minn<=val) pr=query((root<<1)|1,mid+1,e1,l,r,val);
		return pr;
		
	}
	return 0;
}



int main()

{

	int n,m;

	scanf("%d%d",&n,&m);
	

		for(int i=1;i<=n;i++) scanf("%d",&b[i]);

		build(1,n,1);
		int money=m;
		int tmp=0;
		int i;
		for(i=1;i<=100000&&tmp<n;i++)
		{
			
			int kk=query(1,1,n,1,n,money);
			while(kk)
			{
				tmp++;
				money-=b[kk];
				b[kk]=INF;
				updateone(1,1,n,kk);
				if(kk>=n) break;
				kk=query(1,1,n,kk+1,n,money);
			}

			res[i][0]=tmp;
			res[i][1]=money;
			money+=m;
			//if(tmp>=n) break;
		}
		int q,y;
		scanf("%d",&q);
		for(int j=0;j<q;j++)
		{
			scanf("%d",&y);
			y=Min(i-1,y);
				printf("%d %d\n",res[y][0],res[y][1]);
		}
	return 0;

}

 

 

 

ACM-ICPC 2018 南京赛区网络预赛  B. The writing on the wall (单调栈+dp)

题目链接

题意:

给你一个n*m的方格,里面都是由n*m个1*1的小方格构成的

然后其中有k个点是被挖空的,输入下面k行,每行x,y表示被挖空的点(1<=x<=n,1<=y<=m)

最后问你能里面有多少个矩形?

没有被挖过的是有36个矩形

中间被挖掉一个,只有20个矩形

1<=n<=1e5,1<=m<=100,1<=k<=1e5

解析:

用dp维护

dp[i][j]表示i*j大小的矩阵有多少个矩形

那么

dp[i][j]=dp[i-1][j]+dp[i][j-1]-dp[i-1][j-1]+qsum[i]

qsum[i]表示包括(i,j)这个点的矩形有多少个,这一部分也是多出来新的多出来的矩形

然后关键就是求这个qsum[i],一开始用树状数组...然后发现不行...比赛就挂了

后来想了一下,发现可以用单调队列+前缀数组解决.....

先用一个yang[w],表示对第w行中,被挖空的方格到当前列j的距离

例如对于上面挖空的矩阵样例,i=3,j=3,yang[1]=3,yang[2]=1,yang[1]=3

那么我们可以发现,其实对于i=3,j=3,第一行只能贡献+1的答案,但是yang[1]=3

然后我们就需要用单调队列/单调栈(我这里使用单调队列模拟的单调栈,原理是一样的)来做,对于第2行,我们需要从下往上,找到第一个yang[]比他小的行,

然后这之间的行对答案的贡献都是yang[2],其实就是下图

对于(i,j),每一行的贡献就是绿色部分。

那么我们就是遍历到每一行,将他插入到单调队列里面,维护最小的单调队列,将在该行之前,比他大的行都弹出

然后就根据第一个比它小的行的id,用一个前缀的数组暂时记录qsum[i]=yang[i]*(i-id)+qsum[i]

最后输出dp[n][m]就可以了。

利用单调栈,可以找到从左/右遍历第一个比它小/大的元素的位置

这道题..后面补的时候dp[i][j]表示j*i的矩阵,...结果这里n,m是不一样大的....数组开错了,结果小的数据对,大的数据就错....

改了一个下午,以为是答案超long long,结果用公式推出来k=0时ans=(1+n)*n*(1+m)*m/4

根本不会爆long long 。。。。最后发现了错误,真的是不好的代码习惯....

#include <cstdio>
#include <cstring>
#include <map>
#include <queue>
#include <algorithm>
using namespace std;
typedef long long ll;
typedef pair<ll,int> pII;
const int MAXN = 1e5+10;
const int MAXM = 100+1;
const ll INF = 0x3f3f3f3f3f3f3f3f;

typedef struct node
{
	ll x;
	int id;
	friend bool operator <(node a,node b)
	{
		return a.x<b.x;
	}
}node;

int n,m,k;
int mp[MAXN][MAXM];
ll dp[MAXN][MAXM];
ll yang[MAXN];
node mq[MAXN];
ll qsum[MAXN];


int main()
{
	int t;
	scanf("%d",&t);
	int cas=0;
	while(t--)
	{
		cas++;
		scanf("%d%d%d",&n,&m,&k);
		for(int i=0;i<=n;i++)
		{
			for(int j=0;j<=m;j++)
				mp[i][j]=1,dp[i][j]=0;
			yang[i]=0;
		}
		for(int i=1;i<=k;i++)
		{
			int x,y;
			scanf("%d%d",&x,&y);
			mp[x][y]=0;
		}
		int beg;
		int head,tail;
		for(int i=1;i<=m;i++)
		{
			beg=0;
			head=tail=0;
			for(int j=1;j<=n;j++)
			{

				if(mp[j][i])
				{
					yang[j]++;
				}
				else
				{
					beg=j;
					yang[j]=0;
				}

				dp[j][i]+=dp[j-1][i]+dp[j][i-1]-dp[j-1][i-1];

				while (head<tail&&mq[tail-1].x>yang[j]) tail--;
				if(head==tail) qsum[j]=yang[j]*j;
				else qsum[j]=yang[j]*(j-mq[tail-1].id)+qsum[mq[tail-1].id];
				mq[tail++]=node{yang[j],j};
				dp[j][i]+=qsum[j];
			}
		}
		printf("Case #%d: %lld\n",cas,dp[n][m]);
	}
    return 0;
}

猜你喜欢

转载自blog.csdn.net/qq_37025443/article/details/82315620