一双木棋
根据题目所说,一个格子可以落子当且仅当这个格子内没有棋子且这个格子的左侧及上方的所有格子内都有棋子。
可知每一行的棋子数目是单调不降的
最多有10行,总情况数相当于在0~10这11个数中选取可以重复的十个数的情况
可重集的数目,相当于只有 种合法的棋子摆放情况
要求第一个取的和第二个取的差最大,故在第一个人的回合,保存差的最大值,第二个人的回合,保存差的最小值
用unordered_map压一个十一进制数,用数组保存棋子使用情况,dp转移即可
IIIDX
每种音乐的解锁互相有依赖,这种依赖关系形成了一棵树
题意转变为:一棵树每一条从上到下的链单调不降,并且令编号较大的节点权值更大的情况方案
先考虑没有相同权值
显然,一个节点的子树全部节点的取值直接影响到父亲节点的取值,若一个节点想要取得最大值,当且仅当它的子树节点全部取得最大值
贪心即可
但是有了相同权值,导致子树不一定取到最大值的情况下这个节点依旧能取得最大值
会发现,子树取得最大值不一定是最优情况,因为此节点同层其他节点有可能会取这个值
按照顺序处理每个节点,到达一个节点,这个节点一定要取可能取到的最大值
然后它的子树一定取值小于等于这个数,需要减去子树的可能取值后在考虑后面的取值
每次都取这个值的最大编号,为剩余的点来腾出空间
线段树维护,将可选值从大到小排序,每个点维护前面剩余的总数,每次选取条件是后面的所有点的值都大于等于这个点的size
#include<bits/stdc++.h>
using namespace std;
bool cmp(int x,int y){
return x>y;
}
struct node{
int tot,tag;
};
struct segment_tree{
node tr[2000100];
void push_down(int id,int l,int r){
tr[id].tot+=tr[id].tag;
if(l!=r) tr[id<<1].tag+=tr[id].tag,tr[id<<1|1].tag+=tr[id].tag;
tr[id].tag=0;
}
void upd(int id,int l,int r,int x,int y,int w){
if(l>r) return ;
if(x<=l&&r<=y){
tr[id].tag+=w;
push_down(id,l,r);
return ;
}
push_down(id,l,r);
int mid=(l+r)>>1;
if(mid>=x) upd(id<<1,l,mid,x,y,w);
if(mid<y) upd(id<<1|1,mid+1,r,x,y,w);
push_down(id<<1,l,mid),push_down(id<<1|1,mid+1,r);
tr[id].tot=min(tr[id<<1].tot,tr[id<<1|1].tot);
}
int q_th(int id,int l,int r,int th){
int mid=(l+r)>>1;
if(l==r) return (tr[id].tot>=th?l:l+1);
push_down(id<<1,l,mid),push_down(id<<1|1,mid+1,r);
assert(tr[id].tot==min(tr[id<<1].tot,tr[id<<1|1].tot));
if(th<=tr[id<<1|1].tot) return q_th(id<<1,l,mid,th);
else return q_th(id<<1|1,mid+1,r,th);
}
};
segment_tree ty;
int a;
double b;
vector<int>tu[500010];
int sz[500010],fa[500010];
bool vis[500010];
void dfs(int x);
int ans[500010],ans_id[500010],w[500010],cnt[500010];
int main(){
scanf("%d%lf",&a,&b);
for(int i=1;i<=a;i++){
scanf("%d",&w[i]);
}
sort(w+1,w+a+1,cmp);
for(int i=1;i<=a;i++) ty.upd(1,1,a,i,i,i);
for(int i=1;i<=a;i++){
if(i/b) tu[(int)(i/b)].push_back(i),fa[i]=i/b;
}
for(int i=a-1;i>=1;i--) cnt[i]=(w[i]==w[i+1]?cnt[i+1]+1:0);
for(int i=1;i<=a;i++){
if(!vis[i]) dfs(i);
}
memset(vis,0,sizeof(vis));vis[0]=true;
for(int i=1;i<=a;i++){
if(!vis[fa[i]]) ty.upd(1,1,a,ans_id[fa[i]],a,sz[fa[i]]-1),vis[fa[i]]=true;
int id=ty.q_th(1,1,a,sz[i]);
id+=cnt[id],ans[i]=w[id];
ans_id[i]=id;
ty.upd(1,1,a,id,a,-sz[i]);
}
for(int i=1;i<=a;i++) printf("%d ",ans[i]);
return 0;
}
void dfs(int x){
vis[x]=true,sz[x]=1;
for(int i=0;i<tu[x].size();i++){
int p=tu[x][i];
dfs(p);
sz[x]+=sz[p];
}
}
劈配
按照顺序选取导师,要求每个人先选取较高志愿的导师
网络流,动态加边,因为每次加边后在残量网络上计算并不会影响到前面的取值
先按照每一位成员的排名,再按照每一档导师的顺序加边,匹配上即可
第二位二分新排名,重新动态加边判断即可
线段树合并
相当于在较优的时间内完成两个数组的按位加法
每次从根往下寻找位置,若两个节点都有,判断是否为叶子节点,若是叶子节点直接假加,否则继续合并,若有一边是空的,在就直接取另一个节点
时间复杂度根加法的次数有关,若整个程序改变了n个点的值,时间复杂度为O(nlogW)
雨天的尾巴
树上差分,每次改动四个节点的权值
每个节点开以救济粮类型为节点的线段树,记录每种救济量的出现次数
然后从叶子节点向上做线段树合并
每次查询每个节点的最大值所在位置即可