竞赛期中考试总结及题解

总结:

时间分配不均匀+对常见套路题不熟悉+码力太弱+思维能力欠缺。
还有就是看到最优化问题就往贪心上想。

T1:

题解:

容易想到朴素 DP,将商店按离学校距离由小到大排序,定义 \(f_{i,j}\) 表示前 \(i-1\) 个商店买 \(j\) 瓶可乐且当前在 \(i\) 的最小花费。
然后方程很好得出:

\[f_{i,j}=min(f_{i-1,k}+(j-k)\times C_i+(X_i-X_{i-1})\times j^2) \]

因为要枚举 \(i,j,k\),所以时间复杂度为 \(O(nk^2)\)
可以优化。
将提出已知量,合并未知量后得到:

\[f_{i,j}=min(f_{i-1,k}+k\times C_i)+j\times C_i+(X_i-X_{i-1})\times j^2 \]

因为 \(k \leq j\),且可用范围连续上升,且可以简单维护最值,所以可以用单调队列维护 \(min(f_{i-1,k}+k\times C_i)\)

完毕。

代码:

展开查看

#include 
using namespace std;
const long long MAXN=505,MAXK=1e4+50;
long long f[MAXN][MAXK];
long long K,M,N;
struct Store
{
	long long X,F,C;
}s[MAXN];
long long Sum[MAXN];
bool cmp(Store x,Store y)
{
	if(x.X==y.X)
	{
		return x.Cq;
	memset(f,0x3f,sizeof(f));
	f[0][0]=0;
	for(long long i=1;i<=N;i++)
	{
		for(long long j=0;j<=min(K,Sum[i-1]);j++)
		{
			while(!q.empty()&&j-q.front()>s[i-1].F)
				q.pop_front();
			while(!q.empty()&&f[i-1][q.back()]-q.back()*s[i-1].C>f[i-1][j]-j*s[i-1].C)
				q.pop_back();
			q.push_back(j);
			f[i][j]=f[i-1][q.front()]-q.front()*s[i-1].C+j*s[i-1].C+(s[i].X-s[i-1].X)*j*j;
		}
		q.clear();
	}
	cout<
本题总结:

想到朴素 DP,也想到要优化,没想到要单调队列优化,去考虑其它特性去了。
主要是对单调队列不熟练,第一时间没反应过来。
再有就是明明能写朴素 DP 对拍,或者骗分,但因为时间分配不当,导致没时间写。
因为这题是我放第 \(3\) 道做的。

T2:

题解:

把红球看成 \(-1\),绿球看成 \(1\),要求 \(N\)\(-1\)\(M\)\(1\) 排成序列,前缀和任何时候不能低于 \(-K\) 的可行序列数。
显然可以发现这是卡特兰数的经典问题。
如果 \(K=0\),那就是模板题。
把序列看成 \(N\)\(M\) 列的矩阵,求从 \((0,0)\) 走到 \((N,M)\) 的最短路方案数。
如果没有限制,答案就是 \(M+N\) 步任选 \(N\) 步走上,其它步走右的方案数,也就是 \(\tbinom{N}{N+M}\)
\(K=0\) 时,答案要减去不合法方案,找规律可得将每一个不合法方案第一个不合法位置及之前的走法翻转,其它不变,最后会走到 \((N-1,M+1)\) 这个点上。
所以答案为 \(\tbinom{N}{N+M}-\tbinom{M+1}{N+M}\)
但这里有 \(K\),相当于把错误判定线往上平移了,原本一些不对的序列也可以变对了。
继续找规律,可得把不合法方案按照同样的走法可以走到 \((N-K-1,M+K+1)\)
所以本题答案为 \(\tbinom{N}{N+M}-\tbinom{M+K+1}{N+M}\)
注意:可能会有无解的情况。当 \(M+K<N\) 时无解。

完毕。

代码

#include <bits/stdc++.h>
#define ll long long 
using namespace std;

const int P=1e9+7,MAXN=5e6+50;

ll Pow(ll a,ll b)
{
	ll ans=1ll;
	while(b)
	{
		if(b&1)
		ans=ans*a%P;
		a=a*a%P;
		b>>=1;
	}
	return ans;
}
ll jc[MAXN];
void Init()
{
	jc[0]=1ll;
	for(int i=1;i<=MAXN-50;i++)
	{
		jc[i]=jc[i-1]*i%P;
	}
	return;
}
ll Inv(ll a)
{
	return Pow(a,P-2);
}
ll C(ll n,ll m)
{
	if(n<m)
	return 0ll;
	if(m==0)
	return 1ll;
	if(m==1)
	return n;
	return jc[n]*Inv(jc[m])%P*Inv(jc[n-m])%P;
}
ll N,M,K; 
ll f[MAXN];
int main()
{
	Init();
	scanf("%lld%lld%lld",&N,&M,&K);
	if(N>M+K)
	{
		cout<<0;
		return 0;
	}
	cout<<(C(N+M,N)-C(N+M,M+K+1)+P)%P;
}
本题总结:

考试一眼就看出时卡特兰数,也想到转换成矩阵,没想出来走错的位置可以经过映射转换到终点为同一个点。
所以就用 DP 做的。
而且我还没判断无解的情况,对 \(10\) 分数据点没有在意,导致丢了 \(10\) 分。
这是卡特兰数的常见性质,没做出来还是因为对卡特兰数不熟练。
丢分主要是审题和分析题目性质不仔细,漏判了。

T3:

题解:

这里只写我本人做法。
因为区间很多,不考虑枚举区间,考虑枚举次大值。
\(a_i\) 为次大值时,区间最左边可以到它左边第二个比它大的数的右边,最右边可以到它右边第二个比它大的数的左边。如果左边没有第二个比它大的就把 \(L_i\) 赋值为 \(1\),右边遇到此情况同理赋值为 \(N\)
显然如果找左、右边第一个最大值可以用笛卡尔树,所以我先用笛卡尔树找到 \(l_i,r_i\)
预处理关于 \(a\) 数组的 ST 表,再对于每一个数,在 \([1,l_i-1]\) 之间二分找距离 \(l_i-1\) 最近的大于 \(a_i\) 的数,找不到就把 \(L_i\) 赋值为 \(1\),右边同理。

处理好区间问题,在区间找异或最大我有两种方法,由于第二种更好写所以我选了第二种。
\(a\) 数组分块,每一块都建一棵 trie 树,对于一个区间,如果整块都在这个区间内就在 trie 树查异或最大,否则就枚举找最大。

思路很简单,复杂度为 \(O(n\sqrt{n} \log{n})\),随机数据可以快速通过。

完毕

代码:
快读是考试结束后加的。

#include <iostream>
#include <cstdio>
#include <cmath>
#include <cstring>
long long max(long long a,long long b)
{
	return a>b?a:b;
}
static char buf[1000000],*p1=buf,*p2=buf,obuf[1000000],*p3=obuf;
#define getchar() p1==p2&&(p2=(p1=buf)+fread(buf,1,1000000,stdin),p1==p2)?EOF:*p1++
#define putchar(x) (p3-obuf<1000000)?(*p3++=x):(fwrite(obuf,p3-obuf,1,stdout),p3=obuf,*p3++=x)
template<typename item>
inline void read(register item &x)
{
    x=0;register char c=getchar();
    while(c<'0'||c>'9')c=getchar();
    while(c>='0'&&c<='9')x=(x<<3)+(x<<1)+(c^48),c=getchar();
}
static char cc[10000];
template<typename item>
inline void print(register item x)
{ 
	register long long len=0;
	while(x)cc[len++]=x%10+'0',x/=10;
	while(len--)putchar(cc[len]);
}

const long long MAXN=1e5+50,MAXNUM=32,MAXSQRT=250;
const int LOG=ceil(log2(MAXN));
int ST[MAXN][LOG];
int Log2[MAXN];
struct DIKAER
{
	int Lson,Rson;
	long long id;
}tr[MAXN];
long long h,S[MAXN];
long long Son[MAXSQRT][MAXN][2];

long long Tot[MAXN];

long long n,s,a[MAXN];

long long NewNode(long long G)
{
	if(Tot[G]==0)
		Tot[G]=1;
	Tot[G]++;
	Son[G][Tot[G]][0]=Son[G][Tot[G]][1]=0;
	return Tot[G];
}
long long Build(long long n)
{
	h=0;
	memset(S,0,sizeof(S));
	for(register long long i=1;i<=n;++i)
	{
		tr[i].Lson=tr[i].Rson=0;
		tr[i].id=i;
		while(h>0&&a[i]>a[S[h]])
		{
			tr[i].Lson=S[h];
			h--;
		}
		if(h)
		{
			tr[S[h]].Rson=i;
		}
		h++;
		S[h]=i;
	}	
	return S[1];
}
long long l[MAXN],r[MAXN],L[MAXN],R[MAXN];
void dfs(int u)
{
	l[u]=r[u]=u;
	if(tr[u].Lson)
	{
		dfs(tr[u].Lson);
		l[u]=l[tr[u].Lson];
	}
	if(tr[u].Rson)
	{
		dfs(tr[u].Rson);
		r[u]=r[tr[u].Rson];
	}
}

void Add(long long G,long long x)
{
	long long u=1;
	for(long long i=MAXNUM;i>=0;i--)
	{
		bool ch=((x>>i)&1);
		if(Son[G][u][ch]==0)
		{
			Son[G][u][ch]=NewNode(G);
		}
		u=Son[G][u][ch];
	}
	return;
}

long long Query(long long G,long long x)
{
	long long ans=0,u=1;
	for(long long i=MAXNUM;i>=0;i--)
	{
		bool ch=((x>>i)&1);
		if(Son[G][u][ch^1]!=0)
		{
			ans=ans<<1|1;
			u=Son[G][u][ch^1];
		}
		else
		{
			if(Son[G][u][ch]!=0)
			{
				ans=ans<<1;
				u=Son[G][u][ch];
			}
			else
			{
				return ans;
			}
		}
	}
	return ans;
}
long long GetMax(long long x,long long y,long long z)
{
	long long i=(x-1)/s+1;
	long long j=(y-1)/s+1;
	long long ans=-1e18;
	if(i==j)
	{
		for(long long k=x;k<=y;k++)
		{
			ans=max(ans,z^a[k]);
		}
	}
	else
	{
		for(long long k=x;k<=i*s;k++)
		{
			ans=max(ans,z^a[k]);
		}
		for(long long k=(j-1)*s+1;k<=y;k++)
		{
			ans=max(ans,z^a[k]);
		}
		for(long long k=i+1;k<j;k++)
		{
			ans=max(ans,Query(k,z));
		}
	}
	return ans;
}

int MAX(int l,int r)
{
	int t=Log2[r-l+1];
	return max(ST[r][t],ST[l+(1<<t)-1][t]);
}

int main()
{
	read(n);
	s=sqrt(n)*log(n);
	for(long long i=1;i<=n;i++)
	{
		read(a[i]);
	}
	for(int i=1,k=0,u=1;i<=n;i++)
	{
		ST[i][0]=a[i];
		if(i==(u<<1))
		u<<=1,k++;
		Log2[i]=k;
		for(int j=1;j<=k;j++)
		{
			ST[i][j]=max(ST[i][j-1],ST[i-(1<<j-1)][j-1]); 
		}
	}
	dfs(Build(n));
	for(long long i=1;i<=n;i+=s)
	{
		for(long long j=i;j<=i+s-1&&j<=n;j++)
		{
			Add((i-1)/s+1,a[j]);
		}
	}
	long long ans=0;
	for(long long i=1;i<=n;i++)
	{
		if(l[i]==1&&r[i]==n)
		continue;
	
		int ll=1,rr=l[i]-2;
		if(MAX(ll,rr)<a[i])
		{
			L[i]=1;
		}
		else
		{
			while(ll<=rr)
			{
				int Mid=ll+rr>>1;
				if(MAX(Mid,rr)>a[i])
				{
					L[i]=Mid;
					ll=Mid+1;
				}
				else
				{
					rr=Mid-1;
				}
			}
		}
		ll=r[i]+2,rr=n;
		if(MAX(ll,rr)<a[i])
		{
			R[i]=n;
		}
		else
		{
			while(ll<=rr)
			{
				int Mid=ll+rr>>1;
				if(MAX(ll,Mid)>a[i])
				{
					R[i]=Mid;
					rr=Mid-1;
				}
				else
				{
					ll=Mid+1;
				}
			}
		}
		L[i]=L[i]+1;
		R[i]=R[i]-1;
		ans=max(ans,GetMax(L[i],R[i],a[i]));
	}
	printf("%lld",ans);
	fwrite(obuf,p3-obuf,1,stdout);
}
本题总结:

考场就想到这个正解了,虽然比另外一个方法好写但依旧很难写,导致写了特别久还写挂了。写得太久,写完离考试结束还有 \(1\) 个小时,我才只写了这一题的代码,所以没写对拍,导致出错。
但如果我写了对拍,检查出错误,当时也调不出。考试完之后我花了 \(2\) 个小时才调过。

所以我代码能力太弱了,遇到简单数据结构题都会写挂。可能是因为我比较少写笛卡尔树,因为最后错误也出在笛卡尔树上。

T4:

题解:

先有几个很显然但还是很难想的结论:

  • 一定是最后一次经过第 \(i\) 个幼儿园才把糖卖给第 \(i\) 个幼儿园。
  • 如果要经过一个幼儿园,经过后身上携带的糖果数不低于 \(A_i-B_i\),令 \(C_i \gets A_i-B_i\)
  • 如果当前可以到 \(2\) 个幼儿园,优先去 \(C_i\) 更大的一方卖糖。

通过最后一个结论,可以得出整张图可以化为一棵树,因为一个点向外走的走法是确定的。
如何将图化为一棵树使得满足每个点该有的先后顺序不变。
最大的最上,最小的是叶子,先走大再走小。
可以从下往上建树。
\(C_i\) 从小到大排序,对于一个点 \(i\),一次连接与它相邻的且比它 \(C_i\) 值小的且之前与它不在同一个联通块的点。
并查集维护联通。
这样显然形成了一棵树。
\(f_u\) 表示处理完以 \(u\) 为根的子树且从其中一个儿子处结束不返回的最小答案。
首先考虑叶子节点,\(f_u\) 表示处理这一个点的答案,答案为 \(\max(A_u,B_u)\)
对于非叶子节点,且它的儿子的 \(f\) 值都算好了。
枚举一个儿子 \(v\) 为不返回的子树,其它儿子都得回到 \(u\) 节点。
因为树是按照 \(C_i\) 从下到上以此递减来构造的,所以 \(C_u \geq C_v\),所以如果不考虑当前枚举到的子树,其它要返回的子树加上 \(u\) 节点的最小答案为 \(SumB_u-SumB_v+C_u\)
\(v\) 节点需要 \(f_v\) 的花费,而到 \(v\) 只有 \(C_u\) 棵糖。所以如果 \(f_v > C_u\),则节点的最小答案为 \(SumB_u-SumB_v+f_v\)
综上:

\[f_u=\min(SumB_u-SumB_v+\max(f_v,C_u)) \]

完毕。

代码:

#include <bits/stdc++.h>

using namespace std;
const long long MAXN=1e6+50;
struct Dot
{
	long long A,B,C,id;
}d[MAXN];
struct Edge
{
	long long x,y,Next;
}e[MAXN<<2],tr[MAXN<<2];
long long elast[MAXN],elasttr[MAXN];
long long tot=0,tottr=0;
void Add(long long x,long long y)
{
	tot++;
	e[tot].x=x;
	e[tot].y=y;
	e[tot].Next=elast[x];
	elast[x]=tot;
	return;
}
void Addtr(long long x,long long y)
{
	tottr++;
	tr[tottr].x=x;
	tr[tottr].y=y;
	tr[tottr].Next=elasttr[x];
	elasttr[x]=tottr;
	return;
}
long long N,M;
long long father[MAXN];
long long getfather(long long x)
{
	if(x!=father[x])
	father[x]=getfather(father[x]);
	return father[x];
}
bool cmp(Dot x,Dot y)
{
	return x.C<y.C;
}
long long C[MAXN];
long long SumB[MAXN],f[MAXN];
long long A[MAXN],B[MAXN];
void dfs(long long u,long long fa)
{ 
	SumB[u]=B[u];
	for(long long i=elasttr[u];i;i=tr[i].Next)
	{
		long long v=tr[i].y;
		if(v==fa)
		continue;
		
		dfs(v,u);
		SumB[u]+=SumB[v];
	}
	f[u]=1e18;
	for(long long i=elasttr[u];i;i=tr[i].Next)
	{
		long long v=tr[i].y;
		if(v==fa)
		continue;
		f[u]=min(f[u],max(C[u],f[v])+SumB[u]-SumB[v]);
	}
	if(f[u]==1e18)
	{
		f[u]=max(A[u],B[u]);
	}
}
bool vis[MAXN];
int main()
{
	scanf("%lld%lld",&N,&M);
	for(long long i=1;i<=N;i++)
	{
		father[i]=i;
		scanf("%lld%lld",&d[i].A,&d[i].B);
		B[i]=d[i].B;
		A[i]=d[i].A;
		d[i].C=d[i].A-d[i].B;
		d[i].id=i;
		C[i]=d[i].C;
	}
	for(long long i=1;i<=M;i++)
	{
		long long x,y;
		scanf("%lld%lld",&x,&y);
		Add(x,y);
		Add(y,x); 
	}
	sort(d+1,d+N+1,cmp);
	for(long long i=1;i<=N;i++)
	{
		long long u=d[i].id;
		vis[u]=1;
		for(long long j=elast[u];j;j=e[j].Next)
		{
			long long v=e[j].y;
			if(vis[v])
			{
				long long fx=getfather(v),fy=getfather(u);
				if(fx!=fy)
				{
					father[fx]=fy;
					Addtr(fx,fy);
					Addtr(fy,fx);
				}
			}
		}
	}
	dfs(getfather(1),0);
	cout<<f[getfather(1)];
}
本题总结:

没有思路,思维能力欠缺。

初三目标:

由于很多知识欠缺加上学过知识掌握不牢固,所以目标是
把知识范围内的东西做到

  • 能自己写出来保证不出错(指数据结构)
  • 遇到问题能想到正确方法
  • 想到大概题目做法能好好用代码写出来

另外要自己补完之前不会的基础知识。
在此之上再考虑自学新内容。

对于考试:
较简单的题能拿满分(至少一道),较难的题尽量拿分。

posted @ 2021-11-08 21:17  0htoAi  阅读(97)  评论(0编辑  收藏  举报