题目链接
题意:
有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
#include
#include
#include
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].minnr)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) 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
题目链接
题意:
给你一个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
#include
#include