NOIP模拟59

T1 柱状图

解题思路

二分答案+线段树check

显然对于最后的限制,我们希望向上移的和向下移的柱子数尽量接近。

因此枚举每一个柱子当做最高的一个的时刻,二分找到一个当前最优解更新答案。

开两棵线段树分别维护当前柱子左右两侧的柱子的 \(h_i+i\)\(h_i-i\) 每次查询个数用个数以及权值和判断更新即可。

需要卡常,但是我懒直接卡了一下时就走了。。

code

#include<bits/stdc++.h>
#define int long long
#define ull unsigned long long
#define f() cout<<"Failed"<<endl
#define ls tre[x].l
#define rs tre[x].r
using namespace std;
inline int read()
{
	int x=0,f=1;char ch=getchar();
	while(ch>'9'||ch<'0'){if(ch=='-')f=-1;ch=getchar();}
	while(ch>='0'&&ch<='9'){x=(x<<1)+(x<<3)+(ch^48);ch=getchar();}
	return x*f;
}
const int N=1e5+10,INF=1e9;
int n,ans=1e18,maxn,s[N];
bool check(){return (1.0*clock())/(1.0*CLOCKS_PER_SEC)<=1.49;}
struct Segment_Tree
{
	int root,all;
	Segment_Tree(){all=0;root=0;}
	struct Node
	{
		int l,r,siz,dat;
	}tre[N*30];
	void push_up(int x){tre[x].siz=tre[ls].siz+tre[rs].siz;tre[x].dat=tre[ls].dat+tre[rs].dat;}
	void insert(int &x,int l,int r,int pos,int val)
	{
        if(!x) x=++all;
		if(l==r) return tre[x].siz+=val,tre[x].dat=tre[x].siz*l,void();
		int mid=floor((1.0*l+1.0*r)/2.0);
		if(pos<=mid) insert(ls,l,mid,pos,val);
		else insert(rs,mid+1,r,pos,val);
		push_up(x);
	}
	int query_siz(int x,int l,int r,int L,int R)
	{
		if(!x) return 0;
		if(L<=l&&r<=R) return tre[x].siz;
		int mid=floor((1.0*l+1.0*r)/2.0),sum=0;
		if(L<=mid) sum+=query_siz(ls,l,mid,L,R);
		if(R>mid) sum+=query_siz(rs,mid+1,r,L,R);
		return sum;
	}
	int query_sum(int x,int l,int r,int L,int R)
	{
		if(!x) return 0;
		if(L<=l&&r<=R) return tre[x].dat;
		int mid=floor((1.0*l+1.0*r)/2.0),sum=0;
		if(L<=mid) sum+=query_sum(ls,l,mid,L,R);
		if(R>mid) sum+=query_sum(rs,mid+1,r,L,R);
		return sum;
	}
}L,R;
int solve(int x,int val)
{
	int tmp1=L.query_siz(L.root,-N,INF+N,-N,val-x-1)*(val-x)-L.query_sum(L.root,-N,INF+N,-N,val-x-1);
	int tmp2=L.query_sum(L.root,-N,INF+N,val-x+1,INF+N)-L.query_siz(L.root,-N,INF+N,val-x+1,INF+N)*(val-x);
	int tmp3=R.query_siz(R.root,-N,INF+N,-N,val+x-1)*(val+x)-R.query_sum(R.root,-N,INF+N,-N,val+x-1);
	int tmp4=R.query_sum(R.root,-N,INF+N,val+x+1,INF+N)-R.query_siz(R.root,-N,INF+N,val+x+1,INF+N)*(val+x);
    if(tmp1+tmp2+tmp3+tmp4<0) return INF*INF;
	return tmp1+tmp2+tmp3+tmp4;
}
signed main()
{
	freopen("c.in","r",stdin); freopen("c.out","w",stdout);
	n=read();
	for(int i=1;i<=n;i++) s[i]=read(),maxn=max(maxn,s[i]+i);
	for(int i=1;i<=n;i++) R.insert(R.root,-N,INF+N,s[i]+i,1);
	for(int i=1;i<=n;i++)
	{
		if(!check()) break;
		int l=max(i,n-i+1),r=maxn;
		while(l+1<r)
		{
			int mid=(l+r)>>1;
			int temp=L.query_siz(L.root,-N,INF+N,-N,mid-i-1)+R.query_siz(R.root,-N,INF+N,-N,mid+i-1);
			if(temp<=n-temp-1) l=mid;
			else r=mid;
		}
        ans=min(ans,min(solve(i,l),solve(i,r)));
		R.insert(R.root,-N,INF+N,s[i]+i,-1);
		L.insert(L.root,-N,INF+N,s[i]-i,1);
	}
    printf("%lld",ans);
	return 0;
}

T2 应急棍

大坑未补

需要高精小数,并且比较难打,咕了。

T3 擒敌拳

解题思路

李超线段树维护单调栈。

大概是个板子了吧,尽管我刚刚学会,单调栈预处理出某个高度可行的区间,在这个区间中插入一条直线其实就是插入一条线段。

然后对于标记下放的时候记录当前区间比较占优势的线段的斜率截距,也就是区间中点取值较优的一个。

优于我们既想要更新答案又想要同时保留两个值,因此就有一个 swap 的操作。

code

#include<bits/stdc++.h>
#define int long long
#define ull unsigned long long
#define f() cout<<"Failed"<<endl
#define ls x<<1
#define rs x<<1|1
using namespace std;
inline int read()
{
	int x=0,f=1;char ch=getchar();
	while(ch>'9'||ch<'0'){if(ch=='-')f=-1;ch=getchar();}
	while(ch>='0'&&ch<='9'){x=(x<<1)+(x<<3)+(ch^48);ch=getchar();}
	return x*f;
}
const int N=2e5+10,INF=1e18;
int n,top,sta[N],s[N],l[N],r[N];
struct Segment_Tree{int k,b;}tre[N<<2];
int g(int x,int k,int b){return x*k+b;}
void insert(int x,int l,int r,int k,int b)
{
	if(g(l,tre[x].k,tre[x].b)<=g(l,k,b)&&g(r,tre[x].k,tre[x].b)<=g(r,k,b)) return tre[x].k=k,tre[x].b=b,void();
	if(l==r) return;
	int mid=(l+r)>>1;
	if(g(mid,tre[x].k,tre[x].b)<=g(mid,k,b)) swap(tre[x].k,k),swap(tre[x].b,b);
	if(g(l,tre[x].k,tre[x].b)<=g(l,k,b))  insert(ls,l,mid,k,b);
	if(g(r,tre[x].k,tre[x].b)<=g(r,k,b))  insert(rs,mid+1,r,k,b);
}
void update(int x,int l,int r,int L,int R,int k,int b)
{
	if(L<=l&&r<=R) return insert(x,l,r,k,b),void();
	int mid=(l+r)>>1;
	if(L<=mid) update(ls,l,mid,L,R,k,b);
	if(R>mid) update(rs,mid+1,r,L,R,k,b);
}
int query(int x,int l,int r,int pos)
{
	if(l==r) return g(l,tre[x].k,tre[x].b);
	int mid=(l+r)>>1,dat=g(pos,tre[x].k,tre[x].b);
	if(pos<=mid) dat=max(dat,query(ls,l,mid,pos));
	else dat=max(dat,query(rs,mid+1,r,pos));
	return dat;
}
signed main()
{
	freopen("b.in","r",stdin); freopen("b.out","w",stdout);
	n=read(); for(int i=1;i<=n;i++) s[i]=read(),l[i]=r[i]=i;
	for(int i=1;i<=n;i++)
	{
		while(top&&s[i]<=s[sta[top]]) l[i]=l[sta[top--]];
		sta[++top]=i;
	}
	top=0;
	for(int i=n;i>=1;i--)
	{
		while(top&&s[i]<=s[sta[top]]) r[i]=r[sta[top--]];
		sta[++top]=i;
	}
	for(int i=1,maxn=0;i<=n;i++)
	{
		update(1,1,n,l[i],r[i],s[i],-(l[i]-1)*s[i]);
		printf("%lld ",maxn=max(maxn,query(1,1,n,i)));
	}
	return 0;
}

T4 连接(边数)

解题思路

显然是一个基环树森林, 1 节点需要向每一个树的叶子节点连边,然后每隔 k 连接一个点。

然后就是处理环的情况,可以倍增跳看全部覆盖之后是否是最优解,也可以枚举每个点在所覆盖的 k 中的位置,对于本题而言复杂度都允许。。。

code

#include<bits/stdc++.h>
#define int long long
#define ull unsigned long long
#define f() cout<<"Failed"<<endl
using namespace std;
inline int read()
{
	int x=0,f=1;char ch=getchar();
	while(ch>'9'||ch<'0'){if(ch=='-')f=-1;ch=getchar();}
	while(ch>='0'&&ch<='9'){x=(x<<1)+(x<<3)+(ch^48);ch=getchar();}
	return x*f;
}
const int N=5e5+10,INF=1e18;
int n,m,cnt,all,top,ans,tim,sta[N],du[N];
int f[N][25],s[N<<1],near[N<<1],pre[N<<1];
int tot=1,head[N],nxt[N<<1],ver[N<<1];
bool b[N],vis[N],jud[N],suc[N];
set<int> se;
void add_edge(int x,int y)
{
	ver[++tot]=y;
	nxt[tot]=head[x];
	head[x]=tot;
	du[y]++;
}
void dfs(int x)
{
	if(!b[x])
	{
		b[x]=true; if(vis[x]) return b[x]=false,void();
		for(int i=head[x];i;i=nxt[i]) dfs(ver[i]);	
		b[x]=false; vis[x]=true;
		return ;
	}
	int temp=x; jud[x]=true;
	do
	{
		temp=ver[head[temp]];
		jud[temp]=true;
	}while(temp!=x);
}
void SPJ()
{
	suc[1]=true;
	int to=ver[head[1]];
	du[to]--; suc[to]=true;
	if(se.find(1)!=se.end()) se.erase(1);
	if(se.find(to)!=se.end()) se.erase(to);
}
void topo_sort()
{
	queue<int> q;
	if(!suc[1]) SPJ(),q.push(ver[head[1]]);
	for(auto it=se.begin();it!=se.end();it++)
		if(!du[(*it)]&&!jud[(*it)]&&!suc[(*it)])
			ans++,suc[(*it)]=true,q.push((*it));
	while(!q.empty())
	{
		int x=q.front(),temp=m-1,fro=0; suc[x]=true;  q.pop();
		while(temp--)
		{
			fro=x; x=ver[head[x]]; if(!du[fro]) du[x]--; suc[x]=true;
			if(!du[fro]&&se.find(fro)!=se.end()) se.erase(fro); 
		}
		fro=x; x=ver[head[x]]; if(!du[fro]) du[x]--;
		if(!du[fro]&&se.find(fro)!=se.end()) se.erase(fro);
		if(!du[x]&&!jud[x]&&!suc[x]){q.push(x);ans++;suc[x]=true;continue;}
		while(!du[x]&&suc[x])
		{
			if(se.find(x)!=se.end()) se.erase(x);
			x=ver[head[x]]; du[x]--;
		}
	}
}
int solve(int x)
{
	int len=0,minn=INF,temp=x;
	do
	{
		temp=ver[head[temp]];
		s[++len]=temp;
	}while(temp!=x);
	for(int i=len+1;i<=2*len;i++) s[i]=s[i-len];
	near[2*len+1]=2*len+1; pre[2*len+1]=INF;
	for(int i=2*len;i>=1;i--)
		if(!suc[s[i]]) near[i]=i;
		else near[i]=near[i+1];
	for(int i=1;i<=len*2;i++) pre[i]=pre[i-1]+(suc[s[i]]^1);
	for(int i=2*len+1;i>=1;i--)
	{
		f[i][0]=near[min(i+m,len*2+1)];
		for(int j=1;j<=20;j++)
			f[i][j]=f[f[i][j-1]][j-1];
	}
	for(int i=1;i<=len;i++)
	{
		int t=0,pos=i;
		for(int j=20;j>=0;j--)
			if(f[pos][j]<=i+len) pos=f[pos][j],t+=1ll<<j;
		if(pos<=i+len-1&&pre[pos-1]!=pre[i+len]) t++;
		minn=min(minn,t);
	}
	for(int i=1;i<=len;i++) suc[s[i]]=true;
	return minn;
}
signed main()
{
	freopen("d.in","r",stdin); freopen("d.out","w",stdout);
	n=read(); m=read();
	for(int i=1,x,y;i<=n;i++) x=read(),y=read(),add_edge(x,y);
	for(int i=1;i<=n;i++) if(!vis[i]) dfs(i);
	for(int i=1;i<=n;i++) if(!jud[i]) se.insert(i);
	while(se.size()) topo_sort();
	for(int i=1;i<=n;i++) if(jud[i]&&!suc[i]) ans+=solve(i);
	printf("%lld",ans);
	return 0;
}
posted @ 2021-09-26 20:59  Varuxn  阅读(86)  评论(0编辑  收藏  举报