20201003校测

不要问我为什么今天写昨天的校测

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;
}
原文地址:https://www.cnblogs.com/zmyzmy/p/13767595.html