2020牛客国庆集训派对day3

本文精选了多项算法竞赛题目并提供了详细的解决方案,包括数列处理、图论优化、概率统计等核心内容,深入探讨了算法设计与实现技巧。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

A Leftbest

给出一个数列,问对于ai前面的比他大的最小值的和。

用set upperbound一下就好了

int main(){
  int n;
  scanf("%d",&n);
  long long ans = 0;
  set<int> fk;
  for(int i = 0; i < n; ++i){
    int x;
    scanf("%d",&x);
    auto it = fk.upper_bound(x);
    fk.insert(x);
    if(it==fk.end())continue;
    ans+=*it;
  }
  printf("%lld\n",ans);
}

 

B First Date

给出一个n个节点m条边的图,每条边都有一个x,y值,他的权值就是x+y*a。这个a是一个[0,1]平均分布。问从s到t的期望时间的最小值。

(这个题很神奇,a是平均分布,为什么a就不能直接等于1/2?)

 

迭代a从0到1的值,然后计算每一个a,s到t的最小时间。最后除以迭代次数得出平均值。

struct Edge{
  int v;
  int x,y;
};

vector<Edge> g[1000];
double dis[10000];

void bfs(int u,double a){
  queue<int> q;
  q.push(u);
  dis[u] = 0;
  while(!q.empty()){
    int t = q.front();q.pop();
    for(auto it : g[t]){
      int v = it.v;
      double w = it.x + a*it.y;
      if(dis[v]==-1 || dis[v] > dis[t]+w){
        dis[v] = dis[t]+w;
        q.push(v);
      }
    }
  }
}
int main(){
  
  int n,m,s,t;
  cin>>n>>m>>s>>t;
  for(int i = 0; i <m; ++i){
    int u,v,x,y;
    cin>>u>>v>>x>>y;
    g[u].pb({v,x,y});
    g[v].pb({u,x,y});
  }
  double ans = 0;
  for(double a = 0; a <=1; a+=0.00001) {
    for(int i = 1; i <=n; ++i)dis[i]=-1;
    bfs(s,a);
    ans += dis[t];
  }
  printf("%.5lf\n",ans/100000);
}

C Sequence

要补吗???

 

D Capture Stars

给出两个内切圆,圆心在x轴且必过(0,0)。且给出m个点(x,y), 问另外一个夹在这两个圆之间的圆能最多包含对少个点(x,y)。

 

圆的反演(新知识啊)。圆的反演一般用在圆的外切内切的题。

反演定义:如果一个圆的圆心为0,圆心为r, 从圆心o拉出一条射线,对于圆上一点P,圆外一点P^{'}, 如果OP*OP^{'} = r^{2} , 则这两个点互为反演。

因此该圆就可以变成一条直线了。

 

由于题目的两个圆过(0,0),而我们需要找一个共用的反演圆将这两个圆上的点都使用同一个反演圆反演到另外一个平面上。

反演圆圆心(0,0),半径为大圆直径2R。

由于圆的反演肯定是对于经过反演中心(0,0)和圆心的直线的垂线,因此对于大圆上一点P (2R,0),反演点P^{'} (x^{'},0)x^{'} * (2R) = (2R)^{2}, x^{'} = 2R

对于小圆上一点P (2r,0),反演点P^{'} (x^{'},0),x^{'} * (2r)= (2R)^2, x^{'} = (2R)^2/(2r) = 2R^2/r

对于平面上任意一点P(x,y),反演点P^{'} (x^{'},y^{'}), 有OP * OP^{'} = (2R)^{2}, x/x^{'} = OP/OP^{'}

x^{'}=x*OP^{'} /OP = x * (2R)^{2} /OP^{2} = 4R^2 * x/(x*x+y*y)

同理

y^{'}=y*OP^{'} /OP = y * (2R)^{2} /OP^{2} = 4R^2 * y/(x*x+y*y)

那么问题变成了有两条直线2R^{2}/r2r,一些点(x',y'),问有一个圆在两直线间,最多包含多少个点(x',y')

 

圆的中心肯定在两直线中间 x0=(2R^{2}/r + 2r)/2 = R^{2}/r +r,圆的半径是r0=(2R^{2}/r - 2r)/2 = R^{2}/r-r

圆这条轨道上移动,求移动到某一个点时,覆盖的点最多。

 

求出每一个点的圆的覆盖范围 \Delta y=sqrt(r0^{2} - (x-x0)^{2}), 范围为[y-\Delta y, y+\Delta y]

最后用加加减减求线段最多覆盖次数就可以了。

 

double sqr(double x) {
	return x*x;
}

int main(){
	int t;
	cin>>t;
	while(t--){
		int n;
		double R,r;
        scanf("%d%lf%lf",&n,&R,&r);
		double x0 = R*R/r+R;
		double r0 = R*R/r-R;
		vector< pair<double,int> > pos;
		for(int i = 0; i < n; ++i){
			double x,y;
			cin>>x>>y;
			double l = sqr(x)+sqr(y);
			x = 4*R*R * x/l;
			y = 4*R*R * y/l;

			if(fabs(x-x0)<=r0){
				double h = sqrt(sqr(r0) - sqr(x-x0));
				pos.pb(mp(y-h,-1));
				pos.pb(mp(y+h,1));
			}
		}

		sort(pos.begin(), pos.end());
		int ct = 0, ans = 0;
		for(auto it :pos){
			ct -= it.second;
			ans = max(ans, ct);
		}
        printf("%d\n",ans);
	}
}

E Triangulation

多边形三角剖分

由于题目是顺时针或者逆时针给出点,因此先全部统一为逆时针的点。

然后区间dp,对于dp[i][k],表示i连一条线到k,把i到k之间的点做一个三角剖分。

找一个在i,k之间的点j,由于是逆时针,因此j必须在i,k的右手边(p[i,k]*p[i,j]<0)。则有dp[i][k] += dp[i][j]*dp[j][k].

 

struct Point{
	long long x,y;
	void read(){
		sf("%lld%lld",&x,&y);
	}
	Point operator -(const Point a){
		Point ret;
		ret.x = x-a.x;
		ret.y = y-a.y;
		return ret;
	}
	long long operator *(const Point a){
		return x*a.y-y*a.x;
	}
}p[300];


long long dp[300][300];

int main(){
	int n;
	cin>>n;
	clr(dp);

	long long s = 0;
	fr(i,0,n){
		p[i].read();
		if(i)
			s += (p[i]-p[0])*(p[i-1]-p[0]);
	}
	if(s>0){
		for(int i = 0; i+i<n-1;++i)swap(p[i],p[n-i-1]);
	}
	fr(i,0,n){
		dp[i][(i+1)%n]=1;
	}

	for(int l = 2; l<=n;++l)
		for(int i = 0; i<n;++i){
			int k = (i+l)%n;
			for(int j = (i+1)%n;j!=k;j=(j+1)%n){
				if((p[k]-p[i])*(p[j]-p[i])<0){
					dp[i][k] = (dp[i][k] + dp[i][j]*dp[j][k])%mod;
				}
			}
		}
	printf("%lld\n",dp[0][n-1]);
}

 

F Points

签到题??

统计度数为1的点

int d[1000010];
int main(){
  int n;
  scanf("%d",&n);
  for(int i = 0;i <n-1;++i){
    int u,v;
    scanf("%d%d",&u,&v);
    d[u]++;d[v]++;
  }
  int ans = 0;
  for(int i = 1; i <=n;++i)if(d[i]==1)++ans;
  printf("%d\n",ans);
}

G ParallelNetworkAnalysis

要补吗??

 

H Graph

给出n个节点和几种操作(对某两个点连或删边,对某两个加进查询集或者从查询集删除)。

对于询问操作,问查询集中的每两个点是否在树上连通。

 

对于树上的增删边直接用LCT维护。

对于查询集是否连通,每加入两个点,则分配给该点一个比较大的随机值。如果查询集的点都连通,那么查询集的总异或值为0。 (为什么随机?我觉得是因为查询集的点数其实很少的,随机出一个很大的数才能防止不同的值异或后也为0,譬如1,2,3)

 

问题变成了LCT怎么维护整个树的异或值。在加入点到查询值的时候,直接修改树上节点的值。但是由于LCT维护的splay是一条链来的,pushup的时候不会统计非链的节点,因此需要多一个sum来统计。这个sum在link的时候被加进去。这样在splay做pushup的时候就可以一直往上滚了。

(不能直接把这个值加入到节点的值中,因为当access变成一条链的时候父节点和当前节点就拥有重复的值了)

access的时候,由于非链的节点变成链,需要对sum做修改(减去以前的,添加现在的)。

link的时候,也需要先对x,y做一个换根,保证连接的时候是根。

 

 

int son[N][2], f[N];
int sum[N],sum1[N],r[N];
int a[N];
int n,m;
int cnt;

void pushup(int x){
	sum[x] = sum[son[x][0]] ^ sum[son[x][1]] ^ a[x] ^ sum1[x];
}

void switch_son(int x){
	int t = son[x][0]; son[x][0] = son[x][1];son[x][1] = t;
	r[x]^=1;
}

void pushdown(int x){
	if(r[x]){
        if(son[x][0])
		switch_son(son[x][0]);
        if(son[x][1])
		switch_son(son[x][1]);
		r[x] = 0;
	}
}

bool isroot(int x){
	return !(son[f[x]][0]==x || son[f[x]][1]==x);
}

void rotate(int x){
	int y = f[x], z = f[y];
	if(!isroot(y)){
		son[z][son[z][1]==y] = x;
	}
	int p = son[y][1]==x;
	int t = son[x][!p];
	son[x][!p] = y;
	son[y][p] = t;
	f[x] = z; f[y] = x; if(t)f[t] = y;
	pushup(y);
}

void splay (int x) {
		//push down first
	int y = x;
	stack<int> st;
	st.push(y);
	while(!isroot(y)) st.push(f[y]),y=f[y];
	while(!st.empty()) {
		pushdown(st.top()),st.pop();
	}

	while(!isroot(x)){
		int y = f[x];
		int z = f[y];
		if(!isroot(y)){
			rotate(y);
		}
		rotate(x);
	}
	pushup(x);
}

void access(int x){
    int y = 0;
    while(x){
		splay(x);
		sum1[x] ^=sum[son[x][1]];
		sum1[x] ^=sum[y];
		son[x][1] = y; y = x;
		pushup(x);
        x = f[x];
	}
}

void make_root(int x){
	access(x);
	splay(x);
	switch_son(x);
}

int findroot (int x) {
	access(x);
	splay(x);

	while(son[x][0])x = son[x][0];
	return x;
}

void link(int x, int y){
	make_root(x);
	if(findroot(y)==x)return;
	if(sum[x]){
		cnt--;
	}
	if(sum[y]){
		cnt--;
	}
  	make_root(y);
	f[y] = x;
	sum1[x]^=sum[y];
	make_root(x);
    pushup(x);
	if(sum[x]){
		cnt++;
	}
}

void cut(int x, int y){
	make_root(x);
    access(y);
//	if(findroot(y)!=x) return;
    splay(x);
    if(f[y] != x || son[y][0]) return; //very tricky
    if(sum[x]){
		cnt--;
	}
	f[y] = 0;
	son[x][1] =0;
	splay(x);
	if(sum[x]){
		cnt++;
	}
	splay(y);
	if(sum[y]){
		cnt++;
	}
}

void add(int x, int v){
	make_root(x);
	if(sum[x]){
		cnt--;
	}
	a[x]^=v;
	pushup(x);
	if(sum[x]){
		cnt++;
	}
}

void add(int x, int y, int v){
	add(x,v);
	add(y,v);
}


int main(){
  srand(time(0));
	int t;
	cin>>t;
	while(t--){
		sf("%d%d",&n,&m);
		fr(i,0,n+1){
			a[i] = 0;
			clr(son[i]);
			f[i] = 0;
			sum[i] = 0;
			r[i] = 0;
			sum1[i] = 0;
		}
		cnt = 0;
		map<pair<int,int>,int > v;
		fr(i,0,m){
			int op,x,y;
			sf("%d",&op);
			if(op!=5){
				sf("%d %d",&x,&y);
                if(x>y)swap(x,y);
			}
			if(op==1){
				link(x,y);
			}
			else if(op==2){
				cut(x,y);
			}
			else if(op==3){
				v[mp(x,y)] = rand();
				add(x,y,v[mp(x,y)]);
			}
			else if(op==4){
				add(x,y,v[mp(x,y)]);
              	v[mp(x,y)] = 0;
			}
			else if(op==5){
				if(cnt==0)printf("YES\n");
				else printf("NO\n");
			}
		}
	}
}

 

I Rooted Tree

 

我也不知道为什么,该序列是一个哈代-拉马努金拆分数列

在这里插入图片描述

把数列分解成奇数项和偶数项去dp求通项

然后就可以直接dp了

long long f[500010], g[500010];
long long dp[500010];
int mod = 998244353;

int main(){
  int n;
  scanf("%d",&n);
  for(long long i = 1; i<=n;++i){
    f[i] = (3ll*i*i-i)/2;
    g[i] = (3ll*i*i+i)/2;
  }
  
  dp[0] = 1;
  for(int i = 1; i<=n;i++){
    for(int j = 1; f[j]<=i;j++){
      if(j%2==1){
        dp[i] += dp[i-f[j]];
        if(g[j]<=i)dp[i]+= dp[i-g[j]];
      }
      else {
        dp[i] -= dp[i-f[j]];
        if(g[j]<=i)dp[i]-=dp[i-g[j]];
      }
    }
    dp[i] %= mod;
    if (dp[i] < 0) dp[i] += mod;
    //printf("i = %d dp = %lld\n",i,dp[i]);
  }
  
  printf("%lld\n",dp[n-1]);
}

 

J Flowers

给出n种花,每种花有ai朵。问能凑成多少束花,每束花由m种不同的花组成。

 

二分枚举答案,对于需要组成k束花,则需要k*m朵。扫描每一种花判断可以最多给多少朵。

 

int a[300010];
int n,m;


int main(){
  int t;
  cin>>t;
  while(t--){
    cin>>n>>m;
     long long sum = 0;
    for(int i = 0; i < n; ++i){
      scanf("%d",&a[i]);
      sum += a[i];
    }
   
    auto check = [&](long long num){
      long long tot = 0;
      for(int i = 0; i< n; ++i){
        tot += min((long long)a[i],num);
      }
      return tot>=num*m;
    };
    
    long long l = 0, r = sum/m+1;
    long long ans = 0;
    while(l+1<r){
      ll mid = (l+r)>>1;
      if(check(mid)){
        l = mid;
      }
      else r = mid;
    }
    cout<<l<<endl;
  }
}

 

 

### 关于2020寒假算法基础集训营中的欧几里得算法 在2020年的寒假算法基础集训营中,确实存在涉及欧几里得算法的相关题目。具体来说,在第四场竞赛的第一题即为“A. 欧几里得”,该题目的核心在于利用扩展欧几里得定理来解决问题[^5]。 #### 扩展欧几里得算法简介 扩展欧几里得算法主要用于求解形如 ax + by = gcd(a, b) 的线性不定方程的一组特解(x,y),其中gcd表示最大公约数。此方法不仅能够计算两个整数的最大公因数,还能找到满足上述条件的具体系数x和y。 对于给定的数据范围较小的情况可以直接通过递归来实现;而对于较大数据则需考虑效率优化问题。下面给出了一段基于C++语言编写的用于解决此类问题的模板代码: ```cpp #include<bits/stdc++.h> #define int long long using namespace std; // 定义全局变量存储结果 int x, y; void ex_gcd(int a, int b){ if(b == 0){ x = 1; y = 0; return ; } ex_gcd(b, a % b); int tmp = x; x = y; y = tmp - (a / b) * y; } ``` 这段程序实现了经典的扩展欧几里得算法逻辑,并且可以作为处理类似问题的基础工具函数调用。 #### 实际应用案例分析 回到原题本身,“A. 欧几里得”的解答思路就是先预处理斐波那契数列前若干项数值存入数组`a[]`内以便快速查询,之后针对每一次询问直接输出对应位置处两相邻元素之和即可得出最终答案。这实际上巧妙运用到了广为人知的裴蜀定理——任意一对互质正整数都可由它们自身的倍数组合而成,而这里正是借助了这一性质简化了解决方案的设计过程。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值