模拟赛(day6)总结
第一题:约数研究
题目大意:
求 \(\sum_{i=1}^n \lfloor \frac{n}{i} \rfloor\)
强烈要求\(n\)开大,不然暴力都可以过
讲一种很快的方法——整除分块
我们可以知道\(\lfloor \frac{n}{i} \rfloor\)的值在某一段区间\([l,r]\)是相同的,
所以我们可以求出\(l\)和\(r\)
我们设\(x\)为在\(\lfloor \frac{n}{i} \rfloor=\lfloor \frac{n}{x} \rfloor\)的最大正整数
当\(\lfloor \frac{n}{i} \rfloor=\lfloor \frac{n}{x} \rfloor\)时,
\(\lfloor \frac{n}{x} \rfloor\)满足\(\lfloor \frac{n}{i} \rfloor<\frac{n}{x} \leq \lfloor \frac{n}{i} \rfloor+1\)
不等式两边同时乘以\(\frac{1}{n}\),得\(\frac{n}{\lfloor \frac{n}{i} \rfloor+1} \leq x < \frac{n}{\lfloor \frac{n}{i} \rfloor}\)
故,\(x\)为\(\lfloor \frac{n}{\lfloor \frac{n}{i} \rfloor} \rfloor\)
那么,当\(l=\lfloor \frac{n}{i} \rfloor\)时,\(r=\lfloor \frac{n}{\lfloor \frac{n}{l} \rfloor} \rfloor\)
时间复杂度为\(O(\sqrt{n})\)
代码
#include<cstdio>
using namespace std;
int main()
{
int ans=0,n;
scanf("%d",&n);
for (int l=1,r;l<=n;l=r+1)
{
r=n/(n/l);
ans+=(n/l)*(r-l+1);
}
printf("%d\n",ans);
}
第二题:逆序对数列
题目大意:
对于一个数列\(a\),如果有\(i<j\)且\(a_i>a_j\),那么我们称\(a_i\)与\(a_j\)为一对逆序对数。若对于任意一个由\(1-n\)自然数组成的数列,可以很容易求出有多少个逆序对数。那么逆序对数为\(k\)的这样自然数数列到底有多少个?
考虑DP
我们设\(f[i][j]\)表示当前选到第\(i\)个数,逆序对个数为\(j\)。
转移方程就是
\(k\)为 \(0\) 到 \(i-1\) ,表示选第\(i\)个数时,增加的逆序对个数。
答案为 \(f[n][k]\)
时间复杂度为 \(O(n^3)\)
但是,这样还是过不了。
考虑 \(j-k\) 的取值,为\([j-i+1,j]\)
那我们就可以用前缀和来维护
那转移方程就变成
还要考虑 \(j-i \leq 0\) 的情况
这样时间复杂度为\(O(n^2)\)
代码
#include<cstdio>
#include<iostream>
using namespace std;
int f[1005][1005],s[1005];
int main()
{
int n,m;
scanf("%d%d",&n,&m);
f[1][0]=1;
for (int i=0;i<=m;i++) s[i]=1;
for (int i=2;i<=n;i++)
{
for (int j=0;j<=m;j++)
if (j-i+1<=0) f[i][j]=(f[i][j]+s[j])%10000;
else f[i][j]=(f[i][j]+s[j]+10000-s[j-i]+10000)%10000;
s[0]=f[i][0];
for (int j=1;j<=m;j++)
s[j]=(s[j-1]+f[i][j])%10000;
}
printf("%d",f[n][m]);
}
第三题:旅行
题目大意:
Z小镇附近共有N个景点(编号为1,2,3,…,N),这些景点被M条道路连接着,所有道路都是双向的,两个景点之间可能有多条道路。也许是为了保护该地的旅游资源,Z小镇有个奇怪的规定,就是对于一条给定的公路Ri,任何在该公路上行驶的车辆速度必须为Vi。速度变化太快使得游客们很不舒服,因此从一个景点前#### 往另一个景点的时候,大家都希望选择行使过程中最大速度和最小速度的比尽可能小的路线,也就是所谓最舒适的路线。
一道最小生成树,版题
#pragma GCC optimize(3)
#include<cstdio>
#include<iostream>
#include<algorithm>
using namespace std;
int x,y,n,m,ansx,ansy,vis[40000],h[40000],tot=0,fa[40000];
long double ans=40000.000000;
struct edge{
int to,nxt,z,from;
}e[20005];
inline void add(int x,int y,int z)
{
e[++tot].to=y;
e[tot].nxt=h[x];
e[tot].from=x;
e[tot].z=z;
h[x]=tot;
}
bool cmp(edge x,edge y)
{
return x.z<y.z;
}
inline int find(int x)
{
if (fa[x]==x) return x;
else return fa[x]=find(fa[x]);
}
int main()
{
scanf("%d%d",&n,&m);
for (int i=1;i<=m;i++)
{
int q,p,c;
scanf("%d%d%d",&q,&p,&c);
add(q,p,c),add(p,q,c);
}
scanf("%d%d",&x,&y);
sort(e+1,e+1+tot,cmp);
for (register int i=1;i<=tot;i++)
{
for (register int j=1;j<=n;j++) fa[j]=j;
for (register int j=i;j<=tot;j++)
{
if (1.0*e[j].z>ans*e[i].z) break;
int u=find(e[j].from),v=find(e[j].to);\\本代码没写启发式合并,会很慢,要卡卡常
if (u!=v) fa[u]=fa[v];
if (find(x)==find(y))
{
ansx=e[j].z,ansy=e[i].z;
ans=1.0*ansx/ansy;
break;
}
}
}
if (ans==40000)
{
printf("IMPOSSIBLE\n");
return 0;
}
int q=ansx,p=ansy;
int c=q%p;
while (c!=0) q=p,p=c,c=q%p;
ansx/=p,ansy/=p;
if (ansy==1) printf("%d\n",ansx);
else printf("%d/%d\n",ansx,ansy);
}
第四题:繁忙的都市
题目大意:
城市中有n个交叉路口,有些交叉路口之间有道路相连,两个交叉路口之间最多有一条道路相连接。这些道路是双向的,且把所有的交叉路口直接或间接的连接起来了。每条道路都有一个分值,分值越小表示这个道路越繁忙,越需要进行改造。但是市政府的资金有限,市长希望进行改造的道路越少越好,于是他提出下面的要求: 1. 改造的那些道路能够把所有的交叉路口直接或间接的连通起来。 2. 在满足要求1的情况下,改造的道路尽量少。 3. 在满足要求1、2的情况下,改造的那些道路中分值最大的道路分值尽量小。任务:作为市规划局的你,应当作出最佳的决策,选择那些道路应当被修建。
又是一道最小生成树版题
#include<cstdio>
#include<iostream>
using namespace std;
int d[200000],vis[200000],h[200000],tot=0;
struct edge{
int to,nxt,z;
}e[200000];
void add(int x,int y,int z)
{
e[++tot].to=y;
e[tot].nxt=h[x];
e[tot].z=z;
h[x]=tot;
}
int main()
{
int n,m;
scanf("%d%d",&n,&m);
for (int i=1;i<=m;i++)
{
int q,p,c;
scanf("%d%d%d",&q,&p,&c);
add(q,p,c),add(p,q,c);
}
int t=0,ans=-2147483647;
d[++t]=1;vis[1]=1;
while (t<n)
{
int g=2147483647,g1;
for (int i=1;i<=t;i++)
for(int j=h[d[i]];j;j=e[j].nxt)
if (vis[e[j].to]==0&&g>e[j].z)
{
g=e[j].z;
g1=e[j].to;
}
d[++t]=g1,vis[g1]=1;
ans=max(ans,g);
}
printf("%d %d\n",n-1,ans);
}