2021杭电多校8

1003 Ink on paper (最小生成树)

任意门
题意:
给出n个点,每个点以0.5m/s的速度来扩展,然后问多久之后这几个点全部连在一起,输出时间的平方。

思路:
最小生成树板子,找到最长的把一条边,除以0.5,由于两个点是双向一起走的,所以 t 2 t^2 t2 = = = ( d 2 0.5 ) 2 (\frac{\frac{d}{2}}{0.5})^2 (0.52d)2 = d 2 =d^2 =d2

最小生成树

图的最小生成树,就是在这些边中选择N-1条出来,连接所有的N个点。这N-1条边的边权之和是所有方案中最小的。
其应用(举个栗子):
要在n个城市之间铺光缆,要求任意两个城市都可以通讯,由于每个城市所要铺的光缆的费用不同,选择一个花费最小的方法,这就考虑了带权最小生成树。
**相应算法:**最小生成树的算法就是prim算法和kruskal算法。

#include<iostream>
#include<algorithm>
#include<cstring>

using namespace std;

typedef long long ll;
const ll N=5005, inf=9e18;
ll x[N], y[N], a[N][N], d[N];
bool vis[N];
void prim(ll n)
{
    for(ll i=1; i<=n; i++) vis[i]=0, d[i]=inf;
    d[1]=0;//表示从1这个点开始
    for(ll i=1; i<n; i++)
    {
        ll mn=inf, p;
        for(ll j=1; j<=n; j++)
        {
            if(!vis[j] && d[j]<mn)
            {
                mn=d[j];
                p=j;
            }
        }
        vis[p]=1;
        for(ll j=1; j<=n; j++)
        {
            if(!vis[j]) d[j]=min(d[j], a[p][j]);
        }
    }
    ll mx=0;
    for(ll i=1; i<=n; i++) mx = max(d[i], mx);
    printf("%lld\n", mx);
}
signed main()
{
    ll T;
    scanf("%lld",&T);
    while(T--)
    {
        ll n;
        scanf("%lld",&n);
        for(ll i=1; i<=n; i++)
        {
            scanf("%lld%lld",&x[i],&y[i]);
        }
        for(ll i=1; i<=n; i++)
        {
            for(ll j=1; j<=n; j++)
            {//记录下他们距离的平方
                a[i][j] = a[j][i] = (x[i]-x[j])*(x[i]-x[j])+(y[i]-y[j])*(y[i]-y[j]);
            }
        }
        prim(n);
    }
    return 0;
}

二分建图+并查集联通性

#include<iostream>
#include<algorithm>
#include<cstring>

using namespace std;

typedef long long ll;
const int maxn=5050;
int fa[maxn+10];

void init(int n)//并查集的初始化操作
{
    for(int i=1;i<=n;i++)
        fa[i]=i;
}

int find(int x)//并查集的寻找父节点
{
    return x==fa[x]?x:fa[x]=find(fa[x]);
}

void merge(int x,int y)
{
    x=find(x);
    y=find(y);
    if(x!=y)
        fa[x]=y;
}

ll n,d[maxn][maxn];
struct node
{
    ll x,y;
}a[maxn];

ll get(node x,node y)
{
    return (x.x-y.x)*(x.x-y.x)+(x.y-y.y)*(x.y-y.y);
}

bool check(ll t)
{
    init(n);
    int cnt=1;
    for(int i=1;i<=n;i++)
    {
        for(int j=i+1;j<=n;j++)
            if(t>=d[i][j])
            if(find(i)!=find(j))
        {
            cnt++;
            if(cnt==n)
                break;
            merge(i,j);
        }
        if(cnt==n)break;
    }
    return cnt==n;

}

int main()
{
    ios::sync_with_stdio(0),cin.tie(0),cout.tie(0);
    int T;
    cin>>T;
    while(T--)
    {
        cin>>n;
        for(int i=1;i<=n;i++)
            cin>>a[i].x>>a[i].y;
        for(int i=1;i<=n;i++)
            for(int j=i+1;j<=n;j++)
            d[i][j]=get(a[i],a[j]);
        ll l=0,r=1e15+10;
        while(l<r)
        {
            ll mid =(l+r)/2;
            if(check(mid))
                r=mid;
            else l=mid+1;
        }
        cout<<r<<endl;
    }
    return 0;
}

Runtime error(ACCESS_VIOLATION)

我在写这道题的时候,最常见的错误就是这个了

如果出现了这个问题,那就检查一下题目所给的数据范围和所开的数组,是否数组开小了,或者非法访问了,或检查一下scanf有没有少了一个%d


1006 GCD Game(nim游戏+欧拉筛)

任意门
题意:
给出n个数字ai,A和B轮流操作,每次任意选一个数ai,用任意x(1<=x<ai)计算gcd(ai, x)替换ai,不能操作的一方时输。

思路:
gcd是取最大公约数,将每一个数看作一堆石头,每个数都可以分解成质因子相乘的形式, a i = x ∗ y ∗ z a_i=x*y*z ai=xyz,然后,当一个人操作选择了 x ∗ y x*y xy的时候, g c d ( x ∗ y , a i ) = x ∗ y gcd(x*y,a_i)=x*y gcd(xy,ai)=xy相当于将z取走了,其实也就是每次取走一个或者多个至一逆转,那么质因子的个数就相当于石头的数量,每次可以取任意个,这就是nim游戏

nim游戏

这个是博弈论当中最经典的模型

有若干堆石子,每堆石子的数量都是有限的,合法的移动是“选择一堆石子并拿走若干颗(不能不拿)”,如果轮到某个人时所有的石子堆都已经被拿空了,则判负(因为他此刻没有任何合法的移动)。

必胜状态(a1^ a2 …^an!=0)
必败状态(a1^ a2 …^an==0)

#include<iostream>
#include<algorithm>
#include<vector>
#include<string.h>

using namespace std;

typedef long long ll;

const int N=1e7+10;

int num[N];//记录数i的质因数有多少个

int vis[N],primes[N],cnt;//欧拉筛模板
void get_primes(int n)
{
    vis[0]=vis[1]=1;
    for(int i=2;i<n;i++)
    {
        if(!vis[i])primes[++cnt]=i,num[i]=1;
        for(int j=1;primes[j]<=n/i;j++)
        {
            vis[primes[j]*i]=1;
            num[primes[j]*i]=num[i]+1;
            if(i%primes[j]==0)break;
        }

    }
}

int main()
{
    ios::sync_with_stdio(0),cin.tie(0),cout.tie(0);
    get_primes(N-10);
    int T;
    cin>>T;
    while(T--)
    {
        int n;
        cin>>n;
        int ans=0;
        for(int i=1;i<=n;i++)
        {
            int x;
            cin>>x;
            ans^=num[x];
        }
        if(ans!=0)
            cout<<"Alice\n";
        else cout<<"Bob\n";
    }
    return 0;
}

埃及筛

const int N = 1e7 + 5;
int st[N];
void E_sieve(int  n){

	for(int i = 2; i <= n; i++)
	{
		if(st[i] == 0)
		{
			for(int j = 2 * i; j <= n; j += i)
			    st[j] = 1; // j是i的一个倍数,j是合数,筛掉。
		}
	}
	
}

这个时间复杂度 O ( n log ⁡ l o g 2 n ) O(n\log^{log{2}{n}}) O(nloglog2n)
不过这个会有很多重复不必要的地方,比如如果我们扫描过 2 ∗ 3 2*3 23,那么在3的时候又会再次扫描 3 ∗ 2 3*2 32,所以这个时候,我们可以进行优化

const int N = 1e7 + 5;
int st[N];
void E_sieve(int  n){

	for(int i = 2; i <= n; i++)
	{
		if(st[i] == 0)
		{
			for(int j = i * i; j <= n; j += i)
			    st[j] = 1; 
		}
	}
	
}

这个时候,他的时间复杂度近似为 O ( n ) O(n) O(n)

欧拉筛

欧拉筛又叫线性筛,时间复杂度 O ( N ) O(N) O(N)
埃及筛是筛去一个质数的所有倍数,但合数会被不同的质因子筛出多次,从而导致时间浪费。
所以欧拉筛改进了这一点,我们不需要用一个for循环去筛除一个质数的所有倍数,我们将所有质数存储到primes[]中,然后枚举到第i个数时,就筛去所有的primes[j] * i,这样子保证了不会重复删除。这样就在每一次遍历中,正好筛除了所有已知素数的i倍。

i%primes[j]==0时break是为了确保每个合数只被最小质因数筛掉。或者说是被合数的最大因子筛掉

const int N = 1e7 + 5;
int st[N],primes[N];
void ola(int n)
{
    for (int i = 2; i <= n; i ++ )
    {
        if (st[i] == 0) primes[cnt ++ ] = i;//将质数存到primes中
        for (int j = 0; primes[j] <= n / i; j ++ )//要确保质数的第i倍是小于等于n的。
        {
            st[primes[j] * i] = 1;
            if (i % primes[j] == 0) break;
        }
    }
}

1009 Singing Superstar(ac自动机+字符串)

任意门
题意:
给你一个很长的字符串,然后给你几次询问,问这个短的字符串在长的字符串中出现过几次。根据他给的例子:aba在abababa中只出现了两次可知,如果记录一次,就不能再算这次了

思路:
这题一开始想简单了,后来才知道是ac自动机

ac自动机

ac自动机就是kmp+Trie
他就是以Trie结构为基础,以kmp思想建立的

对于允许重叠的情况,就是裸的AC自动机,不允许重叠的只需要记录trie树上每个结尾节点一次匹配是在长串的哪个位置,若这次与上次不重叠则记录。

printf和cin一起用,关了流同步会WA!!!

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值