2021.7.19 2021.7.19 2021.7.19 模拟赛 Ⅶ Ⅶ Ⅶ
由于放假一天 所以模拟赛编号 与游记编号会不一致
目录:
T1.玉米田(加强版)
T2.公约数的和
T3.只不过是长的领带
T4.概率充电器
T 1 : T1: T1:玉米田 ( ( (加强版 ) ) )
L
u
o
g
u
Luogu
Luogu
l
i
n
k
link
link
分析:
洛谷的是弱化版
这个卡了空间 要用滚动数组 还要卡常
状压
d
p
+
dp~+
dp + 滚动数组
+
+
+ 卡常是过不了的 但我是打表
+
+
+ 卡常
+
+
+ 状压
d
p
dp
dp
+
+
+ 滚动数组嘿嘿
这个就不贴了
正解
:
:
:轮廓线
d
p
+
dp~+
dp + 滚动数组
+
+
+ 卡常
假设
d
p
dp
dp到了点
x
x
x 因为
x
x
x的后面和下面都还没有更新
那
x
x
x的取舍取决于 它的左边和上边
然后取出
u
p
up
up和
l
e
f
t
left
left的状态 处理不合法的状态 再转移
k k k为轮廓线的状态 方程:
f[i][k]+=f[i-1][k]; //不取(i,j)
f[i][k^(1<<j)]+=f[i-1][k]; //取(i,j)
CODE:
#include<iostream>
#include<cstdio>
#include<algorithm>
#pragma GCC optimize(2)
#include<cstring>
#define reg register
using namespace std;
const int N=20,Mod=100000000;
int n,m,a[N][N],f[2][(1<<N)],x;
int main(){
// freopen("cowfood.in","r",stdin);
// freopen("cowfood.out","w",stdout);
scanf("%d%d",&n,&m);
for(reg int i=0;i<n;i++)
for(reg int j=0;j<m;j++)
scanf("%d",&a[i][j]);
f[0][0]=1;
for(reg int i=0;i<n;i++)
for(reg int j=0;j<m;j++)
{
x^=1;
memset(f[x],0,sizeof(f[x]));
for(reg int k=0;k<(1<<m);k++)
{
int up=(1<<j)&k,left=(j>0)?(1<<(j-1))&k:0;
if((i==0&&up)||(j==0&&left)) continue;
if(up)
{
f[x][k^(1<<j)]=(1ll*f[x][k^(1<<j)]+f[x^1][k])%Mod;
continue;
}
if(left||a[i][j]==0)
{
f[x][k]=(1ll*f[x][k]+f[x^1][k])%Mod;
continue;
}
f[x][k]=(1ll*f[x][k]+f[x^1][k])%Mod;
f[x][k^(1<<j)]=(1ll*f[x][k^(1<<j)]+f[x^1][k])%Mod;
}
}
int ans=0;
for(reg int i=0;i<(1<<m);i++)
ans=(1ll*ans+f[x][i])%Mod;
printf("%d",ans);
return 0;
}
T 2 : T2: T2:公约数的和
L
u
o
g
u
Luogu
Luogu
l
i
n
k
link
link
分析:
洛谷的是弱化版
原式化为
∑
i
=
1
n
∑
j
=
1
n
g
c
d
(
i
,
j
)
∑
d
=
1
n
d
∑
i
=
1
n
∑
j
=
1
n
[
g
c
d
(
i
,
j
)
=
d
]
∑
d
=
1
n
d
∑
i
×
d
<
=
n
∑
j
×
d
<
=
n
[
g
c
d
(
i
,
j
)
=
1
]
∑
d
=
1
n
d
(
2
∑
i
=
1
⌊
n
d
⌋
φ
(
i
)
−
1
)
\sum_{i=1}^n\sum_{j=1}^ngcd(i,j)\\ \sum_{d=1}^nd\sum_{i=1}^n\sum_{j=1}^n~[gcd(i,j)=d]\\ \sum_{d=1}^nd\sum_{i\times d<=n}\sum_{j\times d<=n}~[gcd(i,j)=1]\\ \sum_{d=1}^nd(2\sum_{i=1}^{⌊\frac{n}{d}⌋ }φ(i)-1)
i=1∑nj=1∑ngcd(i,j)d=1∑ndi=1∑nj=1∑n [gcd(i,j)=d]d=1∑ndi×d<=n∑j×d<=n∑ [gcd(i,j)=1]d=1∑nd(2i=1∑⌊dn⌋φ(i)−1)
后面 2 ∑ i = 1 ⌊ n d ⌋ φ ( i ) − 1 2\sum_{i=1}^{⌊\frac{n}{d}⌋ }φ(i)-1 2∑i=1⌊dn⌋φ(i)−1可以线性求欧拉函数 然后前缀和
∑ i = 1 ⌊ n d ⌋ \sum_{i=1}^{⌊\frac{n}{d}⌋ } ∑i=1⌊dn⌋可以整除分块做 复杂度 O ( T n ) O(T\sqrt n) O(Tn)
CODE:
#include<iostream>
#include<cstdio>
#include<algorithm>
#include<cstring>
#define reg register
#pragma GCC optimize(2)
using namespace std;
typedef long long ll;
const int N=1e6+5;
int T,prime[N],phi[N],tot,vis[N];
ll f[N],n,sum[N];
void Phi()
{
phi[1]=1;
for(reg int i=2;i<=N;i++)
{
if(!vis[i])
{
phi[i]=i-1;
prime[++tot]=i;
}
for(reg int j=1;prime[j]*i<=N&&j<=tot;j++)
{
vis[prime[j]*i]=1;
if(i%prime[j]==0)
{
phi[prime[j]*i]=phi[i]*prime[j];
break;
}
phi[prime[j]*i]=phi[i]*(prime[j]-1);
}
}
for(reg int i=1;i<=N;i++)
sum[i]=sum[i-1]+phi[i];
}
ll query(ll x)
{
ll res=0;
for(ll l=1,r=1;l<=x;l=r+1)
{
r=x/(x/l);
res+=(2*sum[x/l]-1)*(r-l+1)*(r+l)/2;
}
ll qwq=x*(x+1)/2;
return (res-qwq)/2;
}
int main(){
scanf("%d",&T);
Phi();
while(T--)
{
scanf("%lld",&n);
printf("%lld\n",query(n));
}
return 0;
}
T 3 : T3: T3:只不过是长的领带
L
u
o
g
u
l
i
n
k
Luogu~link
Luogu link
分析:
很简单的贪心思路 : : : 小的配对小的 大的配对大的
但
n
2
n^2
n2枚举也不行 就用
s
u
m
i
sum_i
sumi和
s
u
f
i
suf_i
sufi 记录
i
i
i前面和后面的最大奇怪感
然后
s
u
m
sum
sum和
s
u
f
suf
suf取
m
a
x
max
max即可
CODE:
#include<iostream>
#include<cstdio>
#include<algorithm>
#include<cstring>
using namespace std;
const int N=2e5+5;
int n,b[N],tmp,ans[N],sum[N],suf[N];
struct qaq{
int val,id;
}a[N];
bool cmp(qaq x,qaq y){return x.val<y.val;}
int main(){
scanf("%d",&n);
for(int i=1;i<=n+1;i++)
{
scanf("%d",&a[i].val);
a[i].id=i;
}
for(int i=1;i<=n;i++)
scanf("%d",&b[i]);
sort(b+1,b+n+1);
sort(a+1,a+n+2,cmp);
for(int i=1;i<=n;i++)
sum[i]=max(sum[i-1],max(a[i].val-b[i],0));
for(int i=n+1;i>1;i--)
suf[i]=max(suf[i+1],max(a[i].val-b[i-1],0));
for(int i=1;i<=n+1;i++)
ans[a[i].id]=max(sum[i-1],suf[i+1]);
for(int i=1;i<=n+1;i++)
printf("%d ",ans[i]);
return 0;
}
T 4 : T4: T4:概率充电器
L
u
o
g
u
l
i
n
k
Luogu~link
Luogu link
分析:
g m o j gmoj gmoj上的数据更强点 这个做法爆栈了 ( l u o g u (luogu (luogu可过 ) ) )
设
i
i
i自己亮的概率为
t
i
t_i
ti 边
(
x
,
y
)
(x,y)
(x,y)通电概率为
e
d
g
e
x
,
y
edge_{x,y}
edgex,y
p
x
p_x
px为点
x
x
x不亮的概率 因为如果设亮的概率 式子会比较麻烦
p
x
=
(
1
−
t
x
)
∏
x
,
y
∈
E
(
1
−
e
d
g
e
x
,
y
+
e
d
g
e
x
,
y
×
p
y
)
p_x=(1-t_x)\prod_{x,y∈E}(1-edge_{x,y}+edge_{x,y}\times p_y)
px=(1−tx)x,y∈E∏(1−edgex,y+edgex,y×py)
即自己不能亮 边也不能通电 边导电相邻点就不能导电
然后要换根
d
p
dp
dp
f
x
f_x
fx为
x
x
x不被它子树的点 点亮的概率
f
x
=
(
1
−
t
x
)
∏
y
∈
s
o
n
x
(
1
−
e
d
g
e
x
,
y
+
e
d
g
e
x
,
y
×
p
y
)
f_x=(1-t_x)\prod_{y∈son_x}(1-edge_{x,y}+edge_{x,y}\times p_y)
fx=(1−tx)y∈sonx∏(1−edgex,y+edgex,y×py)
d
f
s
dfs
dfs求出
f
f
f 这时根节点的答案是求出的 但其他点还没有
设
g
x
g_x
gx表示以
x
x
x为根
x
x
x的答案
分为两部分:原来的子树 剩余的部分
x
x
x原来的子树已得出了 剩余的部分就是
g
f
a
g_{fa}
gfa减去
x
x
x对
f
a
fa
fa的贡献
P
=
g
f
a
1
−
e
d
g
e
f
a
,
x
+
e
d
g
e
f
a
,
x
×
f
x
P=\frac{g_{fa}}{1-edge_{fa,x}+edge_{fa,x}\times f_x}
P=1−edgefa,x+edgefa,x×fxgfa
g
x
=
f
x
(
1
−
e
d
g
e
f
a
,
x
+
e
d
g
e
f
a
,
x
×
P
)
g_x=f_x(1-edge_{fa,x}+edge_{fa,x}\times P)
gx=fx(1−edgefa,x+edgefa,x×P)
再跑 d f s dfs dfs求出 g g g 最后统计 1 − g i 1-g_i 1−gi就是通电的概率了
b f s bfs bfs做法不会爆栈 后面会 u p d a t e update update
CODE:
#include<iostream>
#include<cstdio>
#include<algorithm>
#include<cstring>
using namespace std;
const int N=5e5+5;
int n,tot,head[N];
double p[N],ans,f[N],ovo[N];
struct node{
int to,next;
double k;
}a[N<<1];
void add(int x,int y,double k)
{
a[++tot]=(node){y,head[x],k};
head[x]=tot;
}
void dfs(int x,int fa)
{
f[x]=1-p[x];
for(int i=head[x];i;i=a[i].next)
{
int qwq=a[i].to;
if(qwq==fa) continue;
dfs(qwq,x);
f[x]=f[x]*(1-a[i].k+a[i].k*f[qwq]);
}
}
void query(int x,int fa,int els)
{
if(x==1) ovo[x]=f[x]; //x是根 已经求出就不用做了
else
{
double P=ovo[fa]/(1-a[els].k+a[els].k*f[x]);
ovo[x]=f[x]*(1-a[els].k+a[els].k*P);
}
for(int i=head[x];i;i=a[i].next)
{
int qwq=a[i].to;
if(qwq==fa) continue;
query(qwq,x,i);
}
}
int main(){
// freopen("charger.in","r",stdin);
// freopen("charger.out","w",stdout);
scanf("%d",&n);
for(int i=1,x,y,k;i<n;i++)
{
scanf("%d%d%d",&x,&y,&k);
add(x,y,k*0.01);
add(y,x,k*0.01);
}
for(int i=1;i<=n;i++)
{
scanf("%lf",&p[i]);
p[i]=p[i]*0.01;
}
dfs(1,0);
query(1,0,0);
for(int i=1;i<=n;i++)
ans+=1-ovo[i];
printf("%.6lf",ans);
return 0;
}
u p d : b f s upd:bfs upd:bfs做法
其实就是用
q
u
e
u
e
queue
queue代替
d
f
s
dfs
dfs的递归 但这样就不会爆栈了
注意最好记录下编号
( ( (手写栈 d f s dfs dfs不知道行不行 ) ) )
CODE:
#include<iostream>
#include<cstdio>
#include<algorithm>
#include<cstring>
#include<queue>
using namespace std;
const int N=5e5+5;
int n,tot,head[N],dfn[N];
double p[N],ans,f[N],ovo[N];
struct node{int to,next; double k;}a[N<<1];
struct qaq{int x,fa;};
struct awa{int id,dep; double valF;}kel[N];
struct ouo{int x,fa; double valG;};
void add(int x,int y,double k)
{
a[++tot]=(node){y,head[x],k};
head[x]=tot;
}
bool cmp(awa x,awa y){return x.dep>y.dep;}
void bfs()
{
queue<qaq> q;
q.push((qaq){1,0});
while(!q.empty())
{
qaq now=q.front();
int x=now.x,fa=now.fa;
q.pop();
for(int i=head[x];i;i=a[i].next)
{
int qwq=a[i].to;
if(qwq==fa) continue;
kel[qwq].dep=kel[x].dep+1;
q.push((qaq){qwq,x});
}
}
for(int i=1;i<=n;i++)
kel[i].id=i;
sort(kel+1,kel+n+1,cmp);
for(int i=1;i<=n;i++)
dfn[kel[i].id]=i;
for(int i=1;i<=n;i++)
{
kel[i].valF=1-p[kel[i].id];
for(int j=head[kel[i].id];j;j=a[j].next)
{
int qwq=dfn[a[j].to];
if(kel[i].dep+1==kel[qwq].dep)
kel[i].valF=kel[i].valF*(1-a[j].k+a[j].k*kel[qwq].valF);
}
}
for(int i=1;i<=n;i++)
f[kel[i].id]=kel[i].valF;
}
void query()
{
queue<ouo> q;
ovo[1]=f[1]; //同理 如果是根就不用做
q.push((ouo){1,0,0});
while(!q.empty())
{
ouo now=q.front();
int x=now.x,fa=now.fa;
q.pop();
for(int i=head[x];i;i=a[i].next)
{
int qwq=a[i].to;
if(qwq==fa) continue;
double P=ovo[x]/(1-a[i].k+a[i].k*f[qwq]);
ovo[qwq]=f[qwq]*(1-a[i].k+a[i].k*P);
q.push((ouo){qwq,x,a[i].k});
}
}
}
int main(){
// freopen("charger.in","r",stdin);
// freopen("charger.out","w",stdout);
scanf("%d",&n);
for(int i=1,x,y,k;i<n;i++)
{
scanf("%d%d%d",&x,&y,&k);
add(x,y,k*0.01);
add(y,x,k*0.01);
}
for(int i=1;i<=n;i++)
{
scanf("%lf",&p[i]);
p[i]=p[i]*0.01;
}
bfs();
query();
for(int i=1;i<=n;i++)
ans+=1-ovo[i];
printf("%.6lf",ans);
return 0;
}
赛后放了个 T 1 T1 T1的再加强版 插头 d p dp dp 我爬了