HGOI8.21集训题解

题解

以后第一题就出数学题我把出题人摁在地上打。


第一题——(sum)(没中文)

【题目描述】

  • 有T组数据,给出数 y ( y 10 18 ) , p ,求所有满足 y | x f ( x ) = y 的所有数的和对p取模。其中 f ( x ) 是因数的个数。
  • 如果有无穷多个x则输出-1

  • 真的是,早上打了两个小时的表,还是没有找出规律,只好爆个零。
  • 可以通过打表发现,当一个数的某个质因数存在两个及以上(当然这里发现4除外),则他一定是正无穷,即输出-1。
  • 然后你还会发现,一个数解的和就是所有质因数的值-1,成为指数并且任意分配的和。
  • 给出部分结论的证明:
  • x = p i k i , y = p i l i p i 为质因数,则为了满足条件2则可以推出 y = k i + 1
  • y t 个不同的质因数,那么 x 就只需要 t 个质因数就可以令 y | x
    而此时的 f ( x ) 还远小于 y ,这时候我们随便补一个不一样的质数就可以满足条件,所以-1。
  • 然后你就要去分解一个数的质因数了对不对,但是你连欧拉筛都打不出来,那我们就要用到一个玄学看脸算法——Miller-rabin算法,详细的自己百度,反正可以在 O ( l o g n ) 时间内概率性的判断一个数是不是质数
  • 然后还有可能会是carmichael数,上面这个判不了,那就还要再来拿着个来试水。然后就成功分解质因数了。
  • 分完质因数就是求解了,上面说的不是很清楚,直接看代码最后求和的那一段吧。
  • 记得用快乘,不然果爆LL


#include <iostream>
#include <cstdio>
#include<bits/stdc++.h>
#include <algorithm>
#include <cstring>
#include <cmath>
#define LL long long
#define TIMES 15
using namespace std;
const int N=20005;
void fff(){
    freopen("sum.in","r",stdin);
    freopen("sum.out","w",stdout);
}
LL y,p,sum;
LL get_rand(LL n){
    LL num=(((unsigned LL) rand()+100000007)*rand())%n;
    return num+1;
}
LL mul(LL x,LL y,LL MOD){
    LL tmp=(x*y-(LL)((long double)x/MOD*y+1e-8)*MOD)%MOD;
    if (tmp<0) tmp+=MOD;
    return tmp;
}
LL ksm(LL x,LL y,LL MOD){
    LL ans=1;x%=MOD;
    while (y){
        if (y&1) ans=mul(ans,x,MOD);
        x=mul(x,x,MOD);y>>=1;
    }
    return ans;
}
LL Mod_Mul(LL a,LL b,LL MOD){
    LL msum=0;
    while (b){
        if(b&1) msum=(msum+a)%MOD;
        b>>=1;
        a=(a+a)%MOD;
    }
    return msum;
}
LL Quk_Mul(LL a,LL b,LL MOD){
    LL qsum=1;
    while (b){
        if(b&1) qsum=Mod_Mul(qsum,a,MOD);
        b>>=1;
        a=Mod_Mul(a,a,MOD);
    }
    return qsum;
}
bool miller_robin(LL n){
    if(n==2||n==3||n==5||n==7||n==11||n==13||n==17||n==41) return true;
    if(n==1||n%2==0||n%3==0||n%5==0||n%7==0||n%11==0||n%13==0||n%41==0||n%17==0) return false;
    int div2=0;
    LL tn=n-1;
    while (tn%2==0){
        div2++;
        tn/=2;
    }
    for (int i=1;i<=TIMES;i++){
        LL x=get_rand(n-1);
        if(x==1) continue;
        x=Quk_Mul(x,tn,n);
        LL pre=x;
        for (int j=0;j<div2;j++){
            x=Mod_Mul(x,x,n);
            if(x==1&&pre!=1&&pre!=n-1) return false;
            pre=x;
        }
        if(x!=1) return false;
    }
    return true;    
}
LL d[N],dminn=2e18;
int dnum;
LL gcd(LL a,LL b){
    if(b==0) return a;
    return gcd(b,a%b);
}
LL pollard_rho(LL dn,LL dc){
    LL x,y,d,i=1,k=2;
    x=get_rand(dn-1);
    y=x;
    while(true){
        i++;
        x=(Mod_Mul(x,x,dn)+dc)%dn;
        d=gcd(y-x,dn);
        if(1<d&&d<dn) return d;
        if(y==x) return dn;
        if(i==k){
            y=x;
            k<<=1;
        }
    }
}
void divide(LL dn,int dk){
    if(dn==1) return;
    if(miller_robin(dn)==true){
        d[dnum++]=dn;
        dminn=min(dminn,dn);
        return;
    }
    LL dtmp=dn;
    while (dtmp>=dn) dtmp=pollard_rho(dtmp,dk--);
    divide(dtmp,dk);
    divide(dn/dtmp,dk);
}
LL f[N];
#define divsor d
#define dcnt dnum

void work(LL x,LL mo) {
    if (x==1) {printf("%d\n",1); return;}
    if (x==4) {printf("%d\n",8%mo); return;}
    memset(divsor,0,sizeof divsor); dcnt=0;
    if( miller_robin(y) )  divsor[++dcnt]=y;else {dcnt++;divide(x,251);}
    while (divsor[dcnt]==0) dcnt--;
    sort(divsor+1,divsor+dcnt+1);
    for (int i=1;i<=dcnt;i++) if (divsor[i]==divsor[i-1]) 
    {printf("-1\n"); return;}  f[0]=1;
    for (int i=1;i<(1<<dcnt);i++)
    {
        int s=0;f[i]=0;
        for (int j=i;j;j-=j&(-j),s++);
        for (int j=1;j<=dcnt;j++)
            if (i&(1<<(j-1))) f[i]=(f[i]+mul(f[i-(1<<(j-1))],ksm(divsor[s],divsor[j]-1,mo),mo))%mo;
    }
    printf("%lld\n",f[(1<<dcnt)-1]);return;
}
int main(){
    srand(unsigned(time(0)));
    fff();
    int T;scanf("%d",&T);
    while(T--){
        sum=0;
        scanf("%lld%lld",&y,&p);
        LL n=y;
        if(y==1){printf("%d\n",1%p);continue;}
        if(y==4){printf("%d\n",8%p);continue;}
        memset(d,0,sizeof(d));dnum=0;
        if(miller_robin(n)) d[++dnum]=n;else{
            dnum++;
            divide(y,251);
        }
        while (d[dnum]==0) dnum--;
        sort(d+1,d+dnum+1);
        bool flag=false;
        for (int i=1;i<=dnum;i++) if(d[i]==d[i-1]){printf("-1\n");flag=true;break;}
        if(flag) continue;
        f[0]=1;
        for (int i=1;i<(1<<dnum);i++){
            int s=0;f[i]=0;
            for (int j=i;j;j-=j&(-j),s++);
            for (int j=1;j<=dnum;j++)
                if(i&(1<<(j-1))) f[i]=(f[i]+mul(f[i-(1<<(j-1))],ksm(d[s],d[j]-1,p),p))%p;
        }
        printf("%lld\n",f[(1<<dnum)-1]);
    }
}

第二题——tree

【题目描述】

  • 给出一棵点上带权树,有一下三种操作:
    • 1、询问以x为子树当中有多少个节点元素大于y
    • 2、修改某个节点x的值变为y
    • 3、添加节点,编号为原有个数n+1,父亲为x,权值为y

  • 刚开始题目没说清楚,以1为根,所以猜错了题面。
  • 标程是用了树上分块维护区间最小值,当块不平衡之后重新分块。
  • 然而数据比较水,直接暴力就能过orz
#include <iostream>
#include <cstdio>
#include <algorithm>
#include <cstring>
#include <cmath>
using namespace std;
void fff(){
    freopen("tree.in","r",stdin);
    freopen("tree.out","w",stdout);
}
const int N=30010;
int n;
struct Edge{
    int to,nxt;
}e[N*3];
int head[N],tot=0;
int read(){
    int x=0;
    char ch=getchar();
    while (ch<'0'||ch>'9') ch=getchar();
    while (ch>='0'&&ch<='9') x=x*10+ch-'0',ch=getchar();
    return x;
}
void add(int u,int v){
    e[++tot].nxt=head[u];
    e[tot].to=v;
    head[u]=tot;
}
int val[N<<1],fa[N<<1];
bool visited[N<<1];
int dfs(int u,int y){
    int sum=0;
    visited[u]=true;
    for (int i=head[u];i;i=e[i].nxt){
        int v=e[i].to;
        if(v!=fa[u]){
            sum+=dfs(v,y);
        }
    }
    return sum+(val[u]>y);
}
void dfs1(int u){
    visited[u]=true;
    for (int i=head[u];i;i=e[i].nxt){
        int v=e[i].to;
        if(!visited[v]){
            fa[v]=u;
            dfs1(v);
        }
    }
}
int main(){
    fff();
    n=read();
    for (int i=1;i<n;i++){
        int u,v;
        u=read(),v=read();
        add(u,v);
        add(v,u);
    }
    for (int i=1;i<=n;i++) val[i]=read();
    memset(visited,0,sizeof(visited));
    dfs1(1);
    int m;
    m=read();
    while(m--){
        int op,x,y;
        op=read();
        if(op==0){
            x=read(),y=read();
            memset(visited,false,sizeof(visited));
            printf("%d\n",dfs(x,y));
        }else if(op==1){
            x=read(),y=read();
            val[x]=y;
        }else if(op==2){
            x=read(),y=read();
            add(x,++n);
            add(n,x);
            fa[n]=x;
            val[n]=y;
        }
    }
}

附上分块的程序


#include<cmath>
#include<cstdio>
#include<cstring>
#include<iostream>
#include<algorithm>
#define Maxn 300010
#define limit 200
using namespace std;
int n,m,u,v,last,op;char ch;
int sum1,sum2,sum3,ad1,ad2;
int w[Maxn<<1],father[Maxn<<1],rec[Maxn],nxt1[Maxn<<1],pre1[Maxn<<1],nxt2[Maxn<<1],pre2[Maxn<<1],end1[Maxn],end2[Maxn];
struct Node
{
    int length,a[limit];
}B[40000];
void add1(int x,int y)
{
    nxt1[++ad1]=y;pre1[ad1]=end1[x];end1[x]=ad1;
}
void add2(int x,int y)
{
    nxt2[++ad2]=y;pre2[ad2]=end2[x];end2[x]=ad2;
}
inline void read(int&x)
{
    ch=getchar();x=0;
    while (ch<'0'||ch>'9') ch=getchar();
    while (ch>='0'&&ch<='9') x=x*10+ch-48,ch=getchar();
}
void build(int u)        //dfs加点,按操作op=2的方式处理孩子 
{
    for(int i=end1[u];i>0;i=pre1[i]) 
        if(father[u]!=nxt1[i])
        {
            int T;
            if(B[rec[u]].length<limit) 
            {
            T=rec[nxt1[i]]=rec[u];
            B[T].a[B[T].length++]=w[nxt1[i]];
            }
            else 
            {
            T=rec[nxt1[i]]=++sum2;
            B[sum2].a[B[sum2].length++]=w[nxt1[i]];
            add2(rec[u],T);
            }
        father[nxt1[i]]=u;sort(B[T].a,B[T].a+B[T].length);
        build(nxt1[i]);
        }
}
void update(int u,int v)        //当块内节点更新时, 暴力重构当前块 
{
    int T=rec[u];
    int k=lower_bound(B[T].a,B[T].a+B[T].length,w[u])-B[T].a;
    B[T].a[k]=v;w[u]=v;
    sort(B[T].a,B[T].a+B[T].length);
}
int bdfs(int u,int x)        //树上块查询 
{
    int sum=B[u].length-(upper_bound(B[u].a,B[u].a+B[u].length,x)-B[u].a);
    for(int i=end2[u];i>0;i=pre2[i])
        sum+=bdfs(nxt2[i], x);
    return sum;
}
int pdfs(int u, int x)        //树上点查询 
{
    int sum=0;
    if(w[u]>x) sum++;
    for(int i=end1[u];i>0;i=pre1[i]) 
        if(nxt1[i]!=father[u]) 
        { 
            if(rec[u]==rec[nxt1[i]]) sum+=pdfs(nxt1[i],x);
                else sum+=bdfs(rec[nxt1[i]],x);
        }
    return sum;
}
int main()
{
    freopen("tree.in","r",stdin);
    freopen("tree.out","w",stdout);
    read(n);
    for(int i=1;i<n;i++) read(u),read(v),add1(u,v),add1(v,u);
    for(int i=1;i<=n;i++) read(w[i]);
    father[1]=0;
    B[++sum2].length=1;
    B[sum2].a[0]=w[1];
    rec[1]=sum2;
    build(1);
    read(m);
    while (m--)
    {
        read(op);read(u);read(v);

        if(op==0) last=pdfs(u,v),printf("%d\n",last);
        if(op==1) update(u,v);
        if(op==2)
        {
            w[++n]=v;
            add1(u,n);
            father[n]=u;
            int T=rec[u];
            if(B[T].length<limit) B[T].a[B[T].length++]=v,rec[n]=rec[u];
            else T=++sum2,B[T].a[B[T].length++]=v,add2(rec[u],sum2),rec[n]=sum2;   //添加新的块 
            sort(B[T].a,B[T].a+B[T].length);        //暴力构建状态 
        }
    }
 }

第三题——town

【题目描述】

  • 给出一个带边带点权的根为1的树,边权为w,点权有a,b两类,设一个点到根的距离为a+到根距离*b,同时可以在某些点进行交换使得到根的距离更短,交换时需要重新加上交换点的a,求每个节点到根的最短距离。

  • 一眼看出这个是 O ( n 2 ) 的dp,当前节点可以在父节点进行交换,而父节点已近处理好了,那么方程可以写成 f [ i ] = m i n ( f [ j ] + V [ i ] ( D [ i ] D [ j ] ) + S [ i ] ) ,其中 f [ i ] 是最终的答案, V [ i ] 是第二个点权b, D [ i ] 是距离。
  • 如果树是一颗平很树,那复杂度是 O ( n l o g n ) ,但有一半是链的情况,那么就需要用到斜率优化。
  • 我们把上面的方程改变一下,变成

    f [ i ] = m i n ( f [ j ] V [ i ] D [ j ] ) + V [ i ] D [ i ] + S [ i ]

  • 我们考虑将(D[i],f[i])放到坐标系中。则最优解即为一条以V[i]为斜率的点从x轴下的无限远处向上平移所得到的第一个点。凸包解决

  • 然后就是二分寻找出这个斜率最相近并且凸出的点,并将其替换。
  • 由于递归,则需要做到可还原栈来进行存储。防止超时就是 O ( 1 ) 还原栈。

#include <iostream>
#include <cstdio>
#include <algorithm>
#include <cstring>
#include <cmath>
#include <vector>
#define LL long long
using namespace std;
void fff(){
    freopen("town.in","r",stdin);
    freopen("town.out","w",stdout);
}
const int N=100100;
int n,end,w;
int a_val[N],b_val[N];
#define x first
#define y second
vector <pair<int,int> > G[N];
LL d[N],f[N],q[N];
struct STACK{
    int k,x,fa,dep,_pos,_val,_end;
}s[N];
double get_k(int j,int k){
    return (double)(f[k]-f[j])/(d[k]-d[j]);
}
int search(int vv){
    int l=0,r=end-2;
    while(l<r){
        int mid=(l+r+1)>>1;
        if(get_k(q[mid],q[mid+1])<vv) l=mid;else r=mid-1;
    }
    return q[l+1];
}
void inst(int l,int r,int &_pos,int &_val,int x){
    while (l<r){
        int mid=(l+r+1)>>1;
        if(get_k(q[mid-1],q[mid])<get_k(q[mid-1],x)) l=mid;else r=mid-1;    
    }
    if(get_k(q[l-1],q[l])>get_k(q[l-1],x)) l--;
    _pos=l+1,_val=q[_pos];
    end=l+2,q[end-1]=x;
}
void work(){
    memset(s,0,sizeof(s));
    w=1;s[w].fa=s[w].k=-1;
    while (w){
        int x=s[w].x,fa=s[w].fa,dep=s[w].dep;
        int &k=s[w].k,&_pos=s[w]._pos,&_val=s[w]._val,&_end=s[w]._end;
        if(k==-1){
            d[x]=dep,f[x]=a_val[x]+1ll*dep*b_val[x];_end=end;
            if(end>1){
                int p=q[0];
                if(get_k(q[0],q[1])<b_val[x]){

                    p=search(b_val[x]);
                }
                f[x]=min(f[x],(LL)(f[p]+a_val[x]+1ll*b_val[x]*(dep-d[p])));
                inst(1,end-1,_pos,_val,x);
            }else _pos=end,_val=q[_pos],q[end++]=x;
            ++k;
        }else{
            if(k<G[x].size()){
                if(G[x][k].x!=fa) s[++w].x=G[x][k].x,s[w].fa=x,s[w].dep=dep+G[x][k].y,s[w].k=-1;
                ++k;
            }else end=_end,q[_pos]=_val,--w;
        }
    }
}
int main(){
    fff();
    scanf("%d",&n);
    for (int i=1;i<n;i++){
        int u,v,w;
        scanf("%d%d%d",&u,&v,&w);
        G[u-1].push_back(make_pair(v-1,w));
        G[v-1].push_back(make_pair(u-1,w));
    }
    for (int i=1;i<n;i++)scanf("%d%d",&a_val[i],&b_val[i]);
    work();
    for (int i=1;i<n;i++) printf("%lld ",f[i]);
    return 0;
}

猜你喜欢

转载自blog.csdn.net/qq_42037034/article/details/81913786