模拟37 考试总结

犹恐相逢是梦中.

image

考试经过

考前状态极度低迷,前面两套题基本没动,甚至想过实在状态差就不开题了
然而被迫开题了,一看T1认为很可做,然后打一半发现忘了扩展\(gcd\),于是考场现推无果,最后一度要崩溃。。。努力平复心情之后冲暴力,结果挂的挂炸的炸,出题人部分分没给全,四个小时25\(pts\),基本垫底
不知道该说什么了,一旦这样就几乎一蹶不振,是因为我真的弱吗?
也许不去想结果就不会有遗憾吧,心中压抑的执念,还是 太深了啊...
记得宝玉老师的那句话:想赢的人不一定能赢,不怕输的才会。
把状态拾起来,一切都会过去的,对吗?
对的。
我永远相信的。

T1.数列

会发现其实是让解一个\(ax+by=c\)的方程,所以扩展欧几里德
以前一直以为这玩意只能解同余方程,事实上啥都能解,按公式套就行
发现让最小化\(abs(x)+abs(y)\),于是琢磨啥时候有最小值
由于可以让\(x\),\(y\)同时靠近或远离(一增一减,通解公式),所以只要让\(x\)在正负分别绝对值最小时取两次答案再取\(min\)就行了
更严谨应该在y也分别取两次答案取最小,不过只要保证\(x\)大于\(y\)就行了,如果漏掉会\(WA\)成80的高分
会发现这是一个单峰函数,所以三分也行

#include <bits/stdc++.h>
using namespace std;
#define int long long
const int N=100050;
int p[N],aa,bb;
inline int exgcd(int a,int b,int &xx,int &yy)
{
	if(b==0){xx=1;yy=0;return a;}
	int gcd=exgcd(b,a%b,xx,yy);
	int tmp=xx;xx=yy;yy=tmp-yy*(a/b);
	return gcd;
}
inline int solve(int m)
{
	int x,y,gcd=exgcd(aa,bb,x,y);
	if(m%gcd){puts("-1");exit(0);}
	x*=m/gcd,y*=m/gcd;
	int t1=bb/gcd,t2=aa/gcd,k=0;
	int ans=1e10;
	if(x>0)
	{
		k=x/t1;x%=t1;y+=k*t2;ans=min(ans,abs(x)+abs(y));
		ans=min(ans,abs(x-t1)+abs(y+t2));
	}
	if(x<0)
	{
		k=x/-t1;x=x%t1;y-=k*t2;ans=min(ans,abs(x)+abs(y));
		ans=min(ans,abs(x+t1)+abs(y-t2));
	}
	if(x==0)
	{
		ans=min(ans,abs(x)+abs(y));
		ans=min(ans,abs(x+t1)+abs(y-t2));
		ans=min(ans,abs(x-t1)+abs(y+t2));
	}
	return ans;
}
signed main()
{
	int n;cin>>n>>aa>>bb;int ans=0;
	if(aa>bb)swap(aa,bb);
	for(int i=1;i<=n;i++)scanf("%lld",&p[i]);
	for(int i=1;i<=n;i++)ans+=solve(p[i]);
	cout<<ans;
	return 0;
}

T2.数对

其实是原题,唯一不同的是可以有顺序,发现\(a\),\(b\)都大的在前面肯定不优,所以按\(a+b\)排序就行了,然后dp除了符号反一下,加了个u权值之外都没变
本着对自己负责的态度又推了个方程
\(f_{i,j}\)表示当前处理到第\(i\)个数对,最大的\(A\)\(j\)的最大收益

\[f_{i,A_i}=max(f_{i-1,k(1<=k<=B_i)})+w_i \;[A_i>B_i] \]

\[f_{i,A_i}=max(f_{i-1,k(1<=k<=A_i)}+w_i),f_{i,j(A_i<=j<=B_i)}=f_{i-1,j}+w_i\;[A_i<=B_i] \]

然后线段树转移就行,注意边界和一些细节,还有原值继承的问题



#include <bits/stdc++.h>
using namespace std;
#define int long long
const int N=200050;
struct node{
  int a,b,w;
}p[N];
inline bool cmp(node x,node y)
{
	return x.a+x.b<y.a+y.b;
}
struct tree{
  int l,r,ma,lazy;
}tr[4*N];
inline void luo(int id,int l,int r)
{
	if(tr[id].lazy&&l!=r)
	{
		tr[id*2].ma+=tr[id].lazy;
		tr[id*2+1].ma+=tr[id].lazy;
		tr[id*2].lazy+=tr[id].lazy;
		tr[id*2+1].lazy+=tr[id].lazy;
		tr[id].lazy=0;
	}
}
inline void change(int id,int l,int r,int p,int v)
{
	if(l==r){tr[id].ma=v;return;}
	luo(id,l,r);int mid=(l+r)>>1;
	if(p<=mid)change(id*2,l,mid,p,v);
	else change(id*2+1,mid+1,r,p,v);
	tr[id].ma=max(tr[id*2].ma,tr[id*2+1].ma);
}
inline void add(int id,int ll,int rr,int l,int r,int v)
{
	if(l>r)return;
	if(l<=ll&&r>=rr)
	{
		tr[id].ma+=v;tr[id].lazy+=v;
		return;
	}
	luo(id,ll,rr);int mid=(ll+rr)>>1;
	if(r<=mid)add(id*2,ll,mid,l,r,v);
	else if(l>mid)add(id*2+1,mid+1,rr,l,r,v);
	else add(id*2,ll,mid,l,mid,v),add(id*2+1,mid+1,rr,mid+1,r,v);
	tr[id].ma=max(tr[id*2].ma,tr[id*2+1].ma);
}
inline int getmax(int id,int ll,int rr,int l,int r)
{
	if(l>r)return 0;
	if(l<=ll&&r>=rr)return tr[id].ma;
	luo(id,ll,rr);int mid=(ll+rr)>>1;
	if(r<=mid)return getmax(id*2,ll,mid,l,r);
	if(l>mid)return getmax(id*2+1,mid+1,rr,l,r);
	return max(getmax(id*2,ll,mid,l,mid),getmax(id*2+1,mid+1,rr,mid+1,r));
}
int c[2*N],lsh[2*N];
signed main()
{
	int n;cin>>n;
	for(int i=1;i<=n;i++)scanf("%lld%lld%lld",&p[i].a,&p[i].b,&p[i].w);
	sort(p+1,p+n+1,cmp);
	for(int i=1;i<=n;i++)c[i]=p[i].a,c[i+n]=p[i].b;
	for(int i=1;i<=2*n;i++)lsh[i]=c[i];
	sort(lsh+1,lsh+2*n+1);
	int cnt=unique(lsh+1,lsh+2*n+1)-lsh-1;
	for(int i=1;i<=2*n;i++)c[i]=lower_bound(lsh+1,lsh+cnt+1,c[i])-lsh;
	for(int i=1;i<=n;i++)p[i].a=c[i],p[i].b=c[i+n];
	change(1,1,cnt,p[1].a,p[1].w);
	for(int i=2;i<=n;i++)
	{
		if(p[i].a>p[i].b)
		{
			int maxx=getmax(1,1,cnt,1,p[i].b);
			int num=getmax(1,1,cnt,p[i].a,p[i].a);
		   if(maxx+p[i].w>num)change(1,1,cnt,p[i].a,maxx+p[i].w);
		}
		else
		{
			int maxx=getmax(1,1,cnt,1,p[i].a);
			add(1,1,cnt,p[i].a,p[i].b,p[i].w);
			change(1,1,cnt,p[i].a,maxx+p[i].w);
		}
	}
	int ans=getmax(1,1,cnt,1,cnt);
	cout<<ans;
	return 0;
}

T3.最小距离

这个多元最短路就很神
暴力是跑\(q\)遍最短路,但是可以在开始时把所有源点都压进去,然后记录每个点是从哪个点转移过来的,相当于记录前趋吧
然后枚举每条边,如果两头的点源点不同,就可以互相更新,用到己方源点的距离+边长+到对方源点的距离更新这个源点的答案
正确性直接看题解:
image

#include <bits/stdc++.h>
using namespace std;
#define int long long
const int N=200050;
struct node{
   int from,to,next,w;
}a[2*N];
int head[N],mm=1;
inline void add(int x,int y,int w)
{
	a[mm].from=x;a[mm].to=y;a[mm].w=w;
	a[mm].next=head[x];head[x]=mm++;
}
vector <int> sp;
bool v[N];int d[N];
priority_queue <pair<int,int> >q;
int f[N],an[N];
inline void dij()
{
	memset(d,0x3f,sizeof(d));
	for(int i=0;i<sp.size();i++)
	{	
		int pp=sp[i];d[pp]=0;f[pp]=pp;
		q.push(make_pair(0,pp));
	}
	while(q.size())
	{	
		int x=q.top().second;q.pop();
		if(v[x])continue;v[x]=1;
		for(int i=head[x];i;i=a[i].next)
		{
			int y=a[i].to;if(v[y])continue;
			if(d[y]>d[x]+a[i].w)
		   {
		   	d[y]=d[x]+a[i].w;f[y]=f[x];
		   	q.push(make_pair(-d[y],y));
		   }
		}
	}
}
signed main()
{
	int n,m,p;cin>>n>>m>>p;
	for(int i=1;i<=p;i++)
	{
		int x;scanf("%lld",&x);
		sp.push_back(x);
	}
	for(int i=1;i<=m;i++)
	{
		int x,y,z;scanf("%lld%lld%lld",&x,&y,&z);
		add(x,y,z);add(y,x,z);
	}
	dij();memset(an,0x3f,sizeof(an));
	for(int i=1;i<mm;i+=2)
	{
		int x=a[i].from,y=a[i].to;
		int fx=f[x],fy=f[y];
		if(fx==fy)continue;
		an[fy]=min(an[fy],d[x]+d[y]+a[i].w);
		an[fx]=min(an[fx],d[y]+d[x]+a[i].w);
	}
	for(int i=0;i<sp.size();i++)printf("%lld ",an[sp[i]]);
	return 0;
}

这种思路很好,值得积累

T4.真相

一般经验是这种看着像模拟的题一般都能做,基本赛时拿不了分
A掉的大佬请自动无视

算法标签:枚举

只要判断是否合法,发现大部分是线性关系,即只和下一个有关,关键在第一类断言和全局有关,比较难处理,分情况讨论:
要么没有第一类断言,那么所有断言一定会成环,分别按照第一个人说真话还是说假话顺着推一边,最后能对上(一种就行)就合法,不然非法
要么有第一类断言,发现第一类断言会把整个序列分成若干块,各个块之间不影响,那么根据断言种类可以搞出来一个类似图的东西:
image
把每个判断拆成两个点,按类型往下连边,最后连出两条链,末端是一类断言,一条真链(末端断言为真),一条假链,我们处理出每条链包含正确的断言数量(即左边的点)\(s\),存进下标为末端一类断言数字的桶里
然后枚举一共几个人讲了真话,判断是否合法,方法是先处理出\(sum\)代表所有假链的\(s\)之和,每次看对应桶中的真链\(s\)减去假链\(s\)加上\(sum\)的值等不等与枚举的值,原因很简单,就看看枚举的符不符合要求就行了,最后记得清空数组

#include <bits/stdc++.h>
using namespace std;
#define int long long
#define py puts("consistent")
#define pn puts("inconsistent")
const int N=1000501;
struct saying{int t,f;}s[N];
int a[N],b[N],n;char c[3];
int sf[N],st[N];bitset <N> v;
stack <int> ss;
inline void clear()
{
	while(ss.size())
	  {int pp=ss.top();s[pp].t=s[pp].f=0;ss.pop();}
	for(int i=0;i<=n;i++)a[i]=st[i]=sf[i]=b[i]=0;
	for(int i=0;i<=n;i++)v.reset(i);
}
signed main()
{
	int t;cin>>t;
	while(t--)
	{
		clear();scanf("%lld",&n);bool flag=0;
		for(int i=0;i<n;i++)
		{
			scanf("%s",c+1);
			if(c[1]=='$')scanf("%lld",&a[i]),flag=1;
			if(c[1]=='+')a[i]=-2;if(c[1]=='-')a[i]=-1;
		}
		if(!flag)
		{
			bool p=0;
			for(int i=0;i<n;i++)
			{	
				if(p&&a[i]==-2)p=1;else if(p&&a[i]==-1)p=0;
				else if(p==0&&a[i]==-2)p=0;else if(p==0&&a[i]==-1)p=1;
			}		
			if(!p){py;continue;}p=1;
			for(int i=0;i<n;i++)
			{
				if(p&&a[i]==-2)p=1;else if(p&&a[i]==-1)p=0;
				else if(p==0&&a[i]==-2)p=0;else if(p==0&&a[i]==-1)p=1;
			}	
			if(p)py;else pn;
		}
		else 
		{
			int k=0,nn=0;while(a[k]<0)k++;k=(k+1)%n;
			for(int i=k;!v[i];i=(i+1)%n)b[++nn]=a[i],v[i]=1;
			for(int i=1;i<=n;i++)
			{
				if(b[i]==-2)st[i+1]=st[i]+1,sf[i+1]=sf[i];
				if(b[i]==-1)st[i+1]=sf[i],sf[i+1]=st[i]+1;
				if(b[i]>=0)
				{
					int pp=b[i];ss.push(pp);
					s[pp].t+=st[i]+1,s[pp].f+=sf[i];
				}
			}
			int sum=0;bool ok=0;
			for(int i=0;i<=n;i++)sum+=s[i].f;
			for(int i=0;i<=n;i++)
			{
				int ans=sum-s[i].f+s[i].t;
				if(ans==i){py;ok=1;break;}
			}
			if(!ok)pn;
		}
	}
	return 0;
}

考试总结

想了很久还觉得,有强大的内心很重要,这正是我所最欠缺的,与此相匹配的是牢靠的基础知识,克服炸裂第一定律的唯一方法是:变得更强。
我们需要时间,而宁静是最好的旅伴.

posted @ 2021-08-12 21:08  D'A'T  阅读(46)  评论(0)    收藏  举报