2020/11/24 NOIp2020模拟赛

Preface

T4>T1>T2>T3

不发大样例是真的吃*,T3猜了个结论就硬顶过去了

T1和T4好像之间都见过类似的,T2SB题,因为陈指导T1炸到95了就终于得了一次Rank1

题面懒得写了,直接看这里


玩具

套路地从大到小枚举一个阈值\(x\),首先将所有宝物个数超过\(x\)的居民的最小的宝物都取走,容易发现这部分需要被永久取走

记录下此时要取走的总和以及个数\(y\),若\(y\le x\)则需要在剩下的所有未取走的宝物中取前\(x+1-y\)小的和

前面的排个序开个vector显然随便维护,后面的离散化之后在权值线段树上二分即可

#include<cstdio>
#include<vector>
#include<algorithm>
#include<iostream>
#define RI register int
#define CI const int&
using namespace std;
typedef vector <int>:: iterator VI;
const int N=100005;
int n,m,x,y,mx,ret,cur,a[N],rst[N]; vector <int> c[N],p[N]; long long ans=1e18,sum;
inline bool cmp(CI x,CI y) { return x>y; }
class Segment_Tree
{
	private:
		int num[N<<2]; long long sum[N<<2];
	public:
		#define TN CI now=1,CI l=1,CI r=cur
		#define LS now<<1,l,mid
		#define RS now<<1|1,mid+1,r
		inline void modify(CI pos,CI mv,TN)
		{
			num[now]+=mv; sum[now]+=mv*rst[pos]; if (l==r) return; int mid=l+r>>1;
			if (pos<=mid) modify(pos,mv,LS); else modify(pos,mv,RS);
		}
		inline long long query(CI k,TN)
		{
			if (l==r) return 1LL*k*rst[l]; int mid=l+r>>1;
			if (num[now<<1]>=k) return query(k,LS);
			else return sum[now<<1]+query(k-num[now<<1],RS);
		}
		#undef TN
		#undef LS
		#undef R
}SEG;
#define trs(x) (lower_bound(rst+1,rst+cur+1,x)-rst)
int main()
{
	freopen("toy.in","r",stdin); freopen("toy.out","w",stdout);
	RI i; for (scanf("%d%d",&n,&m),i=1;i<=m;++i)
	scanf("%d%d",&x,&y),c[y].push_back(x),a[i]=rst[i]=x;
	sort(rst+1,rst+m+1); cur=unique(rst+1,rst+m+1)-rst-1;
	for (i=1;i<=m;++i) SEG.modify(trs(a[i]),1);
	for (i=1;i<=n;++i) mx=max(mx,(int)c[i].size()),p[c[i].size()].push_back(i);
	for (i=1;i<=n;++i) sort(c[i].begin(),c[i].end(),cmp);
	for (i=mx;i;--i)
	{
		ans=min(ans,sum+(ret>i?0:SEG.query(i+1-ret)));
		for (VI it=p[i].begin();it!=p[i].end();++it)
		++ret,sum+=c[*it].back(),SEG.modify(trs(c[*it].back()),-1),
		i!=1&&(p[i-1].push_back(*it),0),c[*it].pop_back();
	}
	return printf("%lld",ans),0;
}

容易发现两棵树之内的答案不变,设为\(s1,s2\),假设我们在两个树中取的点到每个点的距离之和为\(d1,d2\),两棵树的点数为\(c1,c2\),最后的答案显然为:

\[d1\times c2+d2\times c1+c1*c2+s1+s2 \]

考虑只要分别最小化\(d1,d2\)即可,这显然就是一个换根DP的SB题

#include<cstdio>
#include<iostream>
#define RI register int
#define CI const int&
using namespace std;
const int N=100005;
const long long INF=1e18;
struct edge
{
	int to,nxt;
}e[N<<1]; int n,head[N],cnt,x,y,size[N],c1,c2;
bool vis[N]; long long sum[N],dsum[N],s1,s2,d1=INF,d2=INF;
inline void addedge(CI x,CI y)
{
	e[++cnt]=(edge){y,head[x]}; head[x]=cnt;
	e[++cnt]=(edge){x,head[y]}; head[y]=cnt;
}
#define to e[i].to 
inline void DFS1(CI now,CI fa=0)
{
	vis[now]=size[now]=1; for (RI i=head[now];i;i=e[i].nxt) if (to!=fa) 
	DFS1(to,now),size[now]+=size[to],sum[now]+=sum[to]+size[to];
}
inline void DFS2(CI now,CI c,long long& d,long long& s,CI fa=0)
{
	d=min(d,dsum[now]+sum[now]); s+=dsum[now]+sum[now]; for (RI i=head[now];i;i=e[i].nxt)
	if (to!=fa) dsum[to]=dsum[now]+sum[now]-sum[to]-size[to]+c-size[to],DFS2(to,c,d,s,now);
}
#undef to
int main()
{
	freopen("tree.in","r",stdin); freopen("tree.out","w",stdout);
	RI i; for (scanf("%d",&n),i=1;i<=n-2;++i) scanf("%d%d",&x,&y),addedge(x,y);
	DFS1(1); DFS2(1,c1=size[1],d1,s1);
	for (i=1;i<=n;++i) if (!vis[i]) DFS1(i),DFS2(i,c2=size[i],d2,s2);
	return printf("%lld",d1*c2+d2*c1+1LL*c1*c2+(s1+s2)/2LL),0;
}

七巧板

容易发现第一刀最多切到\(6\)块,下面的每一刀最多在于之前的平行的位置略作旋转多切到一块

就是一个等差数列求和,可以\(O(1)\)计算

#include<cstdio>
#define RI register int
#define CI const int&
using namespace std;
int n;
int main()
{
	freopen("tangram.in","r",stdin); freopen("tangram.out","w",stdout);
	return scanf("%d",&n),printf("%lld",1LL*(11+n)*n/2LL+7LL),0;
}

星际战争

容易发现我们可以强制Alice先走,这样显然不会影响答案

考虑把以\(1\)为起点的最短路树求出来,考虑我们枚举Alice的基地\(x\)

若不考虑非树边,设\(y\)\((1,x)\)路径的中点(偏向\(x\)),Alice能占领的显然是\(y\)的子树

现在我们考虑有非树边之后,Alice可能会通过非树边来“偷袭”到其它的点

观察数据范围我们发现非树边只有\(100\)条左右,因此我们可以把所有非树边的端点每次都拿来暴力判断一下能不能从这里偷到上面的点,这显然可以通过预处理最短路实现

考虑现在的问题等价于在一棵树上有某些点, 需要求出所有点的子树的大小之和(要去重)

把一个子树看做DFS序上的区间,问题变为区间覆盖问题,可以按左端点排序后记录最远的右端点来贪心

总复杂度\(O(100\times n\log n)\),实现上常数略有点紧

#include<cstdio>
#include<cstring>
#include<algorithm>
#define RI register int
#define CI const int&
using namespace std;
const int N=100105;
struct edge
{
	int to,nxt;
}; int n,m,x,y,kp[N],tot,idx,dis[205][N],dep[N],L[N],R[N],cnt,ans=1e9;
struct interval
{
	int l,r;
	inline interval(CI L=0,CI R=0) { l=L; r=R; }
	friend inline bool operator < (const interval& A,const interval& B)
	{
		return A.l!=B.l?A.l<B.l:A.r<B.r;
	}
}t[N];
#define to e[i].to
namespace T //Tree
{
	const int P=18;
	edge e[N<<1]; int head[N],cnt,anc[N][P];
	inline void addedge(CI x,CI y)
	{
		e[++cnt]=(edge){y,head[x]}; head[x]=cnt;
		e[++cnt]=(edge){x,head[y]}; head[y]=cnt;
	}
	inline void DFS(CI now=1,CI fa=0)
	{
		RI i; L[now]=++idx; dep[now]=dep[fa]+1; anc[now][0]=fa;
		for (i=0;i<P-1;++i) if (anc[now][i])
		anc[now][i+1]=anc[anc[now][i]][i]; else break;
		for (i=head[now];i;i=e[i].nxt) if (to!=fa) DFS(to,now); R[now]=idx;
	}
	inline int jump(int x,CI y)
	{
		for (RI i=0;i<P;++i) if ((y>>i)&1) x=anc[x][i]; return x;
	}
};
namespace G //Graph
{
	edge e[N<<1]; int head[N],cnt,q[N],pre[N]; bool vis[N];
	inline void addedge(CI x,CI y)
	{
		e[++cnt]=(edge){y,head[x]}; head[x]=cnt;
		e[++cnt]=(edge){x,head[y]}; head[y]=cnt;
	}
	inline void build(void)
	{
		RI H=0,T=1,i; q[1]=vis[1]=1; while (H<T)
		{
			int now=q[++H]; for (i=head[now];i;i=e[i].nxt)
			if (!vis[to]) T::addedge(now,to),vis[to]=1,pre[to]=now,q[++T]=to;
			else if (to!=pre[now]) kp[++tot]=now,kp[++tot]=to;
		}
	}
	inline void BFS(int *dist,CI st)
	{
		RI H=0,T=1,i; memset(vis,0,sizeof(vis)); vis[q[1]=st]=1; dist[st]=0; while (H<T)
		{
			int now=q[++H]; for (i=head[now];i;i=e[i].nxt)
			if (!vis[to]) vis[to]=1,dist[to]=dist[now]+1,q[++T]=to;
		}
	}
};
#undef to
inline int calc(int ret=0)
{
	sort(t+1,t+cnt+1); int mr=0; for (RI i=1;i<=cnt;++i)
	{
		if (t[i].l<=mr) { if (t[i].r>mr) ret+=t[i].r-mr; }
		else ret+=t[i].r-t[i].l+1; mr=max(mr,t[i].r);
	}
	return ret;
}
int main()
{
	freopen("starwar.in","r",stdin); freopen("starwar.out","w",stdout);
	RI i,j; for (scanf("%d%d",&n,&m),i=1;i<=m;++i) scanf("%d%d",&x,&y),G::addedge(x,y);
	G::build(); T::DFS(); sort(kp+1,kp+tot+1); tot=unique(kp+1,kp+tot+1)-kp-1;
	for (i=1;i<=tot;++i) G::BFS(dis[i],kp[i]); for (i=2;i<=n;++i)
	{
		int tp=T::jump(i,dep[i]-dep[1]>>1); t[cnt=1]=interval(L[tp],R[tp]);
		for (j=1;j<=tot;++j) if (dis[j][i]<=dis[j][1])
		tp=T::jump(kp[j],dis[j][1]-dis[j][i]>>1),t[++cnt]=interval(L[tp],R[tp]);
		ans=min(ans,n-calc());
	}
	return printf("%d",ans),0;
}

Postscript

这么水的比赛都要水博客被陈指导嘲讽了

posted @ 2020-11-26 21:13  空気力学の詩  阅读(260)  评论(0编辑  收藏  举报