2019/8/22 校内模拟赛 考试报告

A.地鼠游戏(\(mouse.cpp\))

题目链接(CodeVS1052)

标准贪心,我的做法是将时间线翻转,这样就变成了"过一会就有一个地鼠冒出来",用优先队列维护已经冒出来的地鼠中分值的最大值.

#include<cstdio>
#include<queue>
#include<algorithm>
using namespace std;
const int SIZE=100005;
int n,Las=1,Ans;

struct comb
{
	int Tim,v;
	bool operator <(const comb &x)const
	{
		return v<x.v;
	}
}C[SIZE];
priority_queue<comb>q;

bool cmp(comb A,comb B)
{
	return A.Tim<B.Tim;
}

int main()
{
	freopen("mouse.in","r",stdin);
	freopen("mouse.out","w",stdout);
	scanf("%d",&n);
	for(int i=1;i<=n;i++)
	{
		scanf("%d",&C[i].Tim);
		C[i].Tim=50000-C[i].Tim+1;
	}
	for(int i=1;i<=n;i++)scanf("%d",&C[i].v);
	sort(C+1,C+1+n,cmp);
	for(int nowT=1;nowT<=50000;nowT++)
	{
		while(Las<=n&&C[Las].Tim<=nowT)q.push(C[Las++]);
		if(q.size()){Ans+=q.top().v;q.pop();}
	}
	printf("%d",Ans);
	return 0;
} 

B.[HEOI2015]兔子与樱花(\(sakura.cpp\))

题目链接

题意:给定一颗有根树,每个节点有一个权值,将删除点\(x\)定义为把\(x\)的权值累加到\(x\)的父亲上,把\(x\)的所有儿子接到\(x\)的父亲上.求满足所有点的点权不超过常数\(m\)时最多可以删除多少个点.

经验贪心.我在考场上的想法是深搜,自底向上更新,能删则删,因为删除一个点带来的最坏影响仅仅是使得它的父节点不能被删,无论怎样答案不会更劣.

但是这个做法还涉及到\(DFS\)顺序的问题,当一个点有多个儿子时,应该优先删除权值小的.这样就要调整\(DFS\)的写法,用优先队列或者其它数据结构来维护儿子中最小的权值.

#include<cstdio>
#include<queue>
using namespace std;
const int SIZE=4000005;
int n,m,head[SIZE],nex[SIZE],ve[SIZE],F[SIZE],C[SIZE],Tot,k,Ans,x;
void Link(int u,int v)
{
	nex[++Tot]=head[u];head[u]=Tot;ve[Tot]=v;
	nex[++Tot]=head[v];head[v]=Tot;ve[Tot]=u;
}
struct node
{
	int v,Cx;
	bool operator <(const node &x)const{return Cx>x.Cx;}
};
void DFS(int u)
{
	priority_queue<node>q;
	while(q.size())q.pop();
	for(int i=head[u];i;i=nex[i])
		if(ve[i]!=F[u])
		{
			DFS(ve[i]);
			q.push((node){ve[i],C[ve[i]]});
		}
	while(q.size())
	{
		if(C[u]+C[q.top().v]-1<=m)
			{C[u]+=C[q.top().v]-1;++Ans;q.pop();}
		else break;
	}
}

int main()
{
	scanf("%d%d",&n,&m);
	for(int i=1;i<=n;i++)scanf("%d",&C[i]);
	for(int i=1;i<=n;i++)
	{
		scanf("%d",&k);
		C[i]+=k;
		while(k--){scanf("%d",&x);F[x+1]=i;Link(i,x+1);}
	}
	DFS(1);
	printf("%d\n",Ans);
	return 0;
}

C.MET-Meteors(\(meteors.cpp\))

题目链接

SP10264

整体二分,二分答案,将答案属于\([L,Mid]\)的国家放入左区间,将答案属于\([Mid+1,R]\)的国家放入右区间,递归处理.

在二分的细节上,直接模拟第\([L,Mid]\)次陨石事件,统计结果时,需要"区间修改,查询部分不连续的单点的和",我差分后直接采用了暴力\(O(m)\)统计的方法,因为我觉得如果用其它数据结构时间复杂度也很高,还不如直接暴力.但我还是太菜了,数据结构虽然看起来复杂度高,但是可以证明复杂度是对的.而本题数据范围较大,极限情况超过\(O(n^2)\)的暴力复杂度无法接受😔.只能采用树状数组维护差分前缀和,区间修改,多次单点查询的方式来达到上述效果.

这也再次警示,在类似的分治算法中,每一次处理区间的复杂度必须只与区间长度相关,否则复杂度就有问题😿.

#include<cstdio>
#define LL long long
const int SIZE=300005;
int n,m,k,o[SIZE],Lx[SIZE],Rx[SIZE],A[SIZE],Ans[SIZE],head[SIZE],nex[SIZE],pos[SIZE],Tot;
void Ins(int u,int v){nex[++Tot]=head[u];head[u]=Tot;pos[Tot]=v;}
struct oo{int P,ID;}x[SIZE],Tem[SIZE];

LL C[SIZE];
void change(int i,int x){for(;i<=m;i+=i&(-i))C[i]+=x;}
LL sum(int i){LL x=0;for(;i;i-=i&(-i))x+=C[i];return x;}

void Do(int H,int T,int L,int R)
{
	if(H>T)return;
	if(L==R)
	{
		for(int i=H;i<=T;i++)
			Ans[x[i].ID]=L;
		return;
	}
	int nL=H,nR=T,Mid=(L+R)>>1;
	for(int i=L;i<=Mid;i++)
	{
		if(Lx[i]<=Rx[i])
		{
			change(Lx[i],A[i]);
			change(Rx[i]+1,-A[i]);
		}
		else
		{
			change(1,A[i]);
			change(Rx[i]+1,-A[i]);
			change(Lx[i],A[i]);
		}
	}
	for(int i=H;i<=T;i++)
	{
		LL Gain=0;
		for(int k=head[x[i].ID];k;k=nex[k])
		{
			Gain+=sum(pos[k]);
			if(Gain>=x[i].P)break;//防止超过LL 
		}
		if(Gain<x[i].P){x[i].P-=Gain;Tem[nR--]=x[i];}//不够
		else Tem[nL++]=x[i];//多了 
	}
	for(int i=L;i<=Mid;i++)
	{
		if(Lx[i]<=Rx[i])
		{
			change(Lx[i],-A[i]);
			change(Rx[i]+1,A[i]);
		}
		else
		{
			change(1,-A[i]);
			change(Rx[i]+1,A[i]);
			change(Lx[i],-A[i]);
		}
	}
	for(int i=H;i<=T;i++)x[i]=Tem[i];
	Do(H,nL-1,L,Mid);
	Do(nR+1,T,Mid+1,R);
}

int main()
{
	scanf("%d%d",&n,&m);
	for(int i=1;i<=m;i++)
	{
		scanf("%d",&o[i]);
		Ins(o[i],i);
	}
	for(int i=1;i<=n;i++){scanf("%d",&x[i].P);x[i].ID=i;}
	scanf("%d",&k);
	for(int i=1;i<=k;i++)scanf("%d%d%d",&Lx[i],&Rx[i],&A[i]);
	Do(1,n,1,k+1);
	for(int i=1;i<=n;i++)
	{
		if(Ans[i]==k+1)puts("NIE");
		else printf("%d\n",Ans[i]);
	}
	return 0;
}

附:原来的暴力代码

void Do(int H,int T,int L,int R)
{
	if(H>T)return;
	if(L==R)
	{
		for(int i=H;i<=T;i++)
			Ans[x[i].ID]=L;
		return;
	}
	int nL=H,nR=T,Mid=(L+R)>>1;
	LL now=0;
	for(int i=1;i<=m;i++)D[i]=0;
	for(int i=L;i<=Mid;i++)
	{
		if(Lx[i]<=Rx[i]){D[Lx[i]]+=A[i];D[Rx[i]+1]-=A[i];}
		else{D[1]+=A[i];D[Rx[i]+1]-=A[i];D[Lx[i]]+=A[i];}
	}
	for(int i=1;i<=n;i++)Gain[i]=0;
	for(int i=1;i<=m;i++){now+=D[i];Gain[o[i]]+=now;}
	for(int i=H;i<=T;i++)
	{
		if(Gain[x[i].ID]<x[i].P){x[i].P-=Gain[x[i].ID];Tem[nR--]=x[i];}//²»¹»
		else Tem[nL++]=x[i];//¶àÁË 
	}
	for(int i=H;i<=T;i++)x[i]=Tem[i];
	Do(H,nL-1,L,Mid);
	Do(nR+1,T,Mid+1,R);
}
posted @ 2019-08-22 16:15  TaylorSwift13  阅读(186)  评论(0编辑  收藏  举报