2022考前欢乐赛
[贪心/DP]T1:给出一个序列ai,你初始有1B,可以每天选择把所有的B兑换成R,1B=ai*R,也可以把所有的R换成B。求n天后最多的B币数量,只输出路径。(n<=2e5)
考场
首先在第i天只知道它手里是有B或者R就行,明显阶段性,所以设\(f[i][j]:在i天手里有B或者R的最大价值,转移显然\)
但是数据ai<=1e9会爆long double,需要把只有乘除法的运算取log
点击查看代码
#include<bits/stdc++.h>
using namespace std;
#define ll long long
#define ull unsigned long long
#define chu printf
#define rint register int
#define rll register long long
inline ll re()
{
ll x=0,h=1;char ch=getchar();
while(ch<'0'||ch>'9')
{
if(ch=='-')h=-1;
ch=getchar();
}
while(ch<='9'&&ch>='0')
{
x=(x<<1)+(x<<3)+(ch^48);
ch=getchar();
}
return x*h;
}
const int N=2e5+100;
long double f[N][2];//f[i][0/1]:到第i天货币最多价值
int pre[N][2],n,rem[N],lst[N][2];//记录转移来自:1或者0
//1:RMB 0:BIT
//可有边界?
long double a[N];
long double xs[2];
int main()
{
// freopen("1.in","r",stdin);
// freopen("1.out","w",stdout);
freopen("coin.in","r",stdin);
freopen("coin.out","w",stdout);
n=re();
for(rint i=1;i<=n;++i)a[i]=re(),a[i]=log2(a[i]);
f[0][1]=0;
f[0][0]=-1e15;
for(rint i=1;i<=n;++i)//循环到第几天
{
if(f[i-1][0]>f[i-1][1]+a[i])
{
f[i][0]=f[i-1][0];
lst[i][0]=0;
pre[i][0]=0;
}
else
{
f[i][0]=f[i-1][1]+a[i];
lst[i][0]=1;
pre[i][0]=1;
}
if(f[i-1][1]>f[i-1][0]-a[i])
{
f[i][1]=f[i-1][1];
lst[i][1]=1;
pre[i][1]=0;
}
else
{
f[i][1]=f[i-1][0]-a[i];
lst[i][1]=0;
pre[i][1]=1;
}
}
int pos=n,state=1;//最后一天换不换?是个问题,最好多算一天
while(1)
{
rem[++rem[0]]=pre[pos][state];
state=lst[pos][state];
pos--;
if(pos==0)break;
}
for(rint i=rem[0];i>=1;--i)chu("%d ",rem[i]);
return 0;
}
/*
3
3 5 2
*/
正解
其实DP也是正解。
贪心考虑汇率ai形成若干个单调段,那么需要在峰顶换B,峰底换R,一定是最优的。
[贪心/根号下枚举的放缩思想]T2:给出3种物品1,A,B,还有他们的花费X,Y,Z,求最少的构造N体积的代价。(N,A,B,X,Y,Z<=1e9)
考场
考虑边界,min(N/A,N/B),发现如果A,B都很大,那么枚举范围很小,枚举一个最小的,剩下2个很好贪心;
但是如果A,B很小怎么办?背包也跑不了,于是想到求lcm(1,A,B),先贪心地凑这些(找一个回馈率最大的),然后剩下的就不大了。WA82
正解
但是这是假的,因为考虑lcm凑整块,剩下的块很小,如果把大块里的腾出一小部分和小块凑,会更优,比如12的cost是10,8的cost是3,1是200,lcm是24,剩下4,你先是尽量凑8,这时剩下的4就要花费 800,不如拿出一个8来凑出12,然后只需要花费10就行。
还是枚举一个个数,问题缩小到2个物品。
如果min(N/A,N/B)<sqrt(N),直接枚举其中最大的个数;如果A,B都特别小的话,那么如果回馈率A/X>B/Y,cnt_B<=A-1,否则假设一个部分用了A的体积的B,完全可以用A来代替,cost=cost_A<cost_B。
那么枚举最小的,范围就缩小不少了。
点击查看代码
#include<bits/stdc++.h>
using namespace std;
#define ll long long
#define ull unsigned long long
#define chu printf
#define rint register int
#define rll register long long
#define db double
inline ll re()
{
ll x=0,h=1;char ch=getchar();
while(ch<'0'||ch>'9')
{
if(ch=='-')h=-1;
ch=getchar();
}
while(ch<='9'&&ch>='0')
{
x=(x<<1)+(x<<3)+(ch^48);
ch=getchar();
}
return x*h;
}
const int N=-1;
ull cost[3],val[3];
ull n;
inline ull gcd(ull a,ull b)
{
if(!b)return a;
return gcd(b,a%b);
}
inline ull solve()
{
ull sum=0;
ull lcm=val[0]*val[1]*val[2]/gcd(val[1],val[2]);
ull sd=n/lcm;n-=sd*lcm;
ull c1=lcm/val[0]*cost[0]*sd;
ull c2=lcm/val[1]*cost[1]*sd;
ull c3=lcm/val[2]*cost[2]*sd;
return min(c1,min(c2,c3));
}
int main()
{
// freopen("1.in","r",stdin);
// freopen("1.out","w",stdout);
freopen("cs.in","r",stdin);
freopen("cs.out","w",stdout);
int T=re();
// chu("T:%d\n",T);
while(T--)
{
n=re();
val[0]=1;val[1]=re(),val[2]=re();
cost[0]=re(),cost[1]=re(),cost[2]=re();
if(val[1]>val[2])swap(val[1],val[2]),swap(cost[1],cost[2]);
ull sum=(1llu<<63);
if(val[1]*cost[0]<=cost[1]&&val[2]*cost[0]<=cost[2])//都用1就行了
{
chu("%llu\n",n*cost[0]);continue;
}
//chu("df\n");
ull bj=n/val[2];//枚举B用几个
ull add=0;
if(bj>1000000)//有T的危险
{
if(cost[1]*val[2]>cost[2]*val[1])swap(cost[1],cost[2]),swap(val[1],val[2]);
bj=val[1]-1;
}
for(ull i=0;i<=bj;++i)
{
//chu("bj:%d\n")
if(val[1]*cost[0]<=cost[1])//可以全部用1,不用A
{
sum=min(sum,i*cost[2]+(n-i*val[2])*cost[0]);
}
else
{
ull lef=n-i*val[2];
sum=min(sum,i*cost[2]+lef/val[1]*cost[1]+(lef%val[1])*cost[0]);
}
}
chu("%llu\n",sum+add);
}
return 0;
}
/*
5
10 3 5 2 3 6
10 3 5 1 1000000000 1000000000
139 2 139 1 1 1
139 1 1 1 1 1
139 7 10 3845 26982 30923
*/
[数论/打表找规律]T3:给出n,求n的排列个数,满足\(gcd(u,v)==1 == gcd(pu,pv)==1\),n<=2e5
考场
打表半天,发现一些不靠谱的规律,比如质数不能放在合数位置,比如pi和i必须是倍数关系,然后就瞎剪枝,跑出26就是极限了...25tps
正解
还是打表,但是观察的规律比较接近本质:
【1】可以交换位置的2个数质因子种类相同。显然gcd的产生嘛
【2】N/i相同的质数i可以互相交换。:
\(考虑一组值 5 ,7 ,10, 14\)
可以变成\(7,5,14,10\),首先考虑内部的gcd互质条件满足,对于外部5和10-->2,7和14-->2,也满足;
也就是说如果质数a和b的倍数关系相对于N相同,那么一定存在一组构造使得满足题目要求。
所以直接连乘起来就好了。
对于质因子集合相同:连乘起来;
对于/i相同也是一样;注意对于2个部分不影响彼此。因为【2】部分只有质数,【1】部分只要把质数集合任意数作为代表集合就行。
点击查看代码
#include<bits/stdc++.h>
using namespace std;
#define ll long long
#define ull unsigned long long
#define chu printf
#define rint register int
#define rll register long long
#define db double
inline ll re()
{
ll x=0,h=1;char ch=getchar();
while(ch<'0'||ch>'9')
{
if(ch=='-')h=-1;
ch=getchar();
}
while(ch<='9'&&ch>='0')
{
x=(x<<1)+(x<<3)+(ch^48);
ch=getchar();
}
return x*h;
}
const int N=1e6+100;
const int mod=1e9+7;
int c[N],t[N],zhi[N],mi[N],cnt,fac[N];
bool prime[N];
int n;
int main()
{
// freopen("1.in","r",stdin);
// freopen("1.out","w",stdout);
freopen("tg.in","r",stdin);
freopen("tg.out","w",stdout);
n=re();
fac[0]=1;
for(rint i=1;i<=n;++i)fac[i]=fac[i-1]*1ll*i%mod;
for(rint i=2;i<=n;++i)
{
if(!prime[i])zhi[++cnt]=i,mi[i]=i;
for(rint j=1;j<=cnt&&zhi[j]*i<=n;++j)
{
int goal=zhi[j]*i;
prime[goal]=1;
mi[goal]=zhi[j];
if(i%zhi[j]==0)break;
}
}
for(rint i=2;i<=n;++i)
{
if(prime[i]==0)//如果是质数
{
t[n/i]++;
c[i]++;
}
else
{
int pr=1;int cp=i;
while(cp>1)
{
pr*=mi[cp];int r=mi[cp];
while(cp%r==0)cp/=r;
}
c[pr]++;
}
}
t[1]++;
int ans=1;
for(rint i=2;i<=n;++i)ans=1ll*ans*fac[c[i]]%mod;
for(rint i=1;i<=n;++i)ans=1ll*ans*fac[t[i]]%mod;
chu("%d",ans);
return 0;
}
/*
*/
[图论/贪心]T4:给出l个城市,每个城市有n个车站,每个城市i的车站之间可以用线路连接,边权是ai+dj,j是边的属性;城市之间是高铁连接,i和相邻城市i+1之间的连接代价是ai,求最小权值连接所有点。(n,l,ai,bj<=1e5)
正解
先建立出每个车站的最小生成树(dj本质一样),如果内部一定连接这些边。如果是之间的话,一定是前l-1的边连接。然后替换法,考虑替换边使得代价更小,ai从小到大,并查集维护当前最小未删除块,删边角度更好考虑。
点击查看代码
#include<bits/stdc++.h>
using namespace std;
#define ll long long
#define ull unsigned long long
#define chu printf
#define rint register int
#define rll register long long
#define db double
inline ll re()
{
ll x=0,h=1;char ch=getchar();
while(ch<'0'||ch>'9')
{
if(ch=='-')h=-1;
ch=getchar();
}
while(ch<='9'&&ch>='0')
{
x=(x<<1)+(x<<3)+(ch^48);
ch=getchar();
}
return x*h;
}
const int N=1e4+100,M=1e5+100;
int head[N],tot,fa1[N],fa2[M];//最小生成树的
int edge[M],top;//e是找出来的可以反悔的边,只有权值?就是bi
int sp[M];
bool mark[N];
ll sb[M];
struct node
{
int u,v,w;//树!
bool operator<(const node&U)const
{
return w<U.w;
}
}e[M];
struct city
{
int ar,br,id;
bool operator<(const city&U)const
{
return ar<U.ar;
}
}c[100000+100];
int n,l,m,r,fid[100000+100];
ll dev_pre,dev_now;
inline int father(int fa[],int x)
{
if(x==fa[x])return x;
return fa[x]=father(fa,fa[x]);
}
int main()
{
freopen("rb.in","r",stdin);
freopen("rb.out","w",stdout);
// freopen("1.in","r",stdin);
// freopen("1.out","w",stdout);
n=re(),m=re();
for(rint i=1;i<=m;++i)
{
int x=re()+1,y=re()+1,z=re();
e[i]=(node){x,y,z};
}
l=re();
for(rint i=1;i<=l;++i)
c[i].ar=re(),c[i].br=re(),c[i].id=i;
for(rint i=1;i<=l;++i)sb[i]=sb[i-1]+c[i].br;
r=re();
for(rint i=1;i<=r;++i)sp[i]=re()+1,mark[sp[i]]=1;
//上面读入
sort(e+1,e+1+m);
for(rint i=1;i<=n;++i)fa1[i]=i;
int del=0;
for(rint i=1;i<=m;++i)
{
int f1=father(fa1,e[i].u),f2=father(fa1,e[i].v);
if(f1==f2)continue;
++del;
dev_pre+=e[i].w;
fa1[f1]=f2;
if(mark[f1]&&mark[f2])//如果都是关键点
{
edge[++top]=e[i].w;//只存权值?
}
mark[f2]=mark[f1]|mark[f2];//标记边
if(del==n-1)break;
}
//生成树
dev_pre=1ll*dev_pre*l;
dev_pre+=1ll*sb[l]*(n-1);//每个城市算了n-1次
//统计所有权值
sort(c+1,c+1+l);//按照a排序
sb[top+1]=0;
for(rint i=top;i>=1;--i)sb[i]=sb[i+1]+edge[i];//统计边权的前缀和
for(rint i=1;i<=l;++i)fa2[i]=i,fid[c[i].id]=i;
// chu("sum_pre:%lld\n",dev_pre);
// chu("edge:%d(%d)\n",edge[top],top);
for(rint i=1;i<l;++i)
{
int akk=c[i].id,bkk=c[i].id+1;
if(bkk>l)bkk=1;
// chu("pre:%d sub:%d\n",akk,bkk);
int ak=father(fa2,akk),bk=father(fa2,bkk);//找本质,需要都找?
int val=c[i].ar;
if(c[fid[ak]].br<c[fid[bk]].br)swap(ak,bk);
int x=val-c[fid[ak]].br;
int pos=lower_bound(edge+1,edge+1+top,x)-edge;
//pos--top都删除掉,替换
dev_now+=1ll*val*(top-pos+1+1);
dev_now-=sb[pos];
dev_now-=1ll*c[fid[ak]].br*(top-pos+1);
fa2[ak]=bk;//连到小的
// chu("sub :%lld\n",sb[pos]);
// chu("dev_now:%lld\n",dev_now+dev_pre);
}
chu("%lld",dev_now+dev_pre);
//开始删除边
return 0;
}
/*
[1]最小生成树:记录两边此时的点集合任意都有关键点的边:存起来(w and [u,v])
[2]按照a排序,定义bu<bv,那么二分出bv的必须替换的边,权值加上
[3]经过n-1
3 3
0 1 7
1 2 8
2 0 5
4
8 1
5 1
9 3
7 3
2
1
2
3504710563389
*/