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

*/
posted on 2022-10-28 22:06  HZOI-曹蓉  阅读(10)  评论(0编辑  收藏  举报