2017CCPC哈尔滨站 题解

A. Palindrome

  • 队友写的马拉车+主席树,待填坑

B. K-th Number

题意

  • 就是给你一个序列 A A A,求 A A A的所有长度大于等于 K K K的子区间的第 K K K大数中的第 M M M
  • 好绕啊,注意第 K K K大是指从大到小数

题解

  • 直接做不好做啊,本来想算每个数放入贡献,然后排序,然而无果
  • 显然第 M M M大是一个单调的概念,考虑二分,然后去找第 K K K大大于等于 m i d mid mid的区间个数, c h e c k check check的时候只用双指针扫一下就行了

复杂度

  • O ( n log ⁡ 1 0 9 ) O(n\log 10^9) O(nlog109)

代码

#include<bits/stdc++.h>
using namespace std;
const int maxn=1e5+10;
int n,k,a[maxn];
long long m;
bool check(int x) {
    int p=0,sum=0;
    long long res=0;
    for(int i=1;i<=n;i++) {
        while(p<=n&&sum<k) if(a[++p]>=x) sum++;
        res+=n-p+1;
        if(a[i]>=x) sum--;
    }
    return res>=m;
}
int main() {
    int t;scanf("%d",&t);
    while(t--) {
        scanf("%d %d %lld",&n,&k,&m);
        for(int i=1;i<=n;i++) scanf("%d",&a[i]);
        int l=1,r=1000000000,ans;
        while(l<=r) {
            int mid=(l+r)>>1;
            if(check(mid)) ans=mid,l=mid+1;
            else r=mid-1;
        }
        printf("%d\n",ans);
    }
}

C. Confliction

  • 留坑

D. X-Men

题意

  • 就是一棵树上有一些人,每个人都会在距离自己大于1的所有人当中随机选一个,然后朝着他走一步,耗时1,当所有人两两间隔都小于等于1的时候游戏结束,求游戏进行期望时间

题解

  • 神题!!!出题人的初衷这是一个签到题,结果最后只过了5个人
  • 啥叫期望?出了数学上的定义,通俗的理解就是平均情况下的状态,那么考虑系统达到平衡最快的方式,就是距离最远的两个人对向而走,其他人也跟着靠拢,所以期望时间为最远点对距离除以二并且向下取整,因为最后距离是1而不是0

复杂度

  • O ( n ) O(n) O(n)

代码

#include<bits/stdc++.h>
using namespace std;
const int maxn=1e5+10;
int n,k,a[maxn];
vector<int> vec[maxn];
 
pair<int,int> dfs(int cur,int fa,int dis)
{
    pair<int,int> res=make_pair(0,0);
    if(a[cur]) res={dis,cur};
    for(int i=0;i<vec[cur].size();i++) if(vec[cur][i]!=fa){
        auto k=dfs(vec[cur][i],cur,dis+1);
        if(k.first>res.first) res=k;
    }
    return res;
}
 
int main()
{
    int t;scanf("%d",&t);
    while(t--) {
        scanf("%d %d",&n,&k);
        for(int i=1,u;i<=k;i++) scanf("%d",&u),a[u]=1;
        for(int i=1,u,v;i<n;i++) {
            scanf("%d %d",&u,&v);
            vec[u].push_back(v);
            vec[v].push_back(u);
        }
        
        auto from=dfs(1,0,0);
        auto to=dfs(from.second,0,0);
        printf("%.2lf\n",(double)(to.first/2));

        for(int i=1;i<=n;i++) a[i]=0,vec[i].clear();
    }
}

F. Permutation

题解

  • 签到,交叉放即可

代码

#include<bits/stdc++.h>
using namespace std;
const int maxn=1e5+10;
int n,ans[maxn];
int main() {
    int t;scanf("%d",&t);
    while(t--) {
        scanf("%d",&n);
        int qwq=0;
        for(int i=1;i<=n;i+=2) ans[i]=++qwq;
        for(int i=2;i<=n;i+=2) ans[i]=++qwq;
        for(int i=1;i<=n;i++) printf("%d%c",ans[i],i==n?'\n':' ');
    }
}

H. A Simple Stone Game

题意

  • 就是有 n n n堆石头,每次可以将一堆中的一个石头放到另一堆中,对应代价为 1 1 1,当所有对的石头数量有一个大于1的公共因子 K K K的时候游戏结束,求游戏结束时的最小代价

题解

  • 首先如果所有石堆都有一个公共因子的话,那么他们的和 s u m sum sum也是这个因子的倍数,枚举 s u m sum sum的质因子,然后对于一个质因子 p i p_i pi,将所有 a i % p i a_i\%p_i ai%pi排序,从大到小满足所有数,即让这个数变成 p i p_i pi产生的代价为 p i − ( a i % p i ) p_i-(a_i\%p_i) pi(ai%pi),至于为什么不会是 2 p i 2p_i 2pi,那么这种情况下只会是至少3个数拼到一堆,对应的代价为所有较小的 n u m − 1 num-1 num1之和,那么还不如合并成多堆大小为 p i p_i pi的数,代价更小

复杂度

  • O ( n log ⁡ n log ⁡ 1 0 9 ) O(n\log n\log 10^9) O(nlognlog109)

代码

#include<bits/stdc++.h>
using namespace std;
const int maxn=1e5+10;
int n,a[maxn],b[maxn];
long long solve(long long k) {
    long long sum=0,ans=0;
    for(int i=1;i<=n;i++) b[i]=a[i]%k,sum+=b[i];
    sort(b+1,b+n+1);
    for(int i=n;i>=1&&sum>0;i--,sum-=k) ans+=k-b[i];
    return ans;
}
int main() {
    int t;scanf("%d",&t);
    while(t--) {
        scanf("%d",&n);long long sum=0,ans=1e18;
        for(int i=1;i<=n;i++) scanf("%d",&a[i]),sum+=a[i];
        for(int i=2;1LL*i*i<=sum;i++) {
            if(sum%i==0) {
                ans=min(ans,solve(i));
                while(sum%i==0) sum/=i;
            }
        }
        if(sum>1) ans=min(ans,solve(sum));
        printf("%lld\n",ans);
    }
}

K. Server

题意

  • 就是给你 n n n个区间 [ l i , r i ] [l_i,r_i] [li,ri],每个区间有两个属性 A A A B B B,然后让你选择这些区间的一个自己 S S S,在所有 S S S中的区间覆盖区间 [ 1 , t ] [1,t] [1,t]的情况下使得下面这个式子最小
    ∑ i ∈ S A i ∑ i ∈ S B i \frac{\sum_{i\in S}^{}{A_i}}{\sum_{i\in S}^{}{B_i}} iSBiiSAi

题解

  • 显然是一个 01 01 01规划问题,二分之后关键在于怎么 c h e c k check check,令
    ∑ i ∈ S A i ∑ i ∈ S B i ≤ m i d \frac{\sum_{i\in S}^{}{A_i}}{\sum_{i\in S}^{}{B_i}}\leq mid iSBiiSAimid

    ∑ i ∈ S A i − m i d × B i ≤ 0 \sum_{i\in S}^{}{A_i-mid\times B_i}\leq 0 iSAimid×Bi0
    那么对于 A i − m i d × B i ≤ 0 A_i-mid\times B_i \leq 0 Aimid×Bi0的区间直接选,然后对于大于0的区间考虑选择一些满足未覆盖的区间,考虑用线段树维护做一个 d p dp dp,即用 d p [ i ] dp[i] dp[i]表示覆盖区间 [ 1 , i ] [1,i] [1,i]对应的最小代价,然后对于一个区间 [ l i , r i ] [l_i,r_i] [li,ri],更新 d p [ r i ] = min ⁡ j = l i − 1 r i d p [ j ] + m a x ( 0 , A i − m i d × B i ) dp[r_i]=\min_{j=l_i-1}^{r_i}{dp[j]}+max(0,A_i-mid\times B_i) dp[ri]=minj=li1ridp[j]+max(0,Aimid×Bi),取 m a x max max的原因是对于负的区间代价为0

复杂度

  • O ( n log ⁡ n log ⁡ m ) O(n \log n \log m) O(nlognlogm)
  • log ⁡ m \log m logm指二分

代码

#include<bits/stdc++.h>
using namespace std;
const int maxn=1e5+10;
#define eps 1e-4
int n,t,m;
struct data{
    int s,t,a,b;
    friend bool operator<(const data &u,const data &v) {
        return u.t==v.t?u.s<v.s:u.t<v.t;
    }
}o[maxn];
namespace segment_tree{
    double minn[maxn<<2];
    inline void update(int id,int L,int R,int pos,double k) {
        if(L==R) {minn[id]=min(minn[id],k);return;}
        int mid=(L+R)>>1;
        if(pos<=mid) update(id<<1,L,mid,pos,k);
        else update(id<<1|1,mid+1,R,pos,k);
        minn[id]=min(minn[id<<1],minn[id<<1|1]);
    }
    inline double query(int id,int L,int R,int l,int r) {
        double res=0x3f3f3f3f;
        if(l<=L&&R<=r) return minn[id];
        int mid=(L+R)>>1;
        if(l<=mid) res=min(res,query(id<<1,L,mid,l,r));
        if(r>mid) res=min(res,query(id<<1|1,mid+1,R,l,r));
        return res;
    }
}
using namespace segment_tree;

bool check(double mid) {
    double neg=0;
    for(int i=1;i<=n;i++) if(o[i].a-mid*o[i].b<0) neg+=o[i].a-mid*o[i].b; 
    for(int i=1;i<=4*m;i++) minn[i]=0x3f3f3f3f;
    update(1,0,m,0,0);
    for(int i=1;i<=n;i++) {
        double now=query(1,0,m,o[i].s-1,o[i].t-1)+max(0.0,o[i].a-mid*o[i].b);
        update(1,0,m,o[i].t,now);
    }
    return neg+query(1,0,m,m,m)<=0;
}
int main() {
    int t;scanf("%d",&t);
    while(t--) {
        scanf("%d %d",&n,&m);
        for(int i=1;i<=n;i++) scanf("%d %d %d %d",&o[i].s,&o[i].t,&o[i].a,&o[i].b);
        sort(o+1,o+n+1);
        double l=0,r=2000;
        while(r-l>eps) {
            double mid=(l+r)/2;
            if(check(mid)) r=mid;
            else l=mid;
        }
        printf("%.3lf\n",r);
    }
}

L. Color a Tree

题意

  • 就是给你一颗根为 1 1 1的树,开始所有节点都是白色的,选择最少的节点涂成黑色满足给定的两种描述,描述如下
    • x i x_i xi为根的子树中至少有 y i y_i yi个黑点
    • 除去以 x i x_i xi为根的子树的所有节点中至少有 y i y_i yi个黑点

题解

  • 首先需要注意到如果黑点个数如果为 k k k能满足所有描述,那么 k + 1 k+1 k+1也一定能满足所有描述,即问题满足单调性,考虑二分
  • 对于一个二分的值 m i d mid mid,尝试将第二种描述转化一下,即转化为以 x i x_i xi为根的子树最多有 m i d − y i mid-y_i midyi个黑点,那么这样的话每个节点对应的子树黑点个数对应一个可行的数量区间,然后做一个简单的树形 d p dp dp,即计算满足所有以节点 i i i为根的子树的所有条件的情况下当前节点的子树黑点数量可行区间,那么如果 m i d mid mid在节点1对应的区间里,则可行
  • 挺巧妙的

复杂度

  • O ( n log ⁡ n ) O(n\log n) O(nlogn)

代码

#include<bits/stdc++.h>
using namespace std;
const int maxn=1e5+10;
vector<int> vec[maxn];
int n,m,k,x[2][maxn],y[2][maxn],l[maxn],r[maxn],siz[maxn];

void dfs_size(int cur,int fa) {
    siz[cur]=1;
    for(int i=0;i<vec[cur].size();i++) if(vec[cur][i]!=fa) {
        dfs_size(vec[cur][i],cur);
        siz[cur]+=siz[vec[cur][i]];
    }
}

void dfs(int cur,int fa) {
    int L=0,R=0;
    for(int i=0;i<vec[cur].size();i++) if(vec[cur][i]!=fa) {
        dfs(vec[cur][i],cur);
        L+=l[vec[cur][i]],R+=r[vec[cur][i]];
    }
    l[cur]=max(l[cur],L),r[cur]=min(r[cur],R+1);

}

bool check(int val) {
    for(int i=1;i<=n;i++) l[i]=0,r[i]=siz[i];
    for(int i=1;i<=m;i++) l[x[0][i]]=max(l[x[0][i]],y[0][i]);
    for(int i=1;i<=k;i++) r[x[1][i]]=min(r[x[1][i]],val-y[1][i]);
    dfs(1,0);
    for(int i=1;i<=n;i++) if(l[i]>r[i]) return false;
    return l[1]<=val&&val<=r[1];
}
int main() {
    int t;scanf("%d",&t);
    while(t--) {
        scanf("%d",&n);
        for(int i=1,u,v;i<n;i++) {
            scanf("%d %d",&u,&v);
            vec[u].push_back(v);
            vec[v].push_back(u);
        }
        dfs_size(1,0);
        scanf("%d",&m);
        bool ok=true;
        for(int i=1;i<=m;i++) {
            scanf("%d %d",&x[0][i],&y[0][i]);
            if(y[0][i]>siz[x[0][i]]) ok=false;
        }
        scanf("%d",&k);
        for(int i=1;i<=k;i++) {
            scanf("%d %d",&x[1][i],&y[1][i]);
            if(y[1][i]>n-siz[x[1][i]]) ok=false;
        }
        if(!ok) {
            printf("-1\n");
            for(int i=1;i<=n;i++) vec[i].clear();
            continue;
        }
        int L=0,R=n,ans=-1;
        while(L<=R) {
            int mid=(L+R)>>1;
            if(check(mid)) R=mid-1,ans=mid;
            else L=mid+1;
        }
        printf("%d\n",ans);
        for(int i=1;i<=n;i++) vec[i].clear();
    }
    return 0;
}

M. Geometry Problem

题意

  • 就是给你 n n n个点,然后让你求一个圆,使得至少有 ⌈ n 2 ⌉ \lceil \frac{n}{2}\rceil 2n个点在圆上

题解

  • 由于至少有一半的点在圆上,又因为三个不再同一条直线上的点可以确定一个圆,那么考虑随机出这三个点,然后 O ( n ) O(n) O(n) c h e c k check check所有点是否在圆上,由于三个点都在构成答案的点集中的概率为 1 8 \frac{1}{8} 81,所以期望随机次数为 8 8 8即可得到答案
  • 打虚拟赛的时候由于最后才想起来随机,忘记特判 n ≤ 4 n\leq4 n4的情况导致一直随机而 T L E TLE TLE,然后交了一发比赛就结束了 Q W Q QWQ QWQ

复杂度

  • O ( n ) O(n) O(n)

代码

#include<bits/stdc++.h>
using namespace std;
const int maxn=1e5+10;
#define eps 1e-4
#define pi acos(-1.0)
int sgn(double k) {return k<-eps?-1:(k<eps?0:1);}
struct point{
    double x,y;
    point(double a=0,double b=0) {x=a;y=b;}
    point operator*(double k) {return point(x*k,y*k);}
    double operator*(point other) {return x*other.x+y*other.y;}
    double operator^(point other) {return x*other.y-y*other.x;}
    point operator/(double k) {return point(x/k,y/k);}
    point operator+(point other) {return point(x+other.x,y+other.y);}
    point operator-(point other) {return point(x-other.x,y-other.y);}
    friend double len(point p) {return sqrt(p.x*p.x+p.y*p.y);}
    friend point rotate(point p1,point p2,double a) {
        point vec=p2-p1;
        double xx=vec.x*cos(a)+vec.y*sin(a);
        double yy=vec.y*cos(a)-vec.x*sin(a);
        return point(p1.x+xx,p1.y+yy);
    }
}p[maxn];
struct line{
    point s,e;
    line(){}
    line(point a,point b) {s=a;e=b;}
    friend point intersect(line l1,line l2) {
        double k=((l2.e-l2.s)^(l2.s-l1.s)/((l2.e-l2.s)^(l1.e-l1.s)));
        return l1.s+(l1.e-l1.s)*k;
    }
};
struct circle{
    point o;
    double r;
    circle() {}
    circle(point a,point b,point c) {
        point p1=rotate((a+b)/2,b,3*pi/2),p2=rotate((b+c)/2,c,3*pi/2);
        o=intersect(line((a+b)/2,p1),line((b+c)/2,p2));
        r=len(o-a);
    }
};

int n;
int main() {
    srand(998244353);
    int t;scanf("%d",&t);
    while(t--) {
        scanf("%d",&n);
        for(int i=1;i<=n;i++) scanf("%lf %lf",&p[i].x,&p[i].y);
        circle res;
        if(n==1) {
            printf("%.10lf %.10lf %.10lf\n",p[1].x,p[1].y,0);
            continue;
        }
        else if(n<=4) {
            printf("%.10lf %.10lf %.10lf\n",(p[1].x+p[2].x)/2,(p[1].y+p[2].y)/2,len(p[2]-p[1])/2);
            continue;
        }
        for(;;) {
            int x=rand()%n+1,y=rand()%n+1,z=rand()%n+1;
            if(x==y||x==z||y==z||(sgn((p[y]-p[x])^(p[z]-p[x]))==0)) continue;
            circle o=circle(p[x],p[y],p[z]);
            int cnt=0;
            for(int i=1;i<=n;i++) if(sgn(o.r-len(p[i]-o.o))==0) cnt++;
            if(cnt>=(n+1)/2 &&fabs(o.o.x)<=1e9 &&fabs(o.o.y)<=1e9 &&fabs(o.r)<1e9) {res=o;break;}
        }
        printf("%.10lf %.10lf %.10lf\n",res.o.x,res.o.y,res.r);
    }
}
  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值