模拟15 考试总结

爆零了,整挺好
教训挺深的,说说吧

考试经过

T1一开觉得不太好做,然后开始推,根本推不对
T2极其不可做,发现没有裸的暴力就爬了
T3认为可以使用线段树维护左右位置,但觉得细节有亿点多,于是投身暴力,跑起大模拟
三个小时有将近一个小时是在自闭中度过的,剩下是看看T1写写T3,没思路,过不了样例,最后在迷茫中交上来两个没调出来的东西,喜提0pts
觉得主要还是心态炸了
T1想不到枚举斜率只能爆零,想到了也很难超过60,但T2爆搜还是有分的,感觉状态好点就不会把T3也挂掉,所以这场暴力其实有60,但一分都没得,其实不应该

T1.夜莺与玫瑰

这种题要有顺序的统计
正解是枚举斜率,由于左右对称,水平数值可以直接算,那只要找斜率为正且不平行于坐标轴的直线
然后出题人的解法还是不错的:
image
前驱是为了保证能构成一条直线(两点确定一条直线),后继是为了要保证他的位置被那个点唯一确定
相当于是什么呢?
image
右上角黑矩形是前驱在的,减去后继不在的,就是答案,于是我们可以有,对于每个直线方向向量\((a,b)\),贡献就是:
\((n-a)\times (n-b)-max(n-2a,0)\times max(m-2b,0)\)
那么一个一个枚举(注意gcd=1才合法)a,b,就能在\(n^2\)内回答一个询问
询问多了考虑预处理,题解直接前缀和,但这玩意有亿点难实现,于是考虑别的,战神有个好思路:

拆!

把这个括号拆开,前边一半就成了
$ n\times m -a\times m-b\times n +a\times b $
那么我们只要处理出给定一个n,m,这个矩形里有多少合法的a,b就行,就能前缀和了
后面的max分情况讨论,其实只有左上方有贡献,剩下都是0,一样的拆开前缀和
空间容易炸,不能开long long,要把询问离线,每次给所有询问都加上一项的贡献,然后清空数组循环利用,常数换空间,还要记得多取模

#include <bits/stdc++.h>
using namespace std;
//#define int long long
const int N=4005,mod=(1<<30);
int a1[N][N],a2[N][N];
inline int gcd(int x,int y)
{ 
    if(!y)return x;
    return gcd(y,x%y);
}
bitset <N> sb[N];
struct qu{
    int n,m,ans;
}b[10005];
signed main()
{
    for(int i=1;i<=4000;i++)
     for(int j=1;j<=i;j++)
      if(gcd(i,j)==1)sb[i][j]=sb[j][i]=1;
    for(int i=1;i<=4000;i++)
     for(int j=1;j<=4000;j++)
     {
       a1[i][j]=a1[i-1][j]+a1[i][j-1]-a1[i-1][j-1]+sb[i][j];
       a2[i][j]=((long long)a2[i-1][j]+a2[i][j-1]-a2[i-1][j-1]+mod+sb[i][j]*i*j%mod)%mod;
     }
    int t;cin>>t;
    for(int i=1;i<=t;i++)scanf("%d%d",&b[i].n,&b[i].m);
    for(int i=1;i<=t;i++)b[i].ans=((long long)b[i].ans+((long long)a1[b[i].n-1][b[i].m-1]-a1[b[i].n/2][b[i].m/2]+mod)%mod*b[i].m*b[i].n%mod+
	((long long)a2[b[i].n-1][b[i].m-1]-(long long)4*a2[b[i].n/2][b[i].m/2]%mod+mod)%mod)%mod;
    for(int i=1;i<=4000;i++)
     for(int j=1;j<=4000;j++)
     {
     	a1[i][j]=((long long)a1[i-1][j]+a1[i][j-1]-a1[i-1][j-1]+mod)%mod;
     	a2[i][j]=((long long)a2[i-1][j]+a2[i][j-1]-a2[i-1][j-1]+mod)%mod;
     	if(sb[i][j])a1[i][j]=((long long)a1[i][j]+i)%mod,a2[i][j]=((long long)a2[i][j]+j)%mod;
     }
     for(int i=1;i<=t;i++)b[i].ans=((long long)b[i].ans-((long long)((long long)a1[b[i].n-1][b[i].m-1]-(long long)2*a1[b[i].n/2][b[i].m/2]%mod+mod)%mod*b[i].m%mod)-
	((long long)((long long)a2[b[i].n-1][b[i].m-1]-(long long)2*a2[b[i].n/2][b[i].m/2]%mod+mod)%mod*b[i].n%mod)+2ll*mod)%mod;
     for(int i=1;i<=t;i++)b[i].ans=((long long)2*b[i].ans+b[i].m+b[i].n)%mod,printf("%d\n",b[i].ans);
     return 0;
}

不长,但确实是复杂的有点恶心了,最好预处理一个gcd,这样只有一个带log,能过

T2.影子

点分治我肯定不会,讲讲并查集吧
首先这个题想一种可以最大化的策略
贪心的想策略,先对每个点按照权值排序,然后从大到小一个一个往里加,每加一个点就维护一下最长路径,这里分情况讨论,对于两个连通块,合并之后最长距离,要末是原来的最长距离,要末是原来集合的端点互相连接而成,6种情况,每次记录当前最长路径的两个端点

#include <bits/stdc++.h>
using namespace std;
#define int long long
const int N=100050;
inline int read()
{
	int x=0;char ch=getchar();
	while(ch<'0'||ch>'9'){ch=getchar();}
	while(ch>='0'&&ch<='9'){x=(x<<1)+(x<<3)+(ch^48);ch=getchar();}
	return x;
}
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++;
}
int f[N];
struct wi{
	int v,i;
}v[N];int w[N];
inline bool com(wi x,wi y)
{
	return x.v>y.v;
}
inline int find(int x)
{
	if(f[x]!=x)f[x]=find(f[x]);
	return f[x];
}
bool vis[N];int d[N],fa[N][20],dis[N],t,la[N],lb[N],ma[N];
inline void dfs(int x)
{
	vis[x]=1;
	for(int i=head[x];i;i=a[i].next)
	{
		int y=a[i].to;
		if(vis[y])continue;
		dis[y]=dis[x]+a[i].w;
		d[y]=d[x]+1;
		fa[y][0]=x;
		for(int j=1;j<=t;j++)
		 fa[y][j]=fa[fa[y][j-1]][j-1];
		dfs(y);
	}
}
inline int getlca(int x,int y)
{
	if(d[x]>d[y])swap(x,y);
	for(int i=t;i>=0;i--)
	 if(d[fa[y][i]]>=d[x])y=fa[y][i];
	 if(x==y)return x;
	for(int i=t;i>=0;i--)
	if(fa[x][i]!=fa[y][i])x=fa[x][i],y=fa[y][i];
	return fa[x][0]; 
}
inline int getd(int x,int y)
{
	int lca=getlca(x,y);
	return dis[x]+dis[y]-2*dis[lca];
}
inline void clear()
{
	memset(a,0,sizeof(a));memset(head,0,sizeof(head));mm=1;
	for(int i=1;i<=1e5+3;i++)f[i]=i;
        memset(vis,0,sizeof(vis));memset(d,0,sizeof(d));
        memset(fa,0,sizeof(fa));memset(dis,0,sizeof(dis));
        memset(w,0,sizeof(w));memset(v,0,sizeof(v));
}
signed main() 
{
//	freopen("data.txt","r",stdin);
	int T;cin>>T;
	while(T--)
	{
		int n;n=read();clear();//cout<<T<<" ";
		t=log(n)/log(2);
		for(int i=1;i<=n;i++)v[i].v=read(),v[i].i=i;
		for(int i=1;i<=n;i++)w[i]=v[i].v; 
		for(int i=1;i<=n-1;i++)
		{
			int x,y,w;x=read();y=read();w=read();
			add(x,y,w);add(y,x,w); 
		} 
		d[1]=1;dfs(1);
		sort(v+1,v+n+1,com);
		int ans=0;
                for(int i=1;i<=n;i++)la[i]=lb[i]=i,ma[i]=0;
		for(int i=1;i<=n;i++)
		{
			int x=v[i].i,vv=v[i].v;
			int xx=find(x);
			for(int j=head[x];j;j=a[j].next)
			{
				int y=a[j].to,ff=find(y);
				if(w[y]<vv||ff==xx)continue;
				int llx=la[xx],rrx=lb[xx],lly=la[ff],rry=lb[ff];
				int l1=getd(la[xx],la[ff]),l2=getd(la[xx],lb[ff]),
				    l3=getd(lb[xx],la[ff]),l4=getd(lb[xx],lb[ff]);
				if(ma[ff]>ma[xx])ma[xx]=ma[ff],la[xx]=lly,lb[xx]=rry;
				if(l1>ma[xx])ma[xx]=l1,la[xx]=llx,lb[xx]=lly;
				if(l2>ma[xx])ma[xx]=l2,la[xx]=llx,lb[xx]=rry;
				if(l3>ma[xx])ma[xx]=l3,la[xx]=rrx,lb[xx]=lly;
				if(l4>ma[xx])ma[xx]=l4,la[xx]=rrx,lb[xx]=rry;
				f[ff]=xx;
			}
			ans=max(ans,ma[xx]*vv);
		}
		printf("%lld\n",ans);
	}
	return 0;
}

注意并查集用的时候每次要用一个点的代表元素,不只是这个点,否则狂WA不止

T3.玫瑰花精

一道比较细节的线段树
对于每个节点分别维护最左边的花精位置l,最右边的位置r,最中间的两只的距离/2 mid,以及mid对应的位置p
插入时比较l-1,n-r,mid那个更大,答案分别是1,n,p,然后维护相应信息,l和r都分别用左右的l,r得到,mid来源有三个:左儿子mid,右儿子mid,以及左r右l中间距离除以2,并更新p
删除也一样,这里注意初始化操作,l设成正无穷,mid设成负无穷,r设成0,这保证更新的时候如果优先级相同会先选左边,右边不会比左边优

#include <bits/stdc++.h>
using namespace std;
#define int long long
const int N=200050;
struct node{
	int l,r,ll,rr,mid,p;
}a[4*N];
inline void qi(int id)
{
	if(a[id*2].ll!=1e9)a[id].ll=a[id*2].ll;
	else a[id].ll=a[id*2+1].ll;
	if(a[id*2+1].rr)a[id].rr=a[id*2+1].rr;
	else a[id].rr=a[id*2].rr;
	if(a[id*2].mid>=a[id*2+1].mid)
	  a[id].mid=a[id*2].mid,a[id].p=a[id*2].p;
	else a[id].mid=a[id*2+1].mid,a[id].p=a[id*2+1].p;
	if(a[id*2].rr==0||a[id*2+1].rr==0)return;
	int mi=(a[id*2+1].ll-a[id*2].rr)>>1;
	if(mi>a[id*2].mid)a[id].mid=mi,a[id].p=a[id*2].rr+mi;
	if(a[id*2+1].mid>a[id].mid)
	  a[id].mid=a[id*2+1].mid,a[id].p=a[id*2+1].p;
}
inline void build(int id,int l,int r)
{
	a[id].l=l;a[id].r=r;
	if(l==r)
	{
		a[id].ll=1e9,a[id].rr=0;
		a[id].mid=-1e9;a[id].p=l;
		return;
	}
	int mid=(l+r)>>1;
	build(id*2,l,mid);build(id*2+1,mid+1,r);
	qi(id);
}
inline void insert(int id,int v,int op)
{
	if(a[id].l==a[id].r)
	{
		if(op==1)
		{
			a[id].ll=a[id].rr=a[id].l;
			a[id].mid=-1e9;a[id].p=a[id].l;
		}
		if(op==0)
		{
			a[id].ll=1e9,a[id].rr=0;
			a[id].mid=-1e9;a[id].p=a[id].l;
		}
		return;
	}
	int mid=(a[id].l+a[id].r)>>1;
	if(v<=mid)insert(id*2,v,op);
	else insert(id*2+1,v,op);
	qi(id);
}
int mp[10*N];
signed main()
{
	int n,m;cin>>n>>m;
	build(1,1,n);
	for(int i=1;i<=m;i++)
	{
		int op,x;scanf("%lld%lld",&op,&x);
		if(op==1)
		{
			int p=1;
			if(a[1].mid>a[1].ll-1)p=a[1].p;
			if(n-a[1].rr>max(a[1].mid,a[1].ll-1))p=n;
			mp[x]=p;
			printf("%lld\n",p);
			insert(1,p,1);
		}
		if(op==2)
		{
			int p=mp[x];
			insert(1,p,0);
			mp[x]=0;
		}
	}
	return 0;
}  

想明白边界就没啥了

考试反思

这场爆零告诉我最深的就是一定不能破坏心态,想不出正解导致懒得打暴力这事要避免,题一难大家都难,谁能稳住阵脚,保住暴力的基础上尽量多拿分,谁就是胜利者,所以考试要有自己的节奏,状态上来暴力也能拿100

posted @ 2021-07-24 17:28  D'A'T  阅读(56)  评论(0)    收藏  举报