2020牛客暑期多校训练营(第五场) B、D、E

 

Graph

解法:

完全图最小生成树问题

由boruvka算法我们受到启发,两个连通块(也是最小生成树)直接连一条可以连的权值最小的边一定构成最小生成树。先预处理每个节点到根节点的异或和dis[i],再从二进制高位开始,分治计算,把该数位为1的当一个联通块,为0的一个连通块,这样该数位1的贡献只有一个,保证最小合并后也是最小生成树。当对两个连通块进行连边时,用字典树维护,求出联通两个连通块的最小边权,加上边权即可

#include<bits/stdc++.h>
#define pai pair<int,int>
#define ll long long
using namespace std;
vector<pai> G[100005];
int dis[100005];
int tr[3000005][2];
ll ans=0;
int tot;
void dfs(int u,int fa)
{
    for(auto T:G[u])
    {
        if(T.first==fa) continue;
        dis[T.first]=dis[u]^T.second;
        dfs(T.first,u);
    }
}

void insert(int x)
{
    int p=0;
    for(int i=29;i>=0;i--)
    {
        int d=(x>>i)&1;
        if(tr[p][d]==0)
        {
           tr[p][d]=++tot;
        }
        p=tr[p][d];
    }

}
int qu(int x)
{
    int p=0;
    int res=0;
    for(int i=29;i>=0;i--)
    {
        int d=(x>>i)&1;
        if(tr[p][d])
        {
            p=tr[p][d];
        }
        else
        {
            res|=(1<<i);
            p=tr[p][d^1];
        }
    }
    return res;
}

void dfs1(int l,int r,int dep)
{
    if(l>r||dep<0) return ;
    int mid=l-1;
    while(mid+1<=r&&(dis[mid+1]&(1<<dep))==0) mid++;
    dfs1(l,mid,dep-1);  //分治 解决子问题
    dfs1(mid+1,r,dep-1);
    if(mid==r||mid==l-1) return ;//!!
    int tmp=2e9;
    for(int i=l;i<=mid;i++) insert(dis[i]);
    for(int i=mid+1;i<=r;i++)
    {
        tmp=min(tmp,qu(dis[i]));

    }
    ans+=1ll*tmp;
    for(int i=0;i<=tot;i++) tr[i][0]=tr[i][1]=0;
    tot=0;
}
int main()
{
    int n;
    scanf("%d",&n);
    for(int i=0;i<n-1;i++)
    {
        int u,v,w;
        scanf("%d%d%d",&u,&v,&w);
        u++;
        v++;
        G[u].push_back(make_pair(v,w));
        G[v].push_back(make_pair(u,w));
    }

    dfs(1,0);
    sort(dis+1,dis+1+n);
    dfs1(1,n,29);
    printf("%lld\n",ans);
}

 

 

Drop Voicing

解法:

观察可发现:

规律1:连续多次使用操作1,相当于将前n-1个数不断内循环调换

规律2:连续多次使用操作2,相当于将n个数不断内循环调换

规律3:多次使用操作1后,再使用操作2,将a[0]放去末尾 ,相当于,取前n-1中的任一个数放到a[n-1]

规律3可得:通过这两种操作的组合,可以将任意一个数放到任意一个位置

那么题目可转化为求:通过最少的组合操作调换,使得目标序列sorted

枚举n个最长上升子序列的长度取max

答案就是总长度减去max   ans=n-max

#include<bits/stdc++.h>
using namespace std;
int n;
int a[505];

int gao(int i)
{
	vector<int> v;
	for(int j=0;j<n;j++)
	{
			int jj=lower_bound(v.begin(),v.end(),a[(i+j)%n])-v.begin();
			if(jj!=v.size())v[jj]=a[(i+j)%n];
				else v.push_back(a[(i+j)%n]);
	}
	return (int)v.size() ;
}


int main()
{
	scanf("%d",&n);
	for(int i=0;i<n;i++)
	{
		scanf("%d",&a[i]);
	}
	int ans=0;
	for(int i=0;i<n;i++)
	{
		ans=max(ans,gao(i));
	}
	printf("%d\n",n-ans);
    return 0;
}

Bogo Sort

题意:给定置换规则p数组,求最多有多少不同的序列可通过多次置换变成序列

解法:在置换规则中一定存在多个置换环,即环内数的位置只可能在该环内循环变化,置换周期为环内元素个数

那么答案即为所有置换环大小的lcm

我是用的java大数类BigInteger

__int128


import java.io.IOException;
import java.math.BigInteger;
import java.util.Scanner;


public class Main {
    static int maxn = 200010;
    static int[] a=new int[maxn];
    static int[] vis=new int[maxn];
    static int[] to=new int[maxn]; 
    static BigInteger[] b = new BigInteger[maxn];
    static int tot=0;
    static BigInteger num=BigInteger.ONE;

    public static void main(String[] args) throws IOException {
        int n;
        Scanner in;
        Scanner sc = new Scanner(System.in);
         
        n=sc.nextInt();
        for(int i=0;i<n;i++)
        {
            num.multiply(BigInteger.TEN);
        }
        for(int i=1;i<=n;i++)  {
            vis[i]=0;
        a[i]=sc.nextInt();
        to[i] = a[i];
        }
 
        int c = 0;
        for(int i=1;i<=n;i++)
        {
            tot = 0;
            if(vis[i]==0)
            {                
               int now=i;
                while(vis[now]==0) {
                    vis[now]=1;
                    now=a[now];
                    tot++;
                }                               
            b[++c] =BigInteger.valueOf(tot);            
            }
        }
        BigInteger gcd=b[1],lcm=b[1];        
        for(int i=2;i<=c;i++)
        {
            gcd=lcm.gcd(b[i]);
            lcm=lcm.multiply(b[i]).divide(gcd);        
        }
        lcm.mod(num);         
        System.out.println(lcm.toString());
    }
}

C++ 大数模板:

struct bign{ 
    int d[maxn], len; 
   
    void clean() { while(len > 1 && !d[len-1]) len--; } 
   
    bign()          { memset(d, 0, sizeof(d)); len = 1; } 
    bign(int num)   { *this = num; }  
    bign(char* num) { *this = num; } 
    bign operator = (const char* num){ 
        memset(d, 0, sizeof(d)); len = strlen(num); 
        for(int i = 0; i < len; i++) d[i] = num[len-1-i] - '0'; 
        clean(); 
        return *this; 
    } 
    bign operator = (int num){ 
        char s[20]; sprintf(s, "%d", num); 
        *this = s; 
        return *this; 
    } 
   
    bign operator + (const bign& b){ 
        bign c = *this; int i; 
        for (i = 0; i < b.len; i++){ 
            c.d[i] += b.d[i]; 
            if (c.d[i] > 9) c.d[i]%=10, c.d[i+1]++; 
        } 
        while (c.d[i] > 9) c.d[i++]%=10, c.d[i]++; 
        c.len = max(len, b.len); 
        if (c.d[i] && c.len <= i) c.len = i+1; 
        return c; 
    } 
    bign operator - (const bign& b){ 
        bign c = *this; int i; 
        for (i = 0; i < b.len; i++){ 
            c.d[i] -= b.d[i]; 
            if (c.d[i] < 0) c.d[i]+=10, c.d[i+1]--; 
        } 
        while (c.d[i] < 0) c.d[i++]+=10, c.d[i]--; 
        c.clean(); 
        return c; 
    } 
    bign operator * (const bign& b)const{ 
        int i, j; bign c; c.len = len + b.len;  
        for(j = 0; j < b.len; j++) for(i = 0; i < len; i++)  
            c.d[i+j] += d[i] * b.d[j]; 
        for(i = 0; i < c.len-1; i++) 
            c.d[i+1] += c.d[i]/10, c.d[i] %= 10; 
        c.clean(); 
        return c; 
    } 
    bign operator / (const bign& b){ 
        int i, j; 
        bign c = *this, a = 0; 
        for (i = len - 1; i >= 0; i--) 
        { 
            a = a*10 + d[i]; 
            for (j = 0; j < 10; j++) if (a < b*(j+1)) break; 
            c.d[i] = j; 
            a = a - b*j; 
        } 
        c.clean(); 
        return c; 
    } 
    bign operator % (const bign& b){ 
        int i, j; 
        bign a = 0; 
        for (i = len - 1; i >= 0; i--) 
        { 
            a = a*10 + d[i]; 
            for (j = 0; j < 10; j++) if (a < b*(j+1)) break; 
            a = a - b*j; 
        } 
        return a; 
    } 
    bign operator += (const bign& b){ 
        *this = *this + b; 
        return *this; 
    } 
   
    bool operator <(const bign& b) const{ 
        if(len != b.len) return len < b.len; 
        for(int i = len-1; i >= 0; i--) 
            if(d[i] != b.d[i]) return d[i] < b.d[i]; 
        return false; 
    } 
    bool operator >(const bign& b) const{return b < *this;} 
    bool operator<=(const bign& b) const{return !(b < *this);} 
    bool operator>=(const bign& b) const{return !(*this < b);} 
    bool operator!=(const bign& b) const{return b < *this || *this < b;} 
    bool operator==(const bign& b) const{return !(b < *this) && !(b > *this);} 
   
    string str() const{ 
        char s[maxn]={}; 
        for(int i = 0; i < len; i++) s[len-1-i] = d[i]+'0'; 
        return s; 
    } 
}; 
   
istream& operator >> (istream& in, bign& x) 
{ 
    string s; 
    in >> s; 
    x = s.c_str(); 
    return in; 
} 
   
ostream& operator << (ostream& out, const bign& x) 
{ 
    out << x.str(); 
    return out; 
}

 

  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 2
    评论
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值