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=x∗y∗z,然后,当一个人操作选择了
x
∗
y
x*y
x∗y的时候,
g
c
d
(
x
∗
y
,
a
i
)
=
x
∗
y
gcd(x*y,a_i)=x*y
gcd(x∗y,ai)=x∗y相当于将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
2∗3,那么在3的时候又会再次扫描
3
∗
2
3*2
3∗2,所以这个时候,我们可以进行优化
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!!!