不要问我为什么今天写昨天的校测
T1
description:
有N道判断题(题号编号为0~N-1),已知每道题的答案((0/1))以及答对后能获得的分数。同时还有M个额外分数。
具体而言,每个额外分数给出四个参数(k) (i) (type) (pt)
表示对于所有题号(j)满足(jequiv i(mod 2^k))的题目,如果这些题都选择(type(0/1)),那么会获得(pt)的额外分数
data range;
(N是2的整数次幂且N<=2^{20})
(M<=10^6)
solution:
考虑如何维护那些奇怪的额外分数
有一个很巧妙的方法:
由低位向高位建01Trie,然后树上的每一个节点就恰好对应题目中的“额外分数”
具体来讲,01Trie中深度为d,走到此处值为num就代表这些数除以(2^d)余num
然后就是一个简单的树形dp了
记(f_{pos,0})表示pos子树内的题目全部选0可以获得的最大价值,类似定义(f_{pos,1})
同时记(f_{pos,2})表示pos子树内的题目任意选择可以获得的最大价值
那么就有如下转移:
(f_{pos,0}=f_{lc,0}+f_{rc,0}+ex_{pos,0})
(f_{pos,1}=f_{lc,1}+f_{rc,1}+ex_{pos,1})
(f_{pos,2}=max(f_{lc,2}+f_{rc,2},f_{pos,0},f_{pos,1}))
其中lc,rc分别表示pos的左右儿子,ex表示题目描述中的额外分数
code:
#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
const int N=(1<<20)+5;
int n,st[N],a[N],m;
struct _01Trie
{
int dep,ch[N*21][2],tot;
ll f[N*21][3],ex[N*21][2];
void build(int pos,int d,int num)
{
if(d==dep){f[pos][2]=f[pos][st[num]]=a[num];return;}
ch[pos][0]=++tot;
build(ch[pos][0],d+1,num);
ch[pos][1]=++tot;
build(ch[pos][1],d+1,num+(1<<d));
}
inline void work()
{
dep=log2(n);
tot=1;build(1,0,0);
}
void upd(int pos,int nd,int d,int num,int tp,int val)
{
if(nd==d){ex[pos][tp]+=1ll*val;return;}
if(num&(1<<nd))upd(ch[pos][1],nd+1,d,num,tp,val);
else upd(ch[pos][0],nd+1,d,num,tp,val);
}
void solve(int pos,int d)
{
if(d==dep)return;
solve(ch[pos][0],d+1),solve(ch[pos][1],d+1);
f[pos][0]=f[ch[pos][0]][0]+f[ch[pos][1]][0]+ex[pos][0];
f[pos][1]=f[ch[pos][0]][1]+f[ch[pos][1]][1]+ex[pos][1];
f[pos][2]=max(f[ch[pos][0]][2]+f[ch[pos][1]][2],max(f[pos][0],f[pos][1]));
}
}T;
int main()
{
scanf("%d",&n);
for(int i=0;i<n;++i)scanf("%d",st+i);
for(int i=0;i<n;++i)scanf("%d",a+i);
T.work();
scanf("%d",&m);
while(m--)
{
int k,i,typ,pt;scanf("%d%d%d%d",&k,&i,&typ,&pt);
T.upd(1,0,k,i,typ,pt);
}
T.solve(1,0);
printf("%lld
",T.f[1][2]);
return 0;
}
T2
网络流(不会)
T3
description:
一个(N*M)棋盘中已知每一行的棋子个数(a_i),求出在所有可能的棋局中,棋子数不少于k个的列数最少是多少
data range;
(N<=2*10^5) (M<=10^8)
solution:
考虑二分答案mid判断其是否可行
我们尽量把前mid列全部填满,然后对于剩下的m-mid列,直接上抽屉原理
具体来说mid值是可行的当且仅当((k-1)*(m-mid)>=x)
其中x表示总棋子个数减去前mid列尽量填的个数(即剩下的)
观察到(x=sum_{a_i>=mid}(a_i-mid)=sum_{a_i>=mid}a_i-mid*sum_{a_i>=mid}1)
于是我们可以将a_i从小到大排序然后预处理后缀和check时lower_bound就可以了
这样做的时间复杂度(O(nlog^2n))
其实还可以再优化一点
发现这个东西似乎和[NOI Online #1 提高组]冒泡排序里的柿子十分相似
于是我们可以用动态开点的权值线段树类似地维护
code:
#include<bits/stdc++.h>
using namespace std;
typedef long long LL;
const int N=2e5+5,LOG=20;
int root,n,m,a[N];
struct SGT
{
int tot,lc[N*LOG],rc[N*LOG],c[N*LOG];LL s[N*LOG];
inline void up(int rt){s[rt]=s[lc[rt]]+s[rc[rt]],c[rt]=c[lc[rt]]+c[rc[rt]];}
void upd(int &rt,int l,int r,int p)
{
if(!rt)rt=++tot;
if(l==r){++c[rt],s[rt]+=1ll*l;return;}
int mid=(l+r)>>1;
if(p<=mid)upd(lc[rt],l,mid,p);
else upd(rc[rt],mid+1,r,p);
up(rt);
}
int solve(int rt,int l,int r,int k,LL sum,int cnt)
{
// if(!rt)return 0;
if(l==r)return l;
int mid=(l+r)>>1;
if(sum+s[rc[rt]]-1ll*(cnt+c[rc[rt]])*mid<=1ll*(m-mid)*(k-1))
return solve(lc[rt],l,mid,k,sum+s[rc[rt]],cnt+c[rc[rt]]);
else return solve(rc[rt],mid+1,r,k,sum,cnt);
}
}T;
int main()
{
scanf("%d%d",&n,&m);
for(int i=1;i<=n;++i)scanf("%d",a+i);
for(int i=1;i<=n;++i)T.upd(root,0,m,a[i]);
for(int i=1;i<=n;++i)printf("%d ",T.solve(root,0,m,i,0,0));
return 0;
}