模拟赛20200213【增量求第n小,三角剖分分治最短路,树上路径中位数之和】

T1:

在这里插入图片描述
n ≤ 6500 n\le6500 n6500

题解:

因为 1 0 i + 1 / 2 i & 1 = 0 10^{i+1}/2^i\&1=0 10i+1/2i&1=0, 1 0 i / 2 i & 1 = 1 10^i/2^i\&1=1 10i/2i&1=1,所以 1 0 k 10^k 10k的二进制表示的末尾一定有 k k k个0

所以可以记录长度为 l e n len len的所有合法串及其对应的二进制表示,然后考虑添加第 l e n + 1 len+1 len+1位为0或1,当这个串的二进制表示的第 l e n + 1 len+1 len+1位为1时就不用拓展了(肯定不合法)。所以由 l e n → l e n + 1 len\rarr len+1 lenlen+1,就可以推出第 n n n小的萌数。
由于需要高精度,每个串对时间的贡献为 d 2 d^2 d2 d d d为答案的位数,总复杂度 O ( n d 2 ) O(nd^2) O(nd2)

Code:

#include<bits/stdc++.h>
#define pb(x) push_back(x)
using namespace std;
struct Big{
	int s[505],len;
	Big(){memset(s,0,sizeof s),len=1;}
	Big operator + (const Big &B)const{
		Big c;c.len=max(len,B.len);
		for(int i=0;i<c.len;i++)
			if((c.s[i]+=s[i]+B.s[i])>1) c.s[i+1]++,c.s[i]&=1;
		if(c.s[c.len]) c.len++;
		return c;
	}
	Big operator * (int t){
		Big c;c.len=len;
		for(int i=0;i<len;i++)
			if((c.s[i]+=s[i]*t)) c.s[i+1]+=c.s[i]>>1,c.s[i]&=1;
		for(int &i=c.len;c.s[i];i++) c.s[i+1]+=c.s[i]>>1,c.s[i]&=1;
		return c;
	}
	void write(){
		for(int i=len-1;i>=0;i--) putchar(s[i]+'0');
		putchar('\n');
	}
}p10,p2;
vector<Big>f,tf,g,tg;
int n,m,cnt;
int main()
{
	freopen("quiz.in","r",stdin);
	freopen("quiz.out","w",stdout);
	scanf("%d",&n),p10.s[0]=p2.s[0]=1;
	f.pb(Big()),g.pb(Big());
	for(int k=0;;k++,p10=p10*10,p2=p2*2){
		m=f.size(),tf.clear(),tg.clear();
		for(int i=0;i<m;i++) if(!g[i].s[k]) tf.pb(f[i]),tg.pb(g[i]);
		for(int i=0;i<m;i++) if(!g[i].s[k]){
			tf.pb(f[i]+p2),tg.pb(g[i]+p10),++cnt;
			if(cnt==n) {tf.back().write();return 0;}
		}
		f.swap(tf),g.swap(tg);
	}
}

T2:

在这里插入图片描述
n ≤ 55000 n\le55000 n55000,询问数 ≤ 2 n \le2n 2n

题解:

最短路问题。由于是多对点,显然什么优化建边行不通。
那么另外一个常见技巧就是分治了。
由于给出的图是一个多边形的三角剖分,因此每条对角线都能将这个多边形
分成两个部分,就可以采用分治的方法,点对分为跨过这个对角线或者没跨过对角线。
在这里插入图片描述
Code(我又手残把bfs的队列手写成栈了,惨遭爆5,还有注意询问的数量是小于等于2n):

#include<bits/stdc++.h>
#define maxn 55005
#define pb(x) push_back(x)
using namespace std;
int n,m,ans[maxn<<1],siz[maxn],d[2][maxn];//!!!maxn<<1
bool L[maxn],R[maxn];
struct edge{int x,y;};
struct qry{int x,y,id;};
vector<int>G[maxn];
int que[maxn],l,r;//!!!l,r
void BFS(int u,int *dis){
	que[l=r=1]=u,dis[u]=0;
	while(l<=r){
		u=que[l++];
		for(int i=0,lim=G[u].size(),v;i<lim;i++) if(dis[v=G[u][i]]==-1) dis[v]=dis[u]+1,que[++r]=v;
	}
}
void solve(vector<int>&a,vector<edge>&e,vector<qry>&q){
	if(q.empty()) return;
	if(a.size()==3){
		for(int i=0,lim=q.size();i<lim;i++) ans[q[i].id]=(q[i].x!=q[i].y);
		return;
	}
	int u,v,mn=1e9,n=a.size(),m=e.size();
	siz[a[0]]=1;
	for(int i=1;i<n;i++) siz[a[i]]=siz[a[i-1]]+1;
	for(int i=0;i<m;i++){
		int x=e[i].x,y=e[i].y,len=siz[x]>siz[y]?siz[x]-siz[y]-1:siz[y]-siz[x]-1;
		if((len=max(len,n-2-len))<mn) mn=len,u=x,v=y;
	}
	if(u>v) swap(u,v);
	vector<int>a1,a2; vector<edge>e1,e2; vector<qry>q1,q2;
	a1.clear(),a2.clear(),e1.clear(),e2.clear(),q1.clear(),q2.clear();
	for(int i=0;i<n;i++){
		if(u<=a[i]&&a[i]<=v) L[a[i]]=1,a1.pb(a[i]);
		if(a[i]<=u||v<=a[i]) R[a[i]]=1,a2.pb(a[i]);
	}
	for(int i=0;i<m;i++){
		int x=e[i].x,y=e[i].y;
		if(L[x]&&L[y]) e1.pb(e[i]);
		if(R[x]&&R[y]) e2.pb(e[i]); 
	}
	for(int i=0,lim=q.size();i<lim;i++){
		int x=q[i].x,y=q[i].y;
		if(L[x]&&L[y]) q1.pb(q[i]);
		if(R[x]&&R[y]) q2.pb(q[i]);
	}
	for(int i=0;i<n;i++) d[0][a[i]]=d[1][a[i]]=-1,G[a[i]].clear();
	for(int i=0,lim=e.size();i<lim;i++) G[e[i].x].pb(e[i].y),G[e[i].y].pb(e[i].x);
	BFS(u,d[0]),BFS(v,d[1]);
	for(int i=0,lim=q.size();i<lim;i++){
		int x=q[i].x,y=q[i].y;
		ans[q[i].id]=min(ans[q[i].id],min(d[0][x]+d[0][y],d[1][x]+d[1][y]));
	}
	for(int i=0;i<n;i++) L[a[i]]=R[a[i]]=0;
	solve(a1,e1,q1),solve(a2,e2,q2);
}
vector<int>a;
vector<edge>e;
vector<qry>q;
int main()
{
	freopen("drive.in","r",stdin);
	freopen("drive.out","w",stdout);
	scanf("%d",&n); int x,y;
	for(int i=1;i<=n;i++) a.pb(i),e.push_back((edge){i,i%n+1});
	for(int i=1;i<=n-3;i++) scanf("%d%d",&x,&y),e.push_back((edge){x,y});
	scanf("%d",&m);
	for(int i=1;i<=m;i++) scanf("%d%d",&x,&y),q.push_back((qry){x,y,i}),ans[i]=1e9;
	solve(a,e,q);
	for(int i=1;i<=m;i++) printf("%d\n",ans[i]); 
}

T3:

在这里插入图片描述
n ≤ 32000 n\le32000 n32000

题解:

看到随机树,就想到暴力爬链。
考虑如何统计某条边作为中位数的路径条数。
奇数个数里的中位数,就是小于它的和大于它的数量相等。
从小到大添加边,已经添加的设为 1 1 1,未添加的设为 − 1 -1 1,求当前边作为中位数的路径条数时,先将其添加(改为 1 1 1),然后只需要求出树中包含这条边的权和为 1 1 1的路径数即可。

可以记录 f [ i ] [ j ] f[i][j] f[i][j]表示 i i i子树内到 i i i权和为 j j j的路径条数,设当前添加的边的端点为 x x x,只需从 x x x往上枚举路径的 l c a lca lca计算方案数(计算时还要枚举 x x x的子树内的权和),更新也很好更新,复杂度就是 O ( ∑ d o w n i ∗ u p i ) O(\sum down_i*up_i) O(downiupi) d o w n i down_i downi表示 i i i向下的最大深度, u p i up_i upi表示向上的长度,出题人说随机情况下为 O ( n n ) O(n\sqrt n) O(nn )

Code:

#include<bits/stdc++.h>
#define maxn 32005
#define f(i,j) F[i][(j)+O]
using namespace std;
const int O = 300;
pair<int,int>a[maxn];
int n,cnt,fa[maxn],dep[maxn],F[maxn][605],c[maxn];
int fir[maxn],nxt[maxn<<1],to[maxn<<1],w[maxn<<1],tot;
inline void line(int x,int y,int z){nxt[++tot]=fir[x],fir[x]=tot,to[tot]=y,w[tot]=z;}
void dfs(int u,int ff){
	fa[u]=ff,c[u]=-1;
	for(int i=fir[u],v;i;i=nxt[i]) if((v=to[i])!=ff)
		dfs(v,u),dep[u]=max(dep[u],dep[v]+1),a[++cnt]=make_pair(w[i],v);
}
int solve(int x){
	c[x]=1;
	for(int i=fa[x],k=0;i;k+=c[i],i=fa[i])
		for(int j=-dep[x];j<=dep[x];j++) f(i,j+k-1)-=f(x,j),f(i,j+k+1)+=f(x,j);
	int ret=0;
	for(int i=x,k=c[x];fa[i];i=fa[i],k+=c[i])
		for(int j=-dep[x];j<=dep[x];j++) ret+=(f(fa[i],1-(j+k))-f(i,1-(j+k)-c[i]))*f(x,j);
	return ret;
}
int main()
{
 	freopen("draw.in","r",stdin);
	freopen("draw.out","w",stdout);
	scanf("%d",&n);
	for(int i=1,x,y,z;i<n;i++) scanf("%d%d%d",&x,&y,&z),line(x,y,z),line(y,x,z);
	dfs(1,0);
	for(int i=1;i<=n;i++) for(int j=i,k=0;j;j=fa[j],k--) f(j,k)++;
	sort(a+1,a+n);
	long long ans=0;
	for(int i=1;i<n;i++) ans+=1ll*a[i].first*solve(a[i].second);
	printf("%lld\n",ans);
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值