牛客巅峰训练赛S2钻石&王者——补题

第五场

C题——(树的直径+dfs)

求一个树的非严格第二直径,我们首先可以直接求出树的直径,并记录下直径的两个端点,然后我们考虑分别以这两个端点(注意是两个,只对一个端点判断会漏掉一些情况)进行bfs,记录最大深度以及最大深度的个数,判断最大深度个数是否大于等于二,如果满足条件,答案就是最大深度,否则答案就是最大深度-1。

#define rp(i,s,t) for (int i = (s); i <= (t); i++)
int vis[100007];
int dis[100007];
vector<int> G[100007];
    int ans,pos;
    
class Solution {
public:
    /**
     * 代码中的类名、方法名、参数名已经指定,请勿修改,直接返回方法规定的值即可
     * 
     * @param e int整型vector 长度为n-1的数组,表示结点2到结点n的父结点
     * @return int整型
     */
    
    void bfs(int s){
    queue<int> q;q.push(s);
    vis[s]=1;
    while(!q.empty()){
        int u=q.front();q.pop();
        if(dis[u]>ans) ans=dis[u],pos=u;
        for(auto v:G[u]){
            if(!vis[v]){
                dis[v]=dis[u]+1;
                vis[v]=1;
                q.push(v);
            }
        }
    }
}
    int tree3(vector<int>& v) {
        int n=v.size();
        for(int i=0;i<n;i++){
            G[i+2].push_back(v[i]);
            G[v[i]].push_back(i+2);
        }
        dis[1]=0;
        bfs(1);
        rp(i,1,n+1) vis[i]=0;
        rp(i,1,n+1) if(dis[i]>dis[pos]) pos=i;
        dis[pos]=0;
        bfs(pos);
        int Max=*max_element(dis+1,dis+2+n);
        int cnt=0;
        rp(i,1,n+1) if(dis[i]==Max) cnt++,pos=i;
        dis[pos]=0;
        rp(i,1,n+1) vis[i]=0;
        bfs(pos);
        rp(i,1,n+1) if(dis[i]==Max) cnt++;
        if(cnt>2) return Max;
        else return Max-1;
    }
};

第七场

B题——巴什博弈变形

可以看出当p==q时,就是巴什博弈,然后考虑其他情况。

然后考虑p>q的情况,这时牛牛可以先取一定数量的贝壳,使得剩下的贝壳数是q+1的倍数。

当剩下的贝壳数是q+1的倍数时,这是牛妹的一个必败态,因为不论牛妹怎么取,牛牛都可以取一些贝壳,使得剩下的贝壳还是q+1的倍数,这样牛牛就是必胜的。

p<q的情况同理,不过因为是牛牛先取,需要考虑一种特殊情况,即当p>=n时,牛牛一次可以取完,牛牛必胜,否则牛牛必败。

class Solution {
public:
    /**
     * 
     * @param n int整型 
     * @param p int整型 
     * @param q int整型 
     * @return int整型
     */
    int Gameresults(int n, int p, int q) {
        if(p>=n||p>q) return 1;
        if(p==q){
            if(n%(p+1)==0) return -1;
            else return 1; 
        }
        return -1;
    }
};

C题——(树的直径+dfs)

首先需要知道怎么求树的直径(dfs或树形dp),然后我们可以首先从1开始dfs出直径的一个端点,然后我们以这个端点为根,进行dfs,并用一个数组dp来记录当前节点的儿子节点的最大深度,并求出直径,最后再dfs一次,如果儿子节点的最大深度等于直径,则表明当前节点时直径上的点,对该点进行标记,同理以另一个端点为根进行同样的操作。

trick:注意不能只对一个端点进行标记,这样会漏掉一些情况,比如下面这个图。

建图数据:7,[1,2,2,1,5,5],[2,3,4,5,6,7]

正确答案:7

只对一个端点进行判断的答案是6。

#include <bits/stdc++.h>
#define PI atan(1.0)*4
#define rp(i,s,t) for ( int i = (s); i <= (t); i++)
#define RP(i,t,s) for ( int i = (t); i >= (s); i--)
#define sc(x) scanf("%d",&x)
#define scl(x) scanf("%lld",&x)
#define ll long long
#define ull unsigned long long
#define mst(a,b) memset(a,b,sizeof(a))
#define lson l,m,rt<<1
#define rson m+1,r,rt<<1|1
#define pii pair<int,int>
#define pll pair<ll,ll>
#define pil pair<int,ll>
#define m_p make_pair
#define p_b push_back
#define ins insert
#define era erase
#define INF 0x3f3f3f3f
#define LINF 0x3f3f3f3f3f3f3f3f
#define dg if(debug)
#define outval(a) cout << "Debuging...|" << #a << ": " << a << "\n";
using namespace std;
int debug = 0;

ll gcd(ll a,ll b){
    return b?gcd(b,a%b):a;
}
ll lcm(ll a,ll b){
    return a/gcd(a,b)*b;
}
inline int read(){
    int s=0,f=1;
    char ch=getchar();
    while(ch<'0'||ch>'9'){
        if(ch=='-') f=-1;
        ch=getchar();
    }
    while(ch>='0'&&ch<='9'){
        s=s*10+ch-'0';
        ch=getchar();
    }
    return s*f;
}
const int N = 2e5+7;
int u[N],v[N];
int vis[N];
vector<int> G[N];
int dp[N];
int pos;
int Max;
void dfs1(int u,int fa,int d){
    if(d>Max){
        pos=u;
        Max=d;
    }
    for(auto v:G[u]) if(v!=fa) dfs1(v,u,d+1);
}
void dfs2(int u,int fa,int d){
    if(d>Max){
        pos=u;
        Max=d;
    }
    dp[u]=d;
    for(auto v:G[u]){
        if(v!=fa){
            dfs2(v,u,d+1);
            dp[u]=max(dp[v],dp[u]);
        }
    }
}
class Solution {
public:
    /**
     * 代码中的类名、方法名、参数名已经指定,请勿修改,直接返回方法规定的值即可
     * 
     * @param n int整型 节点个数
     * @param u int整型vector 
     * @param v int整型vector 
     * @return int整型
     */
    int PointsOnDiameter(int n, vector<int>& u, vector<int>& v) {
        rp(i,0,n-2){
            G[u[i]].p_b(v[i]);
            G[v[i]].p_b(u[i]);
        }
        dfs1(1,0,0);
        int L=pos;
        Max=0;
        dfs2(L,0,0);
        rp(i,1,n) if(dp[i]==Max) vis[i]=1;
		int R=pos;
		dfs2(R,0,0);
		rp(i,1,n) if(dp[i]==Max) vis[i]=1;
		int cnt=0;
		rp(i,1,n) cnt+=vis[i];
        return cnt;
    }
};

第八场

C题——(floyd求最短乘积路)

一眼不难发现是到floyd最短路的题,但是发现维护的是乘法,直接进行维护的话精度不够(不能取余,取余会使得答案错误),因此需要转换成加法运算。

乘法怎么转换加法呢?可以通过取log实现。

那么解法就出来了,我们可以维护对边权取log后的加法最短路,同时用一个pair记录对应的实际乘法最短路的值。

而且边的权值定义为组合数C(n,m)=\frac{n!}{m!*(n-m)!},这里我们定义lg[i]为log(1)+log(2)+log(3)+.....+log(i),即log值的前缀和,对应阶乘取log后的值。

边权取log后就转换为lg[n]-lg[m]-lg[n-m]

最后我们套用floyd的模板就行了(不要忘记重载+号),答案就是G[s][t].second%mod。

#include<iostream>
#define ll long long
#include<algorithm>
#include<vector>
#define pdl pair<double,ll>
using namespace std ;
const ll mod = 1e9+7;
pdl operator + (pdl a,pdl b){
    return make_pair(a.first+b.first,a.second*b.second%mod);
}
pdl G[505][505];
int n,m;
ll quickPow(ll a, ll b, ll mod) {
	ll ans = 1;
	while(b) {
		if(b&1) ans=(ans*a)%mod;
		a=(a*a)%mod;
		b>>=1;
	}
	return ans;
}
ll inv(ll x){
	return quickPow(x,mod-2,mod)%mod;
}
ll fac[1007];
double lg[1007];
ll C(int n,int m){
	return fac[n]*inv(fac[n-m])%mod*inv(fac[m])%mod;
}
void init(){
	fac[0]=1;
	for(int i=1;i<=1000;i++) fac[i]=(fac[i-1]*i)%mod;
    for(int i=1;i<=1000;i++) lg[i]=(log(i)+lg[i-1]);
}
void floyd(){
    for(int k=1;k<=n;k++)
        for(int i=1;i<=n;i++)
            for(int j=1;j<=n;j++)
                    G[i][j]=min(G[i][j],G[i][k]+G[k][j]);
}
class Solution {
public:
    /**
     * 代码中的类名、方法名、参数名已经指定,请勿修改,直接返回方法规定的值即可
     * 
     * @param n int整型 有n个城市
     * @param m int整型 有m条路径
     * @param s int整型 起始城市坐标
     * @param t int整型 终点城市坐标
     * @param edge int整型vector<vector<>> 边的格式如下:[[u1,v1,a1,b1],[u2,v2,a2,b2],...]
     * @return int整型
     */
    int minDist(int a,int b,int s, int t, vector<vector<int> >& edge) {
        n=a;
        m=b;
        init();
        for(int i=1;i<=n;i++){
            for(int j=1;j<=n;j++){
                if(i==j) G[i][j]=make_pair(0,1ll);
                else G[i][j]=G[j][i]=make_pair(1e18,0);
            }
        }
        for(int i=0;i<m;i++){
            vector<int> val=edge[i];
            int x=val[0];
            int y=val[1];
            double w1=lg[val[2]]-lg[val[3]]-lg[val[2]-val[3]];
            ll w2=C(val[2],val[3])%mod;
            G[x][y]=G[y][x]=min(G[x][y],make_pair(w1,w2));
        }
        floyd();
        return G[s][t].second%mod;
    }
};

第十场

C题——并查集

把边权拆位后对每一位进行考虑,可以发现只有路径上的所有点的当前位都为1时才能对答案产生贡献,因此我们可以用并查集来维护连通块(保证连通块中的所有点的当前位为1,并且可以到达连通块中的其他点)。

假设当前位为i,当前连通块内的点数为num,对答案产生的贡献就是(\textrm{C}^{1}_{num}+\textrm{C}^{2}_{num})*(1<<i)\Rightarrow \frac{num*(num+1)}{2}*(1<<i)

#include <bits/stdc++.h>
#define PI atan(1.0)*4
#define rp(i,s,t) for (int i = (s); i <= (t); i++)
#define RP(i,t,s) for ( int i = (t); i >= (s); i--)
#define sc(x) scanf("%d",&x)
#define scl(x) scanf("%lld",&x)
#define ll long long
#define ull unsigned long long
#define mst(a,b) memset(a,b,sizeof(a))
#define lson l,m,rt<<1
#define rson m+1,r,rt<<1|1
#define pii pair<int,int>
#define pll pair<ll,ll>
#define pil pair<int,ll>
#define m_p make_pair
#define p_b push_back
#define ins insert
#define era erase
#define INF 0x3f3f3f3f
#define LINF 0x3f3f3f3f3f3f3f3f
#define dg if(debug)
#define outval(a) cout << "Debuging...|" << #a << ": " << a << "\n";
using namespace std;
int debug = 0;
ll gcd(ll a,ll b){
	return b?gcd(b,a%b):a;
}
ll lcm(ll a,ll b){
	return a/gcd(a,b)*b;
}
inline int read(){
	int s=0,f=1;
	char ch=getchar();
	while(ch<'0'||ch>'9'){
		if(ch=='-') f=-1;
		ch=getchar();
	}
	while(ch>='0'&&ch<='9'){
		s=s*10+ch-'0';
		ch=getchar();
	}
	return s*f;
}
const int N = 1e5+7;
#define UnionFindSIZE 1234567
struct Union_Find {
	int d[UnionFindSIZE], num[UnionFindSIZE];
	void init(int n){
		for(int i = 0; i <= n; i++) {
			d[i]=i;
			num[i]=1;
		}
	}
	int find(int x){
		int y = x, z = x;
		while(y != d[y]) { y = d[y]; }
		while(x != y) {
			x = d[x];
			d[z] = y;
			z = x;
		}
		return y;
	}
	bool is_root(int x) { return d[x] == x; }
	bool uu(int x,int y) {
		x=find(x);
		y=find(y);
		if(x == y) { return 0; }
		if(num[x] > num[y]) { swap(x,y); }
		num[y] += num[x];
		d[x] = y;
		return 1;
	}
}U;
int vis[N];
int u[N],v[N],w[N];
class Solution {
public:
    /**
     * 代码中的类名、方法名、参数名已经指定,请勿修改,直接返回方法规定的值即可
     * 
     * @param n int整型 点的个数
     * @param u int整型vector 每条边的起点
     * @param v int整型vector 每条边的终点
     * @param p int整型vector 每个点的价值
     * @return long长整型
     */
    long long solve(int n, vector<int>& uu, vector<int>& vv, vector<int>& ww) {
        ll ans=0;
        rp(i,0,20){
            U.init(n);
            rp(j,0,n-1) w[j]=((ww[j]&(1<<i))!=0);
            rp(j,0,n-2) if(w[uu[j]]==1&&w[vv[j]]==1) U.uu(uu[j],vv[j]);
            rp(j,0,n-1){
                if(!U.is_root(j)||!w[j]) continue;
                ans+=U.num[j]*(U.num[j]+1)/2*(1ll<<i);
            }
        }
        return ans;
    }
};

第十一场

B题——卡特兰数+规律

手推几个样例或者暴力一下不难发现答案就是判断第i个卡特兰数的奇偶性,然后我们再找一下规律可以看出,当n-1==2^k时,输出true,否则为false。

因此我们可以直接用java的大数模拟一下就行了。

import java.util.*;
import java.math.BigInteger;


public class Solution {
    /**
     * 代码中的类名、方法名、参数名已经指定,请勿修改,直接返回方法规定的值即可
     * 
     * @param n string字符串 三角形的长和高
     * @return bool布尔型
     */
    public boolean judge (String n) {
        BigInteger a=new BigInteger(n);
		a=a.add(BigInteger.ONE);
		BigInteger two=BigInteger.valueOf(2);
		while(a.mod(two).equals(BigInteger.ZERO)){
            a=a.divide(two);
        }
		return a.equals(BigInteger.ONE);
    }
}

C题——基环树+暴力

首先n个点n条边的连通无向无权图(不存在自环)这个条件就保证了这个图只有一个环,即是一棵基环树。

因为n比较小,我们可以直接枚举删除环上面的每条边,重现建图后用树形dp求出直径,同时更新维护直径最大值就行了。

#include <bits/stdc++.h>
#define PI atan(1.0)*4
#define rp(i,s,t) for (int i = (s); i <= (t); i++)
#define RP(i,t,s) for (int i = (t); i >= (s); i--)
#define sc(x) scanf("%d",&x)
#define scl(x) scanf("%lld",&x)
#define ll long long
#define ull unsigned long long
#define mst(a,b) memset(a,b,sizeof(a))
#define lson l,m,rt<<1
#define rson m+1,r,rt<<1|1
#define pii pair<int,int>
#define pll pair<ll,ll>
#define pil pair<int,ll>
#define m_p make_pair
#define p_b push_back
#define ins insert
#define era erase
#define INF 0x3f3f3f3f
#define LINF 0x3f3f3f3f3f3f3f3f
#define dg if(debug)
#define outval(a) cout << "Debuging...|" << #a << ": " << a << "\n";
using namespace std;
int debug = 0;
ll gcd(ll a,ll b){
	return b?gcd(b,a%b):a;
}
ll lcm(ll a,ll b){
	return a/gcd(a,b)*b;
}
inline int read(){
	int s=0,f=1;
	char ch=getchar();
	while(ch<'0'||ch>'9'){
		if(ch=='-') f=-1;
		ch=getchar();
	}
	while(ch>='0'&&ch<='9'){
		s=s*10+ch-'0';
		ch=getchar();
	}
	return s*f;
}
const int N = 5e3+7;
vector<pii> G[N];
int inq[N],in[N];
int vis[N];
int n;
int dp[N];
int ans;
void dfs(int u,int fa){
	dp[u]=1;
	int pre=0;
	for(auto vv:G[u]){
		int v=vv.first,id=vv.second;
		if(inq[id]||v==fa) continue;
		dfs(v,u);
		dp[u]=max(dp[u],dp[v]+1);
		ans=max(ans,dp[u]);
		ans=max(dp[v]+pre+1,ans);
		pre=max(pre,dp[v]);
	}
}
class Solution {
public:
    /**
     * 代码中的类名、方法名、参数名已经指定,请勿修改,直接返回方法规定的值即可
     * 
     * @param n int整型 
     * @param u int整型vector 
     * @param v int整型vector 
     * @return int整型
     */
    int MaxDiameter(int n, vector<int>& u, vector<int>& v) {
        rp(i,1,n){
            G[u[i-1]].p_b(m_p(v[i-1],i));
            G[v[i-1]].p_b(m_p(u[i-1],i));
            in[u[i-1]]++;in[v[i-1]]++;
        }
	    queue<int> q;
        rp(i,1,n) if(in[i]==1) q.push(i);
        while(!q.empty()){
            int u=q.front();q.pop();
            for(auto vv:G[u]){
                int v=vv.first,id=vv.second;
                in[v]--;
                vis[id]=1;
                if(in[v]==1) q.push(v);
            }
        }
        int res=0;
        rp(i,1,n){
            if(!vis[i]){//枚举在环上的边
                ans=0;
                inq[i]=1;
                dfs(1,1);
                inq[i]=0;
                res=max(res,ans);
            }
        }
        return res-1;
    }
};

第十二场

B题——思维+前缀和思想

首先默认所有音符都不奏响,这样初始答案就是\sum_{i=1}^{m}-z_{i},每当一个音符奏响时,答案加上该音符的总优美程度(额外优美程度+初始优美程度)。

这样当两个音符u和v可以共鸣时,第一次加上u的总优美程度,这时额外优美程度会抵消掉初始的-z,而当第二次加上v的总优美程度,这时就会多一个额外优美程度z。

刚好符合题意,思想有点类似于前缀和,比较巧妙的题。

class Solution {
public:
    /**
     * 代码中的类名、方法名、参数名已经指定,请勿修改,直接返回方法规定的值即可
     * 
     * @param n int整型 
     * @param m int整型 
     * @param a int整型vector 
     * @param b int整型vector<vector<>> 
     * @return long长整型
     */
    long long wwork(int n, int m, vector<int>& a, vector<vector<int> >& b) {
       vector<long long> aa;aa.clear();
       for(int i=0;i<a.size();i++) aa.push_back(a[i]);
       long long res=0;
        for(int i=0;i<b.size();i++){
           aa[b[i][0]-1]+=b[i][2];
           aa[b[i][1]-1]+=b[i][2];
           res-=b[i][2];
        }
        for(int i=0;i<aa.size();i++){
            res+=max(aa[i],0ll);
        }
        return res;
    }
};

 

 
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值