模拟15 考试总结
爆零了,整挺好
教训挺深的,说说吧
考试经过
T1一开觉得不太好做,然后开始推,根本推不对
T2极其不可做,发现没有裸的暴力就爬了
T3认为可以使用线段树维护左右位置,但觉得细节有亿点多,于是投身暴力,跑起大模拟
三个小时有将近一个小时是在自闭中度过的,剩下是看看T1写写T3,没思路,过不了样例,最后在迷茫中交上来两个没调出来的东西,喜提0pts
觉得主要还是心态炸了
T1想不到枚举斜率只能爆零,想到了也很难超过60,但T2爆搜还是有分的,感觉状态好点就不会把T3也挂掉,所以这场暴力其实有60,但一分都没得,其实不应该
T1.夜莺与玫瑰
这种题要有顺序的统计
正解是枚举斜率,由于左右对称,水平数值可以直接算,那只要找斜率为正且不平行于坐标轴的直线
然后出题人的解法还是不错的:

前驱是为了保证能构成一条直线(两点确定一条直线),后继是为了要保证他的位置被那个点唯一确定
相当于是什么呢?

右上角黑矩形是前驱在的,减去后继不在的,就是答案,于是我们可以有,对于每个直线方向向量\((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

浙公网安备 33010602011771号