[多校联考2021] 模拟赛6

奇怪的数列

题目描述

有一个长度为 (n) 的数字序列,对这个序列的任意一个连续子串,求所有数字之和,重复出现的数字只被统计一次,问第 (k) 大的和是多少。

( t subtask1)(nleq 2000)

( t subtask2:) (0leq a_i)

(1leq nleq 10^5,1leq kleq 2cdot 10^5,0leq|a_i|leq 10^9)

解法

这道题考试时候切了,直接贴考试的笔记。

补充一个细节:这道题最大的误区就是一上来就直接二分答案,但是这样就用不到 (k) 很小的条件,如果你刷过类似的题就知道这个东西多半是让你维护一个不塞所有元素的优先队列,在取出最大值后塞入后继元素即可,所有要找单调性


(nleq 2000) 乱搞一下,期望得分 (20) 分。

(kleq 2cdot 10^5) 你难道就没有一点想法吗?肯定往优先队列方面想

对于 (0leq a_i) 可以直接把每个左端点塞进优先队列里面,右端点都取 (n),如果用了哪个左端点就把右端点左移一格,不难证明时间复杂度 (O(klog n)),期望得分 (20) 分。

可以主席树维护每个左端点对应右端点的答案(直接标记永久化),每个就拿最大值,如果最大值用过就将他删掉(也要可持久化地修改),时间复杂度 (O(klog n)),期望得分 (100) 分。

#include <cstdio>
#include <queue>
#include <map>
using namespace std;
const int M = 100005;
const int N = 200*M;
#define ll long long
const ll inf = 1e15;
int read()
{
	int x=0,f=1;char c;
	while((c=getchar())<'0' || c>'9') {if(c=='-') f=-1;}
	while(c>='0' && c<='9') {x=(x<<3)+(x<<1)+(c^48);c=getchar();}
	return x*f;
}
int n,k,cnt,a[M],rt[M],pos[N],ls[N],rs[N],nt[M];
map<int,int> mp;ll b[M],mx[N],tag[N];
struct node
{
	ll x,y;
	node(ll X=0,ll Y=0) : x(X) , y(Y) {}
	bool operator < (const node &b) const
	{
		return y<b.y;
	}
};priority_queue<node> q;
void up(int x)
{
	mx[x]=max(mx[ls[x]],mx[rs[x]]);
	if(mx[x]==mx[ls[x]]) pos[x]=pos[ls[x]];
	else pos[x]=pos[rs[x]];
	mx[x]+=tag[x];//永久化在这里加 
}
void build(int &x,int l,int r)
{
	x=++cnt;
	if(l==r)
	{
		mx[x]=b[l];pos[x]=l;
		return ;
	}
	int mid=(l+r)>>1;
	build(ls[x],l,mid);
	build(rs[x],mid+1,r);
	up(x);
}
void upd(int &x,int y,int l,int r,int L,int R,ll f)
{
	if(L>r || l>R) return ;
	x=++cnt;ls[x]=ls[y];rs[x]=rs[y];
	tag[x]=tag[y];mx[x]=mx[y];pos[x]=pos[y];
	if(L<=l && r<=R)
	{
		tag[x]+=f;mx[x]+=f;
		return ;
	}
	int mid=(l+r)>>1;
	upd(ls[x],ls[y],l,mid,L,R,f);
	upd(rs[x],rs[y],mid+1,r,L,R,f);
	up(x);
}
signed main()
{
	freopen("seq.in","r",stdin);
	freopen("seq.out","w",stdout);
	n=read();k=read();
	for(int i=1;i<=n;i++)
	{
		a[i]=read();
		b[i]+=b[i-1];
		if(!mp[a[i]]) b[i]+=a[i];//第一次出现
		mp[a[i]]=1;
	}
	build(rt[1],1,n);
	mp.clear();
	for(int i=n;i>=1;i--)//找到后面第一个相同的 
	{
		if(!mp[a[i]]) nt[i]=n;
		else nt[i]=mp[a[i]]-1;
		mp[a[i]]=i; 
	}
	for(int i=2;i<=n;i++)
	{
		upd(rt[i],rt[i-1],1,n,i-1,nt[i-1],-a[i-1]);//删掉i-1
		int x=0;
		upd(x,rt[i],1,n,i-1,i-1,-inf);
		rt[i]=x;
	}
	for(int i=1;i<=n;i++)
		q.push(node(i,mx[rt[i]]));
	while(k--)
	{
		node t=q.top();q.pop();
		if(k==0)
		{
			printf("%lld
",t.y);
			return 0;
		}
		int x=0;
		upd(x,rt[t.x],1,n,pos[rt[t.x]],pos[rt[t.x]],-inf);
		rt[t.x]=x;//删掉最大值 
		q.push(node(t.x,mx[rt[t.x]]));
	}
}

孤独的解锁者

题目描述

给一个 (n) 个点 (m) 条边的有向无环图,每次选一条链,链上权值都必须大于 (0),边的权值也必须大于 (0),选完之后点和边权值都减 (1),问如果要让点权都为 (0) 最小的选链次数。

(1leq nleq 10^4,1leq mleq 10^5)

解法

一开始还想了一些 (dp) 做法,下次看到这种较为复杂的问题可以先想网络流。

考试时候看到这个题以为是经典补流模型,然后一波费用流:

  • (S) 连入点,容量为 (x) 费用为 (1);入点连 (T) 容量为 (x)

  • (S) 连出点,容量为 (x);对于一条边,(u) 的出点连 (v) 的入点,容量为边权。

好家伙,我还以为跑得过,结果直接 ( t TLE)(50) 分。


你发现只有第种条边有个费用 (1),跑费用流肯定是大才小用了。能跑最大流就不要跑费用流

我们想要求出最少的选链次数,使用调整法,使用一条边的单位权值就可以减少一次选链,那么可以看成二分图匹配:

  • (S) 连入点,容量为 (x);出点连 (T),容量为 (x)
  • 对于一条边,(u) 入点连 (v) 出点,容量为边权

那么跑出最大流之后拿初始选链次数减一减就行了,( t dinic) 直接 (10ms) 以内解决。

#include <cstdio>
#include <iostream>
#include <queue>
using namespace std;
const int M = 100005;
const int inf = 1e9;
int read()
{
	int x=0,f=1;char c;
	while((c=getchar())<'0' || c>'9') {if(c=='-') f=-1;}
	while(c>='0' && c<='9') {x=(x<<3)+(x<<1)+(c^48);c=getchar();}
	return x*f;
}
int n,m,s,t,tot,ans,f[M],dis[M],cur[M];
struct edge
{
	int v,c,next;
}e[10*M];
void add(int u,int v,int c)
{
	e[++tot]=edge{v,c,f[u]},f[u]=tot;
	e[++tot]=edge{u,0,f[v]},f[v]=tot;
}
int bfs()
{
	for(int i=s;i<=t;i++) dis[i]=0;
	queue<int> q;
	q.push(s);dis[s]=1;
	while(!q.empty())
	{
		int u=q.front();q.pop();
		if(u==t) return 1;
		for(int i=f[u];i;i=e[i].next)
		{
			int v=e[i].v;
			if(e[i].c>0 && !dis[v])
			{
				dis[v]=dis[u]+1;
				q.push(v);
			}
		}
	}
	return 0;
}
int dfs(int u,int ept)
{
	if(u==t) return ept;
	int flow=0,tmp=0;
	for(int &i=cur[u];i;i=e[i].next)
	{
		int v=e[i].v;
		if(e[i].c>0 && dis[v]==dis[u]+1)
		{
			tmp=dfs(v,min(ept,e[i].c));
			if(!tmp) continue;
			flow+=tmp;
			e[i].c-=tmp;
			e[i^1].c+=tmp;
			ept-=tmp;
			if(!ept) break;
		}
	}
	return flow;
}
signed main()
{
	freopen("tobira.in","r",stdin);
	freopen("tobira.out","w",stdout);
	n=read();m=read();
	tot=1;s=0;t=2*n+1;
	for(int i=1;i<=n;i++)
	{
		int x=read();ans+=x;
		add(s,i,x);
		add(i+n,t,x);
	}
	for(int i=1;i<=m;i++)
	{
		int u=read(),v=read(),c=read();
		add(u,v+n,c);
	}
	while(bfs())
	{
		for(int i=s;i<=t;i++) cur[i]=f[i];
		ans-=dfs(s,inf);
	}
	printf("%d
",ans);
}

闪耀的灾星

题目描述

给定 (n) 个二维平面上的点,每个点都有一个出现概率 (a_i\%_0),问这些点构成凸包的期望面积。

( t subtask1:) (nleq 100)

(1leq nleq1000,0leq|x_i|,|y_i|,a_ileq1000)

解法

( t Oneindark):期望这种东西肯定要考虑拆开算啊。

发现直接考虑整个凸包太操蛋了,考虑凸包面积的计算方式是三角剖分,其实就是凸包上相邻向量的叉积和除以 (2),那么我们考虑每对点构成向量出现在凸包上的概率即可,这样就成功的把贡献拆开了。

向量出现在凸包上概率就是在它右面的点必须不出现的概率,那么枚举点之后暴力判断可以做到 (O(n^3))

可以枚举起点 (s),对所有点极角排序,然后枚举终点 (t),发现可以用指针维护另一个分界点,所以时间复杂度 (O(n^2log n))

精度被卡、三点共线不知道怎么办 (...) 真正碰到这种题就退居其次吧,赛后我都打不出来正解,下面代码仅供参考:

#include <cstdio>
#include <algorithm>
#include <cmath>
using namespace std;
#define db long double
#define eps 1e-9
const int M = 100005;
int read()
{
	int x=0,f=1;char c;
	while((c=getchar())<'0' || c>'9') {if(c=='-') f=-1;}
	while(c>='0' && c<='9') {x=(x<<3)+(x<<1)+(c^48);c=getchar();}
	return x*f;
}
int n,m;db ans,a[M],b[M];
struct point
{
	int x,y;db k,k2;
	point(int X=0,int Y=0) : x(X) , y(Y) {k=atan2(y,x);k2=0;}
	point operator + (const point &r) const {return point(x+r.x,y+r.y);}
	point operator - (const point &r) const {return point(x-r.x,y-r.y);}
	int operator * (const point &r) const {return x*r.y-y*r.x;}
	bool operator < (const point &r) const
	{
		return k<r.k;
	}
}p[M],h[M];
int sig(db x)
{
	if(x>eps) return 1;
	if(x<-eps) return -1;
	return 0;
}
signed main()
{
	freopen("constellation.in","r",stdin);
	freopen("constellation.out","w",stdout);
	n=read();
	for(int i=1;i<=n;i++)
	{
		p[i].x=read();p[i].y=read();
		a[i]=read();a[i]/=1000;
	}
	for(int i=1;i<=n;i++)
	{
		m=0;
		for(int j=1;j<=n;j++)
			if(i!=j)
			{
				h[m++]=p[j]-p[i];
				h[m-1].k2=1-a[j];
			}
		sort(h,h+m);
		int k=0,z=0;db w=a[i];
		for(int j=0;j<m;j++)
			h[j+m]=h[j];
		for(int j=0;j<m;j++)
			if(h[0]*h[j]>=0) k=j;
		for(int j=k+1;j<m;j++)
		{
			if(sig(h[j].k2)==0) z++;
			else w*=h[j].k2;
		}
		for(int j=m;j<2*m;j++)
		{
			while(k+1<j && h[j]*h[k+1]>=0)//在左边了
			{
				k++;
				if(sig(h[k].k2)==0) z--;
				else w/=h[k].k2;
			}
			if(!z)
				ans+=p[i]*(p[i]+h[j])*w*(1-h[j].k2);
			if(sig(h[j].k2)==0) z++;
			else w*=h[j].k2;
		}
	}
	printf("%.6lf
",(double)ans/2);
}
原文地址:https://www.cnblogs.com/C202044zxy/p/14635116.html