20201003校测

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

T1

description:

有N道判断题(题号编号为0~N-1),已知每道题的答案\((0/1)\)以及答对后能获得的分数。同时还有M个额外分数。
具体而言,每个额外分数给出四个参数\(k\) \(i\) \(type\) \(pt\)
表示对于所有题号\(j\)满足\(j\equiv 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\n",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;
}
posted @ 2020-10-04 16:26  BILL666  阅读(108)  评论(0编辑  收藏  举报