HGOI8.20集训题解

题解

蛮难的今天,题面还有错,SDOI题。


第一题——直径(diameter)

【题目描述】

  • 求出给出带点权树上最长链满足链上gcd不等于1。

  • 题面刚开始错了,求的是链上两两不互质,那就只有 O(n3) O ( n 3 ) 暴力才能做…后来标答第一句就和题意不一样我…
  • 说一下标答,其实就是求树上直径的变形,加上了gcd的限制,枚举每一个质因数,然后将能将它的倍数的点都标记,求出每一部分子树的最长半径。
  • 然后求出最长半径,就是从根搜到离根最远的节点,然后将这个点设为新根,从新根再搜到离他最远的节点(证明自己yy一下?)就是最长半径。

#include <iostream>
#include <algorithm>
#include <cstdio>
#include <cstring>
#include <cmath>
#include <map>
#include <vector>
using namespace std;
void fff(){
    freopen("diameter.in","r",stdin);
    freopen("diameter.out","w",stdout);
}
const int N=100100;
struct Edge{
    int to,nxt;
}e[N<<1];
map<int,vector <int> > M;
int a[N],head[N],tot=0;
void add(int u,int v){
    e[++tot].nxt=head[u];
    e[tot].to=v;
    head[u]=tot;
}
int n;
int ans;
int prime[N],vis[N],cnt=0;
int pt,tmax;
bool visited[N];
void init(){
    for (int i=2;i<=100000;i++){
        if(!visited[i]) prime[++cnt]=i;
        for (int j=1;j<=cnt&&i*prime[j]<=100000;j++){
            visited[i*prime[j]]=true;
            if(i%prime[j]==0) break;
        }
    }
}
void dfs(int u,int f,int p,int d){
    vis[u]=p;if(d>tmax) tmax=d,pt=u;
    for (int i=head[u];i;i=e[i].nxt){
        int v=e[i].to;
        if(v!=f&&a[v]%p==0) dfs(v,u,p,d+1);
    }
}
void expl(int u,int f,int p,int d){
    if(d>ans) ans=d;
    for (int i=head[u];i;i=e[i].nxt){
        int v=e[i].to;
        if(v!=f&&a[v]%p==0) expl(v,u,p,d+1);
    }
}
int main(){
    fff();
    init();
    ans=0;
    scanf("%d",&n);
    bool flag=false;
    for(int i=1;i<n;i++){
        int u,v;
        scanf("%d%d",&u,&v);
        if(u!=i&&v!=i) flag=true;
        add(u,v);
        add(v,u);
    }
    for (int i=1;i<=n;i++){ 
        scanf("%d",&a[i]);
        int x=a[i];
        for (int j=1;j<=cnt&&1ll*prime[j]*prime[j]<=x;j++){
            if(x%prime[j]==0){
                while (x%prime[j]==0) x/=prime[j];
                M[prime[j]].push_back(i);
                if(x==1) break;
            }
            if(x!=1) M[x].push_back(i);
        }
    }
    for (map<int,vector <int> >::iterator s=M.begin();s!=M.end();s++){
        int p=(*s).first;
        vector <int> v=(*s).second;
        for (vector <int>::iterator u=v.begin();u!=v.end();u++){
            if(vis[*u]!=p){
                tmax=0;
                dfs(*u,0,p,0);
                expl(pt,0,p,1);
            }
        }
    }
    cout<<ans;
}

第二题——选举(vote)

【题目描述】

  • 给出n个人的不完全统计选票 ci c i ,总票数为v,通过m轮评奖,要求出每个人获得奖项的最多个数和最小个数,票数小于总票数的5%直接淘汰。
  • 评奖的权值为 ai/(bi+1) a i / ( b i + 1 ) 的最大值, ai a i 为当前票数, bi b i 为已获奖数。权值最大的获奖。

  • 有两问,第一问很好解决,求最多次数的一定要让这个人的票数尽量多,这个可以用堆或者暴力维护。
  • 第二问比较难搞,因为不一定是平均情况下就是最少的,所以需要利用dp,令 f[i][j] f [ i ] [ j ] 表示前 i i 个人至少拿j个奖的至少加的票数,那么

    f[i][j]=min(f[i1][jk]+calc(i,k))

  • 其中 calc(i,k) c a l c ( i , k ) i i k个奖最少加的票数,那么对比于最终查询的x来说,前k轮的权值都要大于x获得mid个奖项之后的权值。


#include <iostream>
#include <algorithm>
#include <cstdio>
#include <cstring>
#include <cmath>
#include <queue>
using namespace std;

void fff(){
    freopen("vote.in","r",stdin);
    freopen("vote.out","w",stdout);
}
const int N=110;
int n,m,v,sum=0;
int c[N];
typedef pair<int,int> ii;
struct node{
    int a_val,b_val;
    double val;
    int num;
    bool operator < (const node x)const{
        if(val==x.val) return num>x.num;
        return val<x.val;
    }
}a[N];
int big_ans[N],minn_ans[N],cnt[N];
priority_queue < node > Heap;
pair<int,int> cc[N];
int f[210][210];
bool check(int x,int y){
    for (int i=0;i<=m;i++) f[0][i]=1<<30;
    f[0][0]=0;
    for (int i=1;i<=n&&i<=21;i++){
        if(i==x){
            for (int j=0;j<=m;j++) f[i][j]=f[i-1][j];
            continue;
        }
        for (int j=0;j<=m;j++) f[i][j]=1<<30;
        for (int j=0;j<=m;j++){
            for (int k=0;k<=j;k++){
                int cur=(cc[x].first*k)/(y+1)-cc[i].first;
                if(k && 1LL*(cc[i].first+cur)*(y+1)==1LL*cc[x].first*k && cc[i].second>cc[x].second) cur++;
                if(cur<0) cur=0;
                if(k&&20LL*(cc[i].first+cur)<v) cur=(v+19)/20-cc[i].first;
                f[i][j]=min(f[i][j],f[i-1][j-k]+cur);
            }
        }
    }
    return f[min(n,21)][m-y]<=v-sum;
}
int main(){
    fff();
    scanf("%d%d%d",&v,&n,&m);
    memset(minn_ans,0x3f3f3f,sizeof(minn_ans));
    for (int i=1;i<=n;i++) scanf("%d",&c[i]),sum+=c[i],a[i].num=i;
    for (int i=1;i<=n;i++){
        while (!Heap.empty()) Heap.pop();
        memset(cnt,0,sizeof(cnt));
        for (int j=1;j<=n;j++)
            if(j!=i){
                a[j].a_val=c[j];
                a[j].b_val=0;
                a[j].val=(double)(a[j].a_val)/(double)(a[j].b_val+1);
                if(a[j].a_val<(int)(v*0.05))
                    minn_ans[j]=0;
                else Heap.push(a[j]);
            }
        a[i].a_val=c[i]+v-sum;
        a[i].b_val=0;
        a[i].val=(double)(a[i].a_val)/(double)(a[i].b_val+1);
        if(a[i].a_val>=(int)(v*0.05)) Heap.push(a[i]);
        else{
            big_ans[i]=0;
            continue;
        }
        int tmp=m;
        while (tmp--){
            node t=Heap.top();Heap.pop();
            cnt[t.num]++;
            t.b_val++;
            t.val=(double)(t.a_val)/(double)(t.b_val+1);
            Heap.push(t);
        }
        for (int j=1;j<=n;j++){
            big_ans[j]=max(big_ans[j],cnt[j]);
        }
    }
    for (int i=1;i<=n;i++) printf("%d ",big_ans[i]);
    printf("\n");
    for (int i=1;i<=n;i++) cc[i]=make_pair(c[i],i);
    sort(cc+1,cc+n+1,greater<ii>());
    for (int i=1;i<=n;i++){
        if(20LL*cc[i].first<v) break;
        int l=0,r=m,mid;
        while (l<=r){
            check(i,mid=l+r>>1)?r=(minn_ans[cc[i].second]=mid)-1:l=mid+1;
        }
    }
    for (int i=1;i<=n;i++) printf("%d ",minn_ans[i]);
}

第三题——K 树(Ktree)

【题目描述】

  • 给出一颗树,求出最大的k使得存在 u,v(u!=v) u , v ( u ! = v ) ,满足两个节点下的k个节点组成的子树完全相等。相等表示子树的位置形状完全一样。

  • 第一眼就知道是树上hash。加上可并堆进行合并生成新的hash值。
  • 需要注意的是,这里的可并堆不一定是左偏树(会导致左右不平衡之后带来整个弹出的超时)。
  • 用一个 p 进制数(p>n)来表示每一个节点,这个数有 depth 位,depth 为这个节点在子树中的深度,这个数在 p 进制下从低到高第 i 位表示这个点的 depth-i 级祖先是其父亲的第几个儿子。

  • 然后把距离大于k的弹出就可以了。


#pragma GCC optimize(3)
#pragma G++ optimize(3)
#include <iostream>
#include <algorithm>
#include <cstdio>
#include <cstring>
#include <cmath>
#include <vector>
#include <map>
#define LL long long
using namespace std;
void fff(){
    freopen("Ktree.in","r",stdin);
    freopen("Ktree.out","w",stdout);
}
const int N=100100,base=100003;
int n,cnt=0;
vector <int> son[N];
map<LL,int> M;
int depth[N];
inline void rea(int &x){
  char c=getchar(); x=0;
  for(;c>'9'||c<'0';c=getchar());for(;c>='0'&&c<='9';x=x*10+c-'0',c=getchar());
}
struct iheap{
    iheap *l,*r;
    LL val,mul,add,key;
    int deep,size;
}h[N],*rt[N];
inline void dfs(int u){
    for (int i=0;i<(int)son[u].size();i++)
        depth[son[u][i]]=depth[u]+1,dfs(son[u][i]);
}
inline void Mul(iheap *x,LL y){
    if(!x) return;
    x->val*=y;x->mul*=y;x->add*=y;x->key*=y;
}
inline void Add(iheap *x,LL y){
    if(!x) return;
    x->val+=y*x->size;x->add+=y;x->key+=y;
}
inline void Push(iheap *x){
    if(x->mul!=1) Mul(x->l,x->mul),Mul(x->r,x->mul),x->mul=1;
    if(x->add) Add(x->l,x->add),Add(x->r,x->add),x->add=0;
}
inline void Up(iheap *x){
    x->size=(x->l?x->l->size:0)+(x->r?x->r->size:0)+1;
    x->val=(x->l?x->l->val:0)+(x->r?x->r->val:0)+x->key;
}

inline int ran(){
    static int x=50125012;x+=(x<<4)+1;return x&65536;
}
iheap *Merge(iheap *x,iheap *y){
    if(!x||!y) return !x?y:x;
    if(x->deep<y->deep) return Merge(y,x);
    Push(x),Push(y);
    #ifdef LOCAL
        int ppp;
        (ppp=ran())?x->l=Merge(x->l,y):x->r=Merge(x->r,y);
        printf("%d\n",ppp);
    #else
    ran()?x->l=Merge(x->l,y):x->r=Merge(x->r,y);
    #endif
    return Up(x),x;
}
bool check(int u,int k){
    for (int i=0;i<(int)son[u].size();i++){
        if(check(son[u][i],k)) return true;
        Mul(rt[son[u][i]],base);
        Add(rt[son[u][i]],i+1);
        rt[u]=Merge(rt[u],rt[son[u][i]]);
    }
    h[++cnt].val=h[cnt].key=0;h[cnt].deep=depth[u];
    h[cnt].mul=1;h[cnt].add=0;h[cnt].l=h[cnt].r=0;h[cnt].size=1;
    rt[u]=Merge(rt[u],h+cnt);
    while (rt[u]&&rt[u]->deep>depth[u]+k)
        Push(rt[u]),rt[u]=Merge(rt[u]->l,rt[u]->r);
    if(!rt[u]||rt[u]->deep!=depth[u]+k) return false;
    return M.count(rt[u]->val)?true:(M[rt[u]->val]=1,false);
}
void clean(){
    M.clear();cnt=0;
    for(int i=1;i<=n;i++) rt[i]=0;
}
int main(){
    #ifndef LOCAL
    fff();
    #endif
    rea(n);
    for(int i=1;i<=n;i++){
        int x,v;
        rea(x);
        for (int j=1;j<=x;j++) rea(v),son[i].push_back(v);
    }
    dfs(1);
    int L=1,R=n,mid,ans=0,sx=0;
    while (L<=R){
        clean();
        if(check(1,mid=(L+R)>>1)) L=(ans=mid)+1;
        else R=mid-1;
    }
    printf("%d\n",ans);
    return 0;
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值