校内模拟-2024-3-28
A
原题链接
【题意】
求树上距离(无边权)为 $k$ 的无序点对数。
$1\le n\le 5\times 10^4,1\le k\le 500$。
【解析】
这题我做过(惊恐)于是场上秒了。
可以左转我的题解。
我的做法是 DP,复杂度 $O(nk)$。
B
原题链接
【题意】
给定一棵无边权的树,树上有 $m$ 个关键点,你要选择其中一个出发,到达其余所有关键点,路径(显然)可重复。费用定义为经过边的总数,求最小费用和对应的出发点(多个费用相同的点取最小)。
【解析】
容易发现,答案一定是一些走两遍的路径和一些走一遍的路径组成的,所以为了最小化答案,走一遍的路径就是直径。答案就是 (要经过的点数 $-1$)$\times 2$ $-$ 直径。
猜到这个结论就好想了,考虑如果要经过一个点,当且仅当其子树内有关键点,所以从直径开始 dfs 即可。
别忘了特判只有一个点的情况。
#include<bits/stdc++.h> using namespace std; #define ll long long #define db double #define F(i,x,y) for(int i=x;i<=y;++i) #define fo(i,x,y) for(int i=x;i>=y;--i) #define mem(x,y) memset(x,y,sizeof(x)) #define eb emplace_back #define pii pair<int,int> #define endl '\n' constexpr int N=2e5+10; constexpr int inf=0x3f3f3f3f; constexpr ll INF=0x3f3f3f3f3f3f3f3f; constexpr db eps=1e-8; int n,m; vector<int> e[N]; int st[N],f[N],cnt; int dfs(int u,int fa,int dis){ f[u]=dis; for(int v:e[u]){ if(v==fa) continue; st[u]|=dfs(v,u,dis+1); } return st[u]; } signed main(){ ios::sync_with_stdio(false); cin.tie(nullptr); cin>>n>>m; int x,y; F(i,1,n-1){ cin>>x>>y; e[x].eb(y); e[y].eb(x); } F(i,1,m){ cin>>x; st[x]=1; } if(m==1){ cout<<x<<endl<<0<<endl; return 0; } int c1=0,c2=0; dfs(x,0,0); F(i,1,n) if(st[i]&&f[i]>f[c1]) c1=i; dfs(c1,0,0); F(i,1,n){ if(st[i]){ cnt++; if(f[i]>f[c2]) c2=i; } } cout<<min(c1,c2)<<endl; cout<<(cnt-1)*2-f[c2]<<endl; return 0; }
|
C
原题链接
【题意】
给定 $3$ 个长为 $n$ 的字符串,串中的字符表示一个数(如 ‘A’ 表示相同的一个数)。在 $n$ 进制下,前两个字符串代表的数的和等于第三个串代表的数。求每个字符 $c$ 代表的唯一数 $x$(’A’ $\le c\le$ ‘Z’,$0\le x\le 9$)
【解析】
先考虑爆搜。其实真的就是爆搜,从低位到高位枚举,最后再判断是否可行。可以获得 $40$ 分的好成绩(我考场就是这样的)。
我们想想能不能优化一下爆搜,剪一下枝。容易想到第一个显然的剪枝,最高位不能有进位,不然会超出 $n$ 位。这样是不够的,再考虑如果已经枚举出来一些数了,我们无法判断只是因为不知道是否有进位。
那么根据题目,进位最多为 $1$。所以判断一下,如果进不进位都不成立的话显然不行。
这样就可以通过本题,复杂度十分玄学,我不会算。听说正解是高斯消元,我也不会,先咕着。
#include<bits/stdc++.h> using namespace std; #define ll long long #define endl '\n' #define F(i,x,y) for(int i=(x);i<=(y);++i) #define Fo(i,x,y) for(int i=(x);i>=(y);--i) int n; string a,b,c; int x[30],y[30],z[30]; int num[30],nex[30],st[30],cnt,flag; bool chk(){ if(num[x[0]]+num[y[0]]>=n) return true; Fo(i,n-1,0){ int t1=num[x[i]],t2=num[y[i]],t3=num[z[i]]; if(t1==-1||t2==-1||t3==-1) continue; if((t1+t2)%n!=t3&&(t1+t2+1)%n!=t3) return true; } return false; } bool chk2(){ int tmp=0,i=n-1; while(i>=0){ if((num[x[i]]+num[y[i]]+tmp)%n!=num[z[i]]) break; tmp=(num[x[i]]+num[y[i]]+tmp)/n; i--; } return (i<0); } void dfs(int s){ if(chk()||flag) return ; if(s==n){ if(chk2()){ F(i,0,n-1) cout<<num[i]<<" "; flag=1; } return ; } Fo(i,n-1,0){ if(!st[i]){ st[i]=1; num[nex[s]]=i; dfs(s+1); st[i]=0; num[nex[s]]=-1; } } } void init(int x){ if(!st[x]){ st[x]=1; nex[cnt++]=x; } } signed main(){ ios::sync_with_stdio(false); cin.tie(nullptr); cin>>n>>a>>b>>c; memset(num,-1,sizeof(num)); F(i,0,n-1){ x[i]=(int)a[i]-'A'; y[i]=(int)b[i]-'A'; z[i]=(int)c[i]-'A'; } Fo(i,n-1,0){ init(x[i]); init(y[i]); init(z[i]); } F(i,0,n-1) st[i]=0; dfs(0); return 0; }
|
D
原题链接
【题意】
给定一棵 $n$ 个节点的树,每条边有边权,求出树上两点距离小于等于 $k$ 的点对数量。
【解析】
跟第一题和板子题很像对吧(其实就是点分治板子)。记录所有出现过的距离,双指针尝试更新,容斥减掉子树内的答案并分治。复杂度 $O(n\ log^2n)$。
没啥好说的,很板。
#include<bits/stdc++.h> using namespace std; #define ll long long #define endl '\n' #define eb emplace_back const int N=1e5+10; const int inf=0x3f3f3f3f; int n,k; struct edge{ int v,next,w; }e[N]; int tot,rt,sum,cnt,ans; int head[N],vis[N],mx[N],siz[N],dis[N],rem[N]; void add(int u,int v,int w){ e[++tot].v=v; e[tot].w=w; e[tot].next=head[u]; head[u]=tot; } void getroot(int u,int fa){ siz[u]=1; mx[u]=0; for(int i=head[u];i;i=e[i].next){ int v=e[i].v; if(v==fa||vis[v]) continue; getroot(v,u); siz[u]+=siz[v]; mx[u]=max(mx[u],siz[v]); } mx[u]=max(mx[u],sum-siz[u]); if(mx[u]<mx[rt]) rt=u; } void getdis(int u,int fa){ rem[++cnt]=dis[u]; for(int i=head[u];i;i=e[i].next){ int v=e[i].v; if(vis[v]||v==fa) continue; dis[v]=dis[u]+e[i].w; getdis(v,u); } } int calc(int u,int s){ cnt=0; dis[u]=s; getdis(u,0); sort(rem+1,rem+cnt+1); int l=1,r=cnt,res=0; while(l<=r){ if(rem[l]+rem[r]<=k){ res+=(r-l); l++; } else r--; } return res; } void solve(int u){ vis[u]=1; ans+=calc(u,0); for(int i=head[u];i;i=e[i].next){ int v=e[i].v; if(vis[v]) continue; ans-=calc(v,e[i].w); mx[rt=0]=inf; sum=siz[v]; getroot(v,u); solve(rt); } } signed main(){ cin>>n; for(int i=1,x,y,z;i<n;++i){ cin>>x>>y>>z; add(x,y,z); add(y,x,z); } cin>>k; mx[rt]=sum=n; getroot(1,0); solve(rt); cout<<ans<<endl; return 0; }
|
总而言之,这场模拟赛还是不错的,锻炼了我的点分(就是 B 题想到正解了没对很可惜,以后加强训练代码能力)。希望能越来越好,撒花!