20240713比赛总结
这辈子不想讲题了
T1 炒币
https://gxyzoj.com/d/hzoj/p/3798
57pts:
dp,设\(dp_{0/1,i}\)表示第i天有人民币/比特币的最大值,mx,nx就是前面的最大值
\(dp_{0,i}=dp_{1,mx}\div a_i\)
\(dp_{1,i}=dp_{0,nx}\times a_i\)
但是显然会炸long double
solution 1:
因为我们不关心具体的值,只关心转移的指针,所以可以进行一些改进,可以通过比较比值来找到最值
可以发现,\(\dfrac{dp_{0,i}}{dp_{1,mx}}=\dfrac{dp_{0,nx}}{dp_{1,i}}=a_i\)
所以,当\(\dfrac{dp_{0,i}}{dp_{1,mx}}>\dfrac{dp_{0,mx}}{dp_{1,mx}}\)即\(a_i>a_{mx}\)时,就更新mx
当\(\dfrac{dp_{0,nx}}{dp_{1,i}}>\dfrac{dp_{0,nx}}{dp_{1,nx}}\)即\(a_i>a_{nx}\)时,就更新nx
代码:
#include<cstdio>
#include<iostream>
using namespace std;
int n,a[200005],pre1,pre2,pre[200005][2];
long double dp[200005][2],sum1,sum2,p;
int vis[200005];
int main()
{
// freopen("1.txt","r",stdin);
scanf("%d",&n);
for(int i=1;i<=n;i++)
{
scanf("%d",&a[i]);
}
sum1=1.0,pre1=0,sum2=a[1]*1.0,pre2=1;
p=sum2/sum1;
for(int i=2;i<=n;i++)
{
// dp[i][0]=sum2/(1.0*a[i]);
// dp[i][1]=sum1*(1.0*a[i]);
// pre[i][0]=pre2,pre[i][1]=pre1;
// if(dp[i][0]>sum1)
// {
// sum1=dp[i][0],pre1=i;
// }
// if(dp[i][1]>sum2)
// {
// sum2=dp[i][1],pre2=i;
// }
pre[i][0]=pre2,pre[i][1]=pre1;
if(a[i]*1.0>p) pre2=i;
if(a[i]*1.0<p) pre1=i;
p=1.0*a[i];
}
int lst=pre1,st=1;
// for(int i=1;i<=n;i++)
// {
// if(dp[i][0]>dp[lst][0])
// {
// lst=i;
// }
// }
while(lst!=0)
{
st^=1;
vis[lst]=1;
lst=pre[lst][st];
}
for(int i=1;i<=n;i++)
{
printf("%d ",vis[i]);
}
return 0;
}
solution 2:
低买高卖,直接贪心
在峰值买入,谷值卖出,为防止买后不卖出,可以将n+1设为inf
T2 凑数
https://gxyzoj.com/d/hzoj/p/3799
30pts:
暴力背包
30~80pts:
枚举1,a,b的个数+特判
100pts:
设\(\dfrac{y}{a}<\dfrac{z}{b}\)
当有a个b和b个a时,显然选b个a更优,所以,选b的个数不会超过a
而选a的次数不会超过\(\lfloor \dfrac{n}{a} \rfloor\),这两个数中,必然有一个不超过\(\sqrt{n}\),取最小的枚举即可
代码:
#include<cstdio>
#include<algorithm>
#define ll long long
using namespace std;
int T,n;
ll minn;
struct node{
ll cost,val;
double avg;
}a[5];
int main()
{
scanf("%d",&T);
while(T--)
{
scanf("%d",&n);
scanf("%lld%lld",&a[2].val,&a[3].val);
scanf("%lld%lld%lld",&a[1].cost,&a[2].cost,&a[3].cost);
a[1].val=1;
for(int i=1;i<=3;i++)
{
a[i].avg=a[i].cost*1.0/(1.0*a[i].val);
}
if(a[2].avg>a[3].avg) swap(a[2],a[3]);
ll x=n/a[2].val;
ll tmp=n-x*a[2].val;
minn=1ll*tmp*a[1].cost+1ll*x*a[2].cost;
minn=min(minn,1ll*a[1].cost*n);
if(a[1].cost<a[3].avg||tmp==0)
{
printf("%lld\n",minn);
continue;
}
if(a[2].val<n/a[2].val)
{
for(int i=0;i<=a[2].val;i++)
{
ll y=n-i*a[3].val;
if(y<0) continue;
x=y/a[2].val;
tmp=y-x*a[2].val;
minn=min(minn,1ll*tmp*a[1].cost+1ll*x*a[2].cost+1ll*a[3].cost*i);
}
}
else
{
for(int i=0;i<=n/a[2].val;i++)
{
ll y=n-i*a[2].val;
if(y<0) continue;
x=y/a[3].val;
tmp=y-x*a[3].val;
minn=min(minn,1ll*tmp*a[1].cost+1ll*x*a[3].cost+1ll*a[2].cost*i);
}
}
printf("%lld\n",minn);
}
return 0;
}
T3 同构
抽象的结论题
10~20pts:直接DFS即可
void dfs(int x)
{
if(x>n)
{
ans=(ans+1)%1000000007;
return;
}
for(int i=1;i<=n;i++)
{
if(!b[i])
{
bool fl=0;
for(int j=1;j<x;j++)
{
int tmp1=(gcd(a[j],i)==1),tmp2=(gcd(j,x)==1);
if(tmp1!=tmp2)
{
fl=1;
break;
}
}
if(!fl)
{
b[i]=1,a[x]=i;
dfs(x+1);
b[i]=0;
}
}
}
}
100pts:
可以先按照1~n的顺序填入,然后看哪些位置可以互换
如果两个数所含的质因子一样,那么他们必然可以互换,例如6和12均含有因数2,3,他们就可以互换
而且显然超过\(\dfrac{n}{2}\)的指数和1也可以互换,因为他们与所有数互质
但是这样在55这个样例就会错,考虑到,如果按照整除关系建一张图,将所有不互质的连边
而有些质数可以和\(1,2,...,\lfloor \dfrac{n}{x}\rfloor\)相乘从而形成一条链
如果能交换,显然要整条连进行互换,因为时质数,不会产生多余的因子,所以当链长相等时就能互换
代码:
#include<cstdio>
#define ll long long
using namespace std;
const int p=1e9+7;
int n,pr[1000006],tot,cnt[1000006];
ll fac[1000006];
bool vis[1000006];
void get_prime(int x)
{
for(int i=2;i<=x;i++)
{
if(!vis[i])
{
pr[++tot]=i;
}
for(int j=1;i*pr[j]<=x;j++)
{
vis[i*pr[j]]=1;
if(i%pr[j]==0) break;
}
}
}
void get_rt(int x)
{
int tmp=1;
// printf("%d ",x);
if(!vis[x]&&x*2>n)
{
cnt[1]++;
// printf("1\n");
return;
}
for(int i=1;pr[i]*pr[i]<=x;i++)
{
if(!vis[x]) break;
if(x%pr[i]==0)
{
tmp*=pr[i];
while(x%pr[i]==0) x/=pr[i];
}
}
tmp*=x;
cnt[tmp]++;
// printf("%d\n",tmp);
}
int gcd(int a,int b)
{
if(b==0) return a;
return gcd(b,a%b);
}
int b[35],a[35];
ll ans;
void dfs(int x)
{
if(x>n)
{
ans=(ans+1)%1000000007;
return;
}
for(int i=1;i<=n;i++)
{
if(!b[i])
{
bool fl=0;
for(int j=1;j<x;j++)
{
int tmp1=(gcd(a[j],i)==1),tmp2=(gcd(j,x)==1);
if(tmp1!=tmp2)
{
fl=1;
break;
}
}
if(!fl)
{
b[i]=1,a[x]=i;
dfs(x+1);
b[i]=0;
}
}
}
}
int cnt1[1000006];
int main()
{
get_prime(1e6);
scanf("%d",&n);
if(n<20)
{
dfs(1);
printf("%d",ans);
return 0;
}
fac[0]=1;
for(int i=1;i<=n;i++)
{
fac[i]=fac[i-1]*i%p;
}
cnt[1]=1;
for(int i=2;i<=n;i++)
{
get_rt(i);
if(!vis[i])
{
int tmp=n/i;
cnt1[tmp]++;
}
}
ans=1;
for(int i=1;i<=n;i++)
{
// printf("%d %d %lld\n",cnt[i],fac[cnt[i]],ans);
ans=ans*fac[cnt[i]]%p;
}
for(int i=2;i<=n;i++)
{
ans=ans*fac[cnt1[i]]%p;
}
printf("%lld",ans);
return 0;
}
T4 最近公共祖先
https://gxyzoj.com/d/hzoj/p/3801
20pts:暴力枚举每一个黑点,用RMQ求lca即可
40pts:
更新时暴力向上跳父亲,统计每个节点字数内的黑点数量size
统计答案时同样暴力往上跳,对于每个节点u,假设当前要求的点在它的子树v中,有:
那么答案就可以是u的权值
#include<cstdio>
#include<string>
#include<iostream>
#include<algorithm>
using namespace std;
int n,m,a[100005],head[100005],edgenum,size[100005];
struct edge{
int to,nxt;
}e[200005];
void add_edge(int u,int v)
{
e[++edgenum].nxt=head[u];
e[edgenum].to=v;
head[u]=edgenum;
}
int f[100005];
void dfs(int u,int fa)
{
f[u]=fa;
for(int i=head[u];i;i=e[i].nxt)
{
int v=e[i].to;
if(v==fa) continue;
dfs(v,u);
}
}
void query(int x)
{
int u=0,ans=0;
while(x!=0)
{
if(size[x]-size[u]>0) ans=max(ans,a[x]);
u=x;
x=f[x];
}
printf("%d\n",ans);
}
void add(int x)
{
while(x!=0)
{
size[x]++;
x=f[x];
}
}
int main()
{
scanf("%d%d",&n,&m);
for(int i=1;i<=n;i++)
{
scanf("%d",&a[i]);
}
for(int i=1;i<n;i++)
{
int u,v;
scanf("%d%d",&u,&v);
add_edge(u,v);
add_edge(v,u);
}
dfs(1,0);
while(m--)
{
// printf("%d\n",m);
string opt;
cin>>opt;
int x;
cin>>x;
if(opt=="Query")
{
if(size[1]<1)
{
printf("-1\n");
}
else query(x);
}
else
{
add(x);
}
}
return 0;
}
100pts:
转变思路,由按点找答案变为看根据答案的位置找满足条件的点
显然,当点x变成黑色时,它的子树内都可以用它去更新
接下来暴力跳父亲,如果这条边还未被经过才能更新,因为前面如果已经走过了,再更新就没有意义了
所以,假设当前的节点为x,他是由v转移来的,那么x子树内除去v子树都可以被x更新
这部分显然可以用DFS序+线段树
代码:
#include<cstdio>
#include<algorithm>
#include<string>
#include<iostream>
#define lid id<<1
#define rid id<<1|1
using namespace std;
int n,m,a[100005],head[100005],edgenum;
struct edge{
int to,nxt;
}e[200005];
void add_edge(int u,int v)
{
e[++edgenum].nxt=head[u];
e[edgenum].to=v;
head[u]=edgenum;
}
int in[100005],out[100005],tot,f[100005];
bool vis[100005];
void dfs(int u,int fa)
{
in[u]=++tot;
f[u]=fa;
for(int i=head[u];i;i=e[i].nxt)
{
int v=e[i].to;
if(v==fa) continue;
dfs(v,u);
}
out[u]=tot;
}
struct seg_tree{
int l,r,mx,lazy;
}tr[1600005];
void build(int id,int l,int r)
{
tr[id].l=l,tr[id].r=r;
if(l==r) return;
int mid=(l+r)>>1;
build(lid,l,mid);
build(rid,mid+1,r);
}
void pushdown(int id)
{
tr[lid].lazy=max(tr[id].lazy,tr[lid].lazy);
tr[lid].mx=max(tr[id].lazy,tr[lid].mx);
tr[rid].lazy=max(tr[id].lazy,tr[rid].lazy);
tr[rid].mx=max(tr[id].lazy,tr[rid].mx);
tr[id].lazy=0;
}
void update(int id,int l,int r,int x)
{
if(l>r) return;
if(tr[id].l==l&&tr[id].r==r)
{
tr[id].mx=max(tr[id].mx,x);
tr[id].lazy=max(tr[id].lazy,x);
return;
}
pushdown(id);
int mid=(tr[id].l+tr[id].r)>>1;
if(mid>=r) update(lid,l,r,x);
else if(mid<l) update(rid,l,r,x);
else update(lid,l,mid,x),update(rid,mid+1,r,x);
tr[id].mx=max(tr[lid].mx,tr[rid].mx);
}
int query(int id,int c)
{
if(tr[id].l==tr[id].r)
{
return tr[id].mx;
}
pushdown(id);
int mid=(tr[id].l+tr[id].r)>>1;
if(c<=mid) return query(lid,c);
else return query(rid,c);
}
void add(int x)
{
int fa=f[x];
while(fa)
{
update(1,in[fa],in[x]-1,a[fa]);
// printf("%d %d %d\n",in[fa],in[x]-1,a[fa]);
update(1,out[x]+1,out[fa],a[fa]);
// printf("%d %d %d\n",out[x]+1,out[fa],a[fa]);
if(vis[x]) break;
vis[x]=1;
x=fa,fa=f[x];
}
}
int main()
{
// freopen("lca2.in","r",stdin);
scanf("%d%d",&n,&m);
for(int i=1;i<=n;i++)
{
scanf("%d",&a[i]);
}
for(int i=1;i<n;i++)
{
int u,v;
scanf("%d%d",&u,&v);
add_edge(u,v);
add_edge(v,u);
}
dfs(1,0);
build(1,1,n);
// for(int i=1;i<=n;i++)
// {
// printf("%d %d\n",in[i],out[i]);
// }
int cnt=0;
while(m--)
{
string opt;
cin>>opt;
int x;
scanf("%d",&x);
if(opt=="Query")
{
if(cnt<1) printf("-1\n");
else
{
printf("%d\n",query(1,in[x]));
}
}
else
{
cnt++;
update(1,in[x],out[x],a[x]);
// printf("%d %d %d\n",in[x]+1,out[x],a[x]);
add(x);
}
}
return 0;
}